<?php

namespace API\Bouyco;

use API\Bouyco\Traits\Export;
use API\Bouyco\Traits\Import;
use Entity\Model\Dilispec;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use Helpers\Fichier\Fichier;
use Helpers\Log\LogSysteme;
use Helpers\Outil\Date;

class Bouyco
{
    const JWT_FILENAME  = 'BOUYCO_JWT.json';

    const LOG_FILENAME = 'BOUYCO_LOG.log';

    const CODE_API     = 'PARAMSAP';

    /** @var string $token */
    protected string $token;

    /** @var \PDO $pdo */
    protected \PDO $pdo;

    /** @var mixed $body */
    protected $body;

    /** @var string $urlApi */
    protected string $urlApi;

    /** @var Client $client */
    protected Client $client;

    /** @var string $sapAuthorization */
    protected string $sapAuthorization = '';

    /** @var string $bycnApiAuthorization */
    protected string $bycnApiAuthorization;

    /** @var string $keyI020 */
    protected string $keyI020 = '';

    /** @var string $keyI435 */
    protected string $keyI435 = '';

    /** @var string $keyI186 */
    protected string $keyI186 = '';

    /** @var string $clientId */
    protected string $clientId = '';

    /** @var string $clientSecret */
    protected string $clientSecret = '';

    /** @var string $scope */
    protected string $scope = '';

    /** @var string $baseUrlFlex */
    protected string $baseUrlFlex = '';

    /** @var string $baseUrlCloud */
    protected string $baseUrlCloud = '';

    /** @var string $urlMicrosoftToken */
    protected string $urlMicrosoftToken = 'https://login.microsoftonline.com/4a3d9983-e936-4837-9552-9d9126a92eb0/oauth2/v2.0/token';

    /** @var int $nbOiaPerPage */
    public int $nbOiaPerPage = 1000;

    /** @var bool $error */
    public bool $error = false;

    /** @var string $codeError */
    public string $codeError = '';

    /** @var mixed $dataError */
    public $dataError;

    /** @var string $nomFichierRetour */
    public string $nomFichierRetour;

    use Import;
    use Export;

    /**
     * @param \PDO $pdo
     * @param string $nomFichierRetour
     * @param bool $initPostToken
     */
    public function __construct($pdo, $nomFichierRetour, $initPostToken = false)
    {
        try {
            $this->nomFichierRetour = $nomFichierRetour;
            $this->pdo      = $pdo;
            $this->client   = new Client(['verify' => false]);
            $input          = (string) file_get_contents('php://input');
            $bodyArray      = [];

            parse_str($input, $bodyArray);
            $this->setBody($bodyArray);
            $resInitParams = $this->initParams();

            if (is_array($resInitParams)) {
                $this->error = true;
                $this->codeError = 'missingParams';
                $this->dataError = $resInitParams;
            }

            if ($initPostToken) {
                if (!$this->error && $this->codeError != 'missingParams') {
                    $this->initToken();
                }
            }
        } catch (RequestException $e) {
            $returnLog='date : '.date("Y-m-d H:i:s").'
            ';

            /** @phpstan-ignore-next-line */
            $returnLog.= 'url : '.$_SERVER['REQUEST_URI'].'
            
            code : '.$e->getResponse()->getStatusCode().'

            body envoyé : '.json_encode($this->getBody()).'

            Api erreur: ' . $e->getResponse()->getBody()->getContents();
            \Helpers\Fichier\Fichier::createFichierTmp(LogSysteme::getServerName().'.'.self::LOG_FILENAME, $returnLog);
            $this->error = true;
            $this->codeError = 'error_log';
        }
    }

    /**
     * @param mixed $body
     * @return $this
     */
    private function setBody($body)
    {
        $this->body = $body;

        return $this;
    }

    /**
     * @param string $url
     * @return $this
     */
    private function setUrlApi($url)
    {
        $this->urlApi = $url;

        return $this;
    }

    /**
     * @return bool
     */
    public function hasError()
    {
        return $this->error;
    }

    /**
     * @return string
     */
    public function getCodeError()
    {
        return $this->codeError;
    }

    /**
     * @return array|mixed
     */
    public function getDataError()
    {
        return $this->dataError;
    }

    /**
     * @param string $bycnApiAuthorization
     * @return Bouyco
     */
    public function setBycnApiAuthorization(string $bycnApiAuthorization): Bouyco
    {
        $this->bycnApiAuthorization = $bycnApiAuthorization;
        return $this;
    }

    /**
     * @return mixed[]
     */
    public function getBody()
    {
        return $this->body;
    }

    /**
     * @return mixed
     * @throws GuzzleException
     * @throws \Exception
     */
    public function getJwtToken()
    {
        $jwtFilename = LogSysteme::getServerName().'.'.self::JWT_FILENAME;
        $createFile  = false;
        $contents    = null;

        if (Fichier::fileExisteTmp($jwtFilename)) {
            $contents = Fichier::getContentFile($jwtFilename);
            $contents = json_decode((string)$contents, true);
            // On vérifie que le fichier n'est pas vide, s'il est vide on le recréer en regénérant un token
            if (!empty($contents)) {
                $createdAt          = intval($contents['createdAt']);
                $expiration         = intval($contents['expires_in']);
                $timestampExpireAt  = $createdAt + $expiration;
                $currentTimestamp   = time();

                // Si le token a expiré on regénère un token
                if ($currentTimestamp >= $timestampExpireAt) {
                    $createFile = true;
                } else {
                    $access_token = $contents['access_token'];
                    $this->setBycnApiAuthorization($access_token);
                }
            } else {
                $createFile = true;
            }
        } else {
            $createFile = true;
        }

        if ($createFile) {
            $this->setUrlApi($this->urlMicrosoftToken);

            $options = [
                'headers'       => [
                    'Content-Type'          => 'application/x-www-form-urlencoded',
                ],
                'form_params'   => [
                    'grant_type'            => 'client_credentials',
                    'client_id'             => $this->clientId,
                    'client_secret'         => $this->clientSecret,
                    'scope'                 => $this->scope,
                    'requested_token_type'  => 'urn:ietf:params:oauth:grant-type:jwt-bearer'
                ]
            ];

            $response = $this->client->post($this->urlMicrosoftToken, $options);
            $contents = json_decode($response->getBody()->getContents(), true);
            $contents['createdAt'] = time();

            Fichier::createFichierTmp($jwtFilename, (string) json_encode($contents));

            $access_token = $contents['access_token'];
            $this->setBycnApiAuthorization($access_token);
        }

        return $contents;
    }

