<?php
namespace API\Sage;

use API\Partenaire\IFactDematReceiver;
use API\Partenaire\IPartenaire;
use DateTimeInterface;
use Entity\Model\Enum\EFacDematFlowStatus;
use Entity\Model\Paravrac;
use GuzzleHttp\Psr7\Stream;
use Helpers\Fichier\Fichier;
use Helpers\Outil\ArrayHelper;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\StreamInterface;
use Psr\SimpleCache\CacheInterface;

class SagePartenaire implements IPartenaire, IFactDematReceiver
{

    private ContainerInterface $container;
    private int $id;
    private string $label;
    private SageEInvoicingWs $ws;

//_____________________________________________________________________________
// Constructor

    public function __construct(
        ContainerInterface $container,
        int $id,
        string $label,
        string $client,
        string $clientSecret,
        string $datasetId
    ) {
        $this->container = $container;
        $this->id = $id;
        $this->label = $label;
        $this->ws = new SageEInvoicingWs(
            $id,
            $this->container->get(Paravrac::class)->isEnvProd() && !str_ends_with($label, 'test'),
            $client,
            $clientSecret,
            $datasetId,
            $this->container->get(CacheInterface::class)
        );
    }
    
    /**
     * @param array<mixed> $config As given by FactureDematRepo
     */
    public static function getFromConfig(ContainerInterface $container, array $config) :self
    {
        return new SagePartenaire(
            $container,
            $config['ID'],
            $config['LABEL'],
            $config['CLIENT_ID'],
            $config['CLIENT_SECRET'],
            $config['DATASET_ID'],
        );
    }

//_____________________________________________________________________________
// Accessor

    public function getId() :int
    {
        return $this->id;
    }

    public function getLabel(bool $short = false): string
    {
        return $this->label;
    }
    
    public function getCode(): string
    {
        return 'SAGE_'.$this->id;
    }

    public function getWs() :SageEInvoicingWs
    {
        return $this->ws;
    }

//_____________________________________________________________________________
// IFactDematReceiver

    public function getPPFId(): string
    {
        return 'NOT_USED';
    }

    public function listNext(array &$context) :array
    {
        $context['nextPage'] = $context['nextPage'] ?? null;

        $response = $this->getWs()->listInvoice(
            $context['dateLimit'],
            $context['nextPage']
        );
        
        $context['isLast'] = true;
        if ($response->meta->hasMore) {
            $context['isLast'] = false;
            // Parse next page token
            $nextPageLink = $response->links->next ?? '';
            parse_str(parse_url($nextPageLink)['query'] ?? '', $query);
            $context['nextPage'] = $query['page']['after'] ?? null;
            if (empty($context['nextPage'])) {
                throw new \Exception('Could not parse next page token from "'.$nextPageLink.'"');
            }
        }
        
        // Convert to Flow
        return array_map(
            fn($invoice) => (object) [
                'flowId' => (string)$invoice->id,
                'flowType' => 'SupplierInvoice',
                'flowProfile' => 'Basic',
                'flowSyntax' => 'UBL',
                'flowDirection' => 'In',
                'submittedAt' => (new \DateTime(
                    $invoice->meta->createdAt
                ))->format(DateTimeInterface::RFC3339),
                'updatedAt' => (new \DateTime(
                    $invoice->meta->createdAt
                ))->format(DateTimeInterface::RFC3339),
            ],
            array_filter( // Filter in incomming invoice, (filter out outgoing invoices)
                $response->data,
                fn($item) => in_array(
                    $item->attributes->eInvoiceTypeCode,
                    ['AP_Invoice', 'AP_CreditNote']
                )
            )
        );
    }

    public function getFlowFile(string $id): StreamInterface
    {
        $resp = $this->getWs()->getInvoice($id, false);
        $att = ArrayHelper::findFirst(
            $resp->attributes->attachments,
            fn($att) => $att->filetype == 'UBL'
        );

        if (empty($att->downloadUrl)) {
            throw new \Exception('Missing UBL for '.var_export($att, true));
        }
        // Note : we cache the stream in order to make it rewindable for Gestion_Fic_PJ::insertFichier
        $cache = Fichier::fopen('php://temp', 'r+');
        stream_copy_to_stream(Fichier::fopen($att->downloadUrl, 'r'), $cache);
        rewind($cache);
        return new Stream($cache);
    }

    public function getCdarFile(string $id): StreamInterface
    {
        // TODO : improve IFactDematReceiver
        throw new \Exception('Not applicable');
    }
    
    public function getReadableViewFile(object $flow): ?StreamInterface
    {
        $resp = $this->getWs()->getInvoice($flow->flowId);
        $att = ArrayHelper::findFirst(
            $resp->attributes->attachments,
            fn($att) => in_array($att->filetype, ['Human_Readable', 'Original_Document'])
        );

        if (empty($att->downloadUrl)) {
            throw new \Exception('Missing Human_Readable for '.var_export($att, true));
        }
        // Note : we cache the stream in order to make it rewindable for Gestion_Fic_PJ::insertFichier
        $cache = Fichier::fopen('php://temp', 'r+');
        stream_copy_to_stream(Fichier::fopen($att->downloadUrl, 'r'), $cache);
        rewind($cache);
        return new Stream($cache);
    }
    
    public function updateStatus(string $flowId, array $cdar) :void
    {
        $dematStatus = EFacDematFlowStatus::from($cdar['status']['code']);
        $status = match ($dematStatus) {
            EFacDematFlowStatus::PrisEnCharge => '40',
            EFacDematFlowStatus::Refuse => '12',
            EFacDematFlowStatus::Litige => '70',
            EFacDematFlowStatus::Appro => '50',
            EFacDematFlowStatus::ApproPartiel => '60',
            EFacDematFlowStatus::Suspendue => '80',
            EFacDematFlowStatus::Complet => '14',
            EFacDematFlowStatus::PaieTrans => '90',
            default => null,
        };
        if (! $status) {
            return;
        }
        $note = $dematStatus == EFacDematFlowStatus::Refuse
            ? $cdar['status']['reason']->value
            : null;
        $this->getWs()->updateStatus($flowId, $status, $note);
    }
}
