<?php
namespace API\FactDematDefault;

use API\Partenaire\BaseOAuth2Ws;
use API\Partenaire\Exception\WsInvalidResponse;
use Entity\Model\Enum\EFacDematFlowStatus;
use GuzzleHttp\Psr7\MultipartStream;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Utils;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\SimpleCache\CacheInterface;
use Skeleton\Renderer\RendererInterface;
use Skeleton\Renderer\TwigRenderer;

/**
 * @phpstan-import-type Flow from \API\FactDematDefault\FactDematDefaultPartenaire
 * @phpstan-import-type SearchResult from \API\FactDematDefault\FactDematDefaultPartenaire
 * @phpstan-import-type CdarInfo from \API\FactDematDefault\FactDematDefaultPartenaire
 */
class FactDematDefaultWs extends BaseOAuth2Ws
{
    private string $baseUrlFlow;

//_____________________________________________________________________________
// Constructor

    public function __construct(
        string $partnCode,
        string $baseUrl,
        string $authUrl,
        string $login,
        string $secret,
        CacheInterface $cache
    ) {
        parent::__construct(
            $authUrl,
            $login,
            $secret,
            $cache,
            'FAC_DEMAT_WS_'.$partnCode
        );
        $this->baseUrlFlow = trim($baseUrl, '/');
    }

//_____________________________________________________________________________
// Accessor

    protected function getBaseUrlFlow() :string
    {
        return $this->baseUrlFlow;
    }

//______________________________________________________________________________
// Flow retrieval

    /**
     * @return SearchResult
     */
    // public function listSupplierInvoice(\DateTime $date, int $limit) :object
    // {
    //     $response = $this->send($request = $this->createRequestFlow(
    //         'POST',
    //         '/flows/search',
    //         [
    //             'limit' => $limit,
    //             'where' => [
    //                 'updatedAfter' => $date->format(\DateTime::RFC3339),
    //                 'flowType' => ['SupplierInvoice', 'CustomerInvoice'],
    //                 'ackStatus' => 'Ok',
    //             ],
    //         ]
    //     ));
        
    //     if (! $this->isValidHttpCodeStatus($response->getStatusCode())) {
    //         throw new WsInvalidResponse($request, $response);
    //     }

    //     /** @var SearchResult */
    //     /** @var \stdClass */
    //     $payload = Utils::jsonDecode($response->getBody());

    //     return (object)[
    //         'total' => $payload->total,
    //         'results' => $payload->results,
    //     ];
    // }

    /**
     * @return SearchResult
     */
    public function listFlow(\DateTime $date, int $limit) :object
    {
        $response = $this->send($request = $this->createRequestFlow(
            'POST',
            '/flows/search',
            $this->createListFlowPayload($date, $limit)
        ));
        
        if (! $this->isValidHttpCodeStatus($response->getStatusCode())) {
            throw new WsInvalidResponse($request, $response);
        }

        /** @var SearchResult */
        /** @var \stdClass */
        $payload = Utils::jsonDecode($response->getBody());

        return (object)[
            'total' => $payload->total,
            'results' => $payload->results,
        ];
    }

    /**
     * Note : this function exist only to be overrided by AgenaWs as thier API does not support ackStatus yet
     * @return array<mixed>
     */
    protected function createListFlowPayload(\DateTime $date, int $limit): array
    {
        return [
            'limit' => $limit,
            'where' => [
                'updatedAfter' => $date->format(\DateTime::RFC3339),
                'flowType' => [
                    'SupplierInvoice', 'CustomerInvoice',
                    'SupplierInvoiceLC', 'CustomerInvoiceLC',
                ],
                'ackStatus' => 'Ok',
            ],
        ];
    }

    public function getFlowFile(string $flowId, ?EFlowFileType $type = null) :StreamInterface
    {
        $response = $this->send($request = $this->createRequestFlow(
            'GET',
            '/flows/'.$flowId
            . '?'
            . ($type != null ? http_build_query([
                'docType' => match ($type) {
                    EFlowFileType::Metadata => 'Metadata',
                    EFlowFileType::Original => 'Original',
                    EFlowFileType::Converted => 'Converted',
                    EFlowFileType::ReadableView => 'ReadableView',
                }
            ]) : '')
        ));
        
        if (! $this->isValidHttpCodeStatus($response->getStatusCode())) {
            throw new WsInvalidResponse($request, $response);
        }

        return $response->getBody();
    }

    /**
     * @param CdarInfo $cdar
     */
    public function updateStatus(RendererInterface $renderer, array $cdar) :void
    {
        // echo $renderer->render(
        //     '@Facture/Demat/_Cdar',
        //     $cdar
        // );
        // header('Content-Type: application/xml');
        // //echo $cdarDoc->saveXML();
        // @exit;

        $multipart = new MultipartStream([
            [
                'name'     => 'flowInfo',
                'contents' => Utils::jsonEncode([
                    'flowSyntax' => 'CDAR',
                    'name' => 'CDAR_' . $cdar['FAC_ENT']['FACT_FOU'] . '_' .date('Y_m_d__H_i_s'),
                ]),
                'headers' => ['Content-Type' => 'application/json'],
            ],
            [
                'name'     => 'file',
                'contents' => $renderer->render('@Facture/Demat/_Cdar', $cdar),
                'filename' => 'cdar.xml',
                'headers' => ['Content-Type' => 'application/xml'],
            ],
        ]);
        ;
        $response = $this->send(
            $request = $this->createRequestFlow('POST', '/flows')
                ->withHeader('Content-Type', 'multipart/form-data; boundary='.$multipart->getBoundary())
                ->withBody($multipart)
        );
        
        if (! $this->isValidHttpCodeStatus($response->getStatusCode())) {
            throw new WsInvalidResponse($request, $response);
        }

        $response->getBody();
        // // TODO : handle response
    }

//_____________________________________________________________________________
// Sub-routine

    protected function send(Request $request) :ResponseInterface
    {
        $resp = $this->getGuzzleClient()->send($request);
        // Try refreshing the token
        if (in_array($resp->getStatusCode(), [403, 401])) {
            $resp = $this->getGuzzleClient()->send($this->addToken($request, false));
        }
        return $resp;
    }

    /**
     * @param ?array<mixed> $jsonPayload
     */
    protected function createRequestFlow(
        string $method,
        string $path,
        ?array $jsonPayload = null
    ) :Request {
        return $this->addToken(
            new Request(
                $method,
                $this->getBaseUrlFlow() . $path,
                ['Accept' => 'application/json']
                + (! empty($jsonPayload) ? ['Content-Type' => 'application/json'] : []),
                ! empty($jsonPayload) ? Utils::jsonEncode($jsonPayload) : null
            )
        );
    }

    protected function addToken(Request $request, bool $useCache = true) :Request
    {
        return $request->withHeader('Authorization', 'Bearer '.$this->getAuthToken($useCache));
    }
}