    /**
     * @return void
     * @throws GuzzleException
     */
    private function initToken(): void
    {
        $token = $this->getJwtToken();

        $this->setBycnApiAuthorization($token['access_token']);
    }

    /**
     * @return array<mixed>
     */
    public function checkToken(): array
    {
        $jwtFilename    = LogSysteme::getServerName().'.'.self::JWT_FILENAME;
        $tokenOk        = false;
        $code           = null;

        if (!Fichier::fileExisteTmp($jwtFilename)) {
            $code = 'tokenNotExist';
        } else {
            $contents = Fichier::getContentFile($jwtFilename);
            $contents = json_decode((string)$contents, true);

            if (empty($contents)) {
                $code = 'tokenNotExist';
            } else {
                $createdAt          = intval($contents['createdAt']);
                $expiration         = intval($contents['expires_in']);
                $timestampExpireAt  = $createdAt + $expiration;
                $currentTimestamp   = time();

                // Si le token a expiré on regénère un token
                if ($currentTimestamp >= $timestampExpireAt) {
                    $code = 'tokenExpired';
                } else {
                    $tokenOk = true;
                }
            }
        }

        return [
            'availability' => $tokenOk,
            'code' => $code,
        ];
    }

    /**
     * Initialise les différents paramètres pour communiquer avec l'API bouygue
     * @return array<mixed>|true
     */
    private function initParams()
    {
        $dilispec = new Dilispec($this->pdo);
        $params = $dilispec->findAll(['TYPEDICO' => self::CODE_API], ['CODEDICO', 'LIB01DICO']);

        $mapping = [
            'CLIENT_ID'             => 'clientId',
            'CLIENT_SECRET'         => 'clientSecret',
            'SCOPE'                 => 'scope',
            'SAP_AUTHORIZATION'     => 'sapAuthorization',
            'KEY_I020'              => 'keyI020',
            'KEY_I186'              => 'keyI186',
            'KEY_I435'              => 'keyI435',
            'BASE_URL_CLOUD'        => 'baseUrlCloud',
            'BASE_URL_FLEX'         => 'baseUrlFlex',
            'URL_MICROSOFT_TOKEN'   => 'urlMicrosoftToken',
        ];

        $paramsEmpty = [];

        foreach ($params as $param) {
            $matching = $mapping[$param['CODEDICO']]??null;
            $this->$matching = $param['LIB01DICO'];
            if (empty($this->$matching)) {
                $paramsEmpty[] = $param['CODEDICO'];
            }
        }

        $limitEntry = $dilispec->find(
            ['TYPEDICO' => 'BYCNAGILE', 'CODEDICO' => 'OA_IMP_NB_LIGNES_PAR_PAGE'],
            ['LIB01DICO'],
            [],
            $this->pdo::FETCH_ASSOC
        );

        if ($limitEntry['LIB01DICO']) {
            $this->nbOiaPerPage = (int) $limitEntry['LIB01DICO'];
        }

        if (!empty($paramsEmpty)) {
            return $paramsEmpty;
        }

        return true;
    }

    /**
     * @param array<mixed> $content
     * @return void
     */
    public function createReturnFile($content)
    {
        if (!empty($content['body'])) {
            $body = json_decode($content['body'], true);
            $message = $body['error']['message']['value'];
        } else {
            $message = $content['message'] ?? $content['data']['message'];
        }

        /** @phpstan-ignore-next-line */
        $newContent = $content['codeHttp'].'|'.$message;

        Fichier::createFichierTmp($this->nomFichierRetour, $newContent);
    }

    /**
     * @param mixed $result
     * @param array<mixed> $params
     * @return array<mixed>
     */
    public function getReturn($result, array $params): array
    {
        if ($result instanceof ClientException) {
            $body = (string)$result->getResponse()->getBody();
            $body = json_decode($body, true);
            $message = $body['error']['message']['value'];

            $return = [
                'codeHttp'  => $result->getCode(),
                'data'      => [
                    'statut'    => 'N',
                    'message'   => $message,
                ]
            ];
        } else {
            if (!$result['result']) {
                $return = [
                    'codeHttp' => 200,
                    'data' => [
                        'statut' => 'O',
                        'message' => 'Aucune donnée à importer',
                    ]
                ];
            } else {
                $return = [
                    'codeHttp' => 200,
                    'data' => [
                        'statut' => 'O',
                        'message' => 'Import réussi',
                    ]
                ];

                if (isset($result['filesCreated'])) {
                    $fichierOut = $params['FichierOut'];

                    Fichier::createFichierTmp($fichierOut, implode('
', $result['filesCreated']));
                }
            }
        }
        return $return;
    }

    /**
     * @param array<mixed> $data
     * @return void
     */
    public function treatRejectedData(array $data)
    {
        $this->processRejectedData($data);
    }
}
