<?php
namespace API\ChorusPro;

use API\Partenaire\BaseOAuth2Ws;
use API\Partenaire\Exception\WsInvalidResponse;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Utils;
use Helpers\Log\LogSysteme;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\SimpleCache\CacheInterface;

/**
 * Operate Chorus
 * @see https://piste.gouv.fr/index.php?option=com_apiportal&view=apitester&usage=api&apitab=tests&apiName=Factures&apiId=10175213-109c-4423-a7ab-05e7a051ea82&managerId=3&type=rest&apiVersion=1.0.0&Itemid=179&swaggerVersion=2.0&lang=fr
 * @_phpstan-type Facture
 * @_phpstan-type \stdClass
 */
class ChorusWs extends BaseOAuth2Ws
{
    private ?Client $client = null;

    private string $baseUrl;

    // Chorus technical account
    private string $chorusLogin;
    private string $chorusPassword;

//_____________________________________________________________________________
// Constructor

    public function __construct(
        int $partnId,
        bool $isProd,
        string $clientId,
        string $clientSecret,
        string $chorusLogin,
        string $chorusPassword,
        CacheInterface $cache
    ) {
        $this->baseUrl = $isProd
            ? 'https://api.piste.gouv.fr'
            : 'https://sandbox-api.piste.gouv.fr';
        $this->chorusLogin = $chorusLogin;
        $this->chorusPassword = $chorusPassword;
        parent::__construct(
            $isProd
                ? 'https://oauth.piste.gouv.fr/api/oauth/token'
                : 'https://sandbox-oauth.piste.gouv.fr/api/oauth/token',
            $clientId,
            $clientSecret,
            $cache,
            'CHORUS_'.$partnId,
        );
    }

    protected function getGuzzleClient() :Client
    {
        if ($this->client == null) {
            $this->client = new Client([
                //'base_uri' => $this->getBaseUrl(),
                'http_errors' => false,
            ]);
        }
        return $this->client;
    }

//_____________________________________________________________________________
// Accessor

    protected function getBaseUrl() :string
    {
        return $this->baseUrl;
    }

//______________________________________________________________________________
// Facture

    /**
     * @return array<object{idFacture:int,dateDepot:string}>
     */
    public function listFacture(\DateTime $dateLimit, int $pageNumber, int $pageSize = 20) :array
    {
        $response = $this->send($request = $this->createRequest(
            'POST',
            '/cpro/factures/v1/rechercher/recipiendaire',
            $json = [
                // 'statutCourant' => 'TODO?',
                'periodeDateDepotDu' => $dateLimit->format('Y-m-d'),
                'paramRecherche' => [
                    'nbResultatsParPage' => $pageSize,
                    'pageResultatDemandee' => $pageNumber,
                    'triColonne' => 'DateDepot',
                    'triSens' => 'Ascendant',
                ],
            ]
        ));
        if (! $this->isValidHttpCodeStatus($response->getStatusCode())) {
            throw new WsInvalidResponse($request, $response);
        }
        /** @var \stdClass{
         *      codeRetour:string,
         *      listeFactures:array<object{idFacture:int}>} */
        $payload = Utils::jsonDecode($response->getBody());
        
        if (!in_array($payload->codeRetour, [
            20000/* Aucun résultat */,
            0/* Ok */
        ])) {
            LogSysteme::prefixErrorLog((string) new \Exception('Invalid codeRetour "'.$payload->codeRetour.'"'));
        }
        
        return $payload->listeFactures ?? [];
    }
    
