<?php
namespace API\Sage;

use API\Partenaire\BaseOAuth2Ws;
use API\Partenaire\Exception\WsInvalidResponse;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Utils;
use Helpers\Outil\MemoizeTrait;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\SimpleCache\CacheInterface;

/**
 * Operate Sage EInvoicing
 * @see https://developer.sage.com/network/docs/v1.0.0/solutions/e-invoicing/invoices/retrieve-an-e-invoice
 * @phpstan-type ListInvoiceResponse object{
 *     data:array<object{
 *          id:string,
 *          meta:object{createdAt:string},
 *          attributes:object{
 *              eInvoiceTypeCode:string
 *         }
 *     }>,
 *     links:object{self:string,next:string},
 *     meta:object{count:int,hasMore:bool}
 * }
 * @phpstan-type InvoiceResponse object{
 *      id:string,
 *      attributes:object{
 *          attachments:array<object{
 *               filename:string,
 *               mimetype:string,
 *               downloadUrl:string,
 *               filetype:string
 *         }>
 *     }
 * }
 */
class SageEInvoicingWs extends BaseOAuth2Ws
{
    private ?Client $client = null;

    private string $baseUrl;
    private string $datasetId;
    private string $audience;

    use MemoizeTrait;

//_____________________________________________________________________________
// Constructor

    public function __construct(
        int $partnId,
        bool $isProd,
        string $clientId,
        string $clientSecret,
        string $datasetId,
        CacheInterface $cache
    ) {
        $this->baseUrl = $isProd
            ? 'https://api.e-invoicing.sage.com'
            : 'https://api-sbx.e-invoicing.sage.com';
        $this->datasetId = $datasetId;
        $this->audience = $isProd
            ? 'snet-cloudops/network/api'
            : 'snet-cloudops/sbx/network/api';
        parent::__construct(
            'https://id.auth.sage.com/oauth/token',
            $clientId,
            $clientSecret,
            $cache,
            'SAGE_'.$partnId,
        );
    }

    protected function getGuzzleClient() :Client
    {
        if ($this->client == null) {
            $handler_stack = HandlerStack::create();

            $handler_stack->push(Middleware::mapRequest(function (RequestInterface $request) {
                $request = $request->withoutHeader('User-Agent');
                //$request = $request->withoutHeader('Accept');
                $request = $request->withHeader('Cache-Control', 'no-cache');
                return $request;
            }));
            $this->client = new Client([
                //'base_uri' => $this->getBaseUrl(),
                'http_errors' => false,
                'handler' => $handler_stack,
            ]);
        }
        return $this->client;
    }

//_____________________________________________________________________________
// Accessor

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

//_____________________________________________________________________________
// OAuth2

    protected function getAuthPayload() :array
    {
        return [
            'grant_type' => 'client_credentials',
            'client_id' => $this->getClientId(),
            'client_secret' => $this->getClientSecret(),
            'audience' => $this->audience,
        ];
    }

//______________________________________________________________________________
// Facture

    /**
     * @return ListInvoiceResponse
     */
    public function listInvoice(\DateTime $dateLimit, ?string $pageToken = null) :object
    {
        // Max interval 90days (~3 month)
        $dateLimit = max($dateLimit, new \DateTime('-90 days'));

        $response = $this->send($request = $this->createRequest(
            'GET',
            '/datasets/'.$this->datasetId.'/e-invoices?'
            . http_build_query([
                'filter' =>
                    // Created after $dateLimit
                    'createdAt ge '. $this->formatDate($dateLimit)
                    . ' and createdAt le ' . $this->formatDate(new \DateTime())
                    // Status == Mise à disposition
                    . " and currentStatus/statusCode eq '00000000-0000-0000-0000-300000000000'"
            ] + (!empty($pageToken) ? [
                'page' => ['after' => $pageToken]
            ] : []))
        ));
        if (! $this->isValidHttpCodeStatus($response->getStatusCode())) {
            throw new WsInvalidResponse($request, $response);
        }
        /** @var ListInvoiceResponse */
        $payload = Utils::jsonDecode($response->getBody());
        
        return $payload;
    }
    
    /**
     * @return InvoiceResponse
     */
    public function getInvoice(string $id, bool $useCache = true) :?object
    {
        if ($useCache == false) {
            $this->memoizeClear();
        }

        // We use a cache to prevent a second fetch on SagePartenaire::getReadableViewFile
        return $this->memoize('SageEInvoicingWs::getInvoice', [$id], function ($id) {
            $response = $this->send($request = $this->createRequest(
                'GET',
                '/datasets/'.$this->datasetId.'/e-invoices/'.$id
            ));
            if (! $this->isValidHttpCodeStatus($response->getStatusCode())) {
                throw new WsInvalidResponse($request, $response);
            }
            /** @var object{data:InvoiceResponse} */
            $payload = Utils::jsonDecode($response->getBody());
            
            return $payload->data;
        });
    }

    /**
     * @param string $status without the guiid format ('00000000-0000-0000-0000-{$status}0000000000')
     */
    public function updateStatus(string $id, string $status, ?string $motif = null) :void
    {
        $response = $this->send($request = $this->createRequest(
            'POST',
            '/datasets/'.$this->datasetId.'/e-invoices/'.$id.'/status',
            [
                'data' => [
                    'type' => 'e-InvoiceStatus',
                    'attributes' => [
                        'statusCode' => '00000000-0000-0000-0000-'.$status.'0000000000',
                        'deliveredAt' => null,
                        'notes' => null,
                        'rejectionReason' => $motif,
                    ],
                ],
            ],
            ['Idempotency-Key' => 'updateStatus_' . implode('_', [$id,$status])]
        ));
        if (! $this->isValidHttpCodeStatus($response->getStatusCode())) {
            throw new WsInvalidResponse($request, $response);
        }
    }

//_____________________________________________________________________________
// Sub-routine

    /**
     * Sage does not suppport H:i:sP (aka +0X:00) so we force it into 'Z'
     */
    private function formatDate(\DateTime $date) :string
    {
        $date = clone $date;
        $date->setTimezone(new \DateTimeZone('GMT+00:00'));
        return $date->format('Y-m-d\TH:i:sp');
    }

    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
     * @param array<mixed> $header
     */
    protected function createRequest(
        string $method,
        string $path,
        ?array $jsonPayload = null,
        ?array $header = []
    ) :Request {
        return new Request(
            $method,
            $this->getBaseUrl() . $path,
            $header + [
                'Accept' => 'application/json',
                'Authorization' => 'Bearer '.$this->getAuthToken(),
            ]
            + (!empty($jsonPayload) ? ['Content-Type' => 'application/json'] : []),
            ! empty($jsonPayload) ? Utils::jsonEncode($jsonPayload) : null
        );
    }
}