    /**
     * @return ?\stdClass
     */
    public function getFacture(int $id) :?object
    {
        $response = $this->send($request = $this->createRequest(
            'POST',
            '/cpro/factures/v1/consulter/recipiendaire',
            ['identifiantFactureCPP' => $id]
        ));
        if (! $this->isValidHttpCodeStatus($response->getStatusCode())) {
            throw new WsInvalidResponse($request, $response);
        }
        /** @var \stdClass{
         *      codeRetour:string,
         *      listeFactures:array<object{idFacture:int}>} */
        $payload = Utils::jsonDecode($response->getBody());

        if (empty($payload->facture)) {
            if ($payload->codeRetour == 20000 /* Does not exist */) {
                return null;
            }
            LogSysteme::prefixErrorLog((string) new \Exception('Invalid codeRetour "'.$payload->codeRetour.'"'));
            return null;
        }
        
        return $payload->facture;
    }

    /**
     * @param int $factureId identifier given by listFacture
     */
    public function getLisible(int $factureId) :?StreamInterface
    {
        $response = $this->send($request = $this->createRequest(
            'POST',
            '/cpro/transverses/v1/telecharger/pieceJointe',
            [
                'objetPieceJointe' => 'DEMANDE_PAIEMENT',
                'demandePaiement' => [
                    'format' => 'PDF',
                    'avecPiecesJointesComplementaires' => 'NON',
                    'listeFacture' => [
                        ['idFacture' => $factureId]
                    ]
                ]
            ]
        ));
        if (! $this->isValidHttpCodeStatus($response->getStatusCode())) {
            throw new WsInvalidResponse($request, $response);
        }
        /** @var \stdClass{
         *      codeRetour:string,
         *      listeFactures:array<object{idFacture:int}>} */
        $payload = Utils::jsonDecode($response->getBody());

        if ($payload->codeRetour != 0) {
            LogSysteme::prefixErrorLog((string) new \Exception('Invalid codeRetour "'.$payload->codeRetour.'"'));
            return null;
        }

        $r = fopen('data://text/plain;base64,' . $payload->pieceJointe, 'r');
        if ($r === false) {
            LogSysteme::prefixErrorLog((string) new \Exception('fopen failed'));
            return null;
        }
        return new Stream($r);
    }

    /**
     * @param string $status Enum: [ MISE_A_DISPOSITION, SUSPENDUE, REJETEE, MANDATEE, MISE_EN_PAIEMENT, COMPTABILISEE, MISE_A_DISPOSITION_COMPTABLE, A_RECYCLER, COMPLETEE, SERVICE_FAIT, PRISE_EN_COMPTE_DESTINATAIRE, TRANSMISE_MOA ]
     */
    public function changeStatus(int $id, string $status, string $motif) :void
    {
        $response = $this->send($request = $this->createRequest(
            'POST',
            '/cpro/factures/v1/traiter/recue',
            [
                'idFacture' => $id,
                'motif' => $motif,
                'nouveauStatut' => $status,
            ]
        ));
        if (! $this->isValidHttpCodeStatus($response->getStatusCode())) {
            throw new WsInvalidResponse($request, $response);
        }
    }

//_____________________________________________________________________________
// Sub-routine

    protected function send(Request $request) :ResponseInterface
    {
        $response = $this->getGuzzleClient()->send($request);

        // Case : token expired
        if ($response->getStatusCode() == 403) {
            $request->withHeader('Authorization', 'Bearer '.$this->getAuthToken(false));
            $response = $this->getGuzzleClient()->send($request);
        }
        return $response;
    }

    /**
     * @param ?array<mixed> $jsonPayload
     */
    protected function createRequest(
        string $method,
        string $path,
        ?array $jsonPayload = null
    ) :Request {
        return new Request(
            $method,
            $this->getBaseUrl() . $path,
            [
                'Accept' => 'application/json',
                'Sec-Fetch-Dest' => 'empty',
                'Authorization' => 'Bearer '.$this->getAuthToken(),
                'cpro-account' => base64_encode(
                    $this->chorusLogin.':'.$this->chorusPassword
                )
            ]
            + (!empty($jsonPayload) ? ['Content-Type' => 'application/json'] : []),
            ! empty($jsonPayload) ? Utils::jsonEncode($jsonPayload) : null
        );
    }
}
