<?php
namespace API\Edicom;

use API\Partenaire\IFactDematReceiver;
use Helpers\Fichier\Gestion_Fic_PJ;
use Helpers\Log\LogSysteme;
use Helpers\Outil\XPathHelper;
use Modules\Facture\Service\FactureDematBatch;
use Psr\Container\ContainerInterface;

/**
 * @phpstan-import-type SubcripItem from \API\Edicom\EdicomWs
 */
class EdicomFactureBatch extends FactureDematBatch
{
    private EdicomPartenaire $partn;

    public function __construct(ContainerInterface $container, EdicomPartenaire $partn)
    {
        parent::__construct($container);
        $this->partn = $partn;
    }
    
    public function batch(string $numeroTraitement): string
    {
        /** @var \stdClass */
        $report = (object) [
            'processed' => 0,
            'failed' => 0,
        ];
        $partn = $this->partn;
        $logger = new \Helpers\Log\OxaEventDet($this->getPdo(), $numeroTraitement);

        //_________________________
        // Process UBL

        /**
        Edicom Workflow
            Le message disparait momentanément après sa récupération via EdicomWs::getFile, elle se resouscrit 1h après.
            EdicomWs::confirmMessage permet de faire disparaitre le message de façons permanente. 
         */
        $prevListId = null;
        while ($list = $partn->listUblItem()) {
            $listId = array_column($list, 'id');

            // Failsafe if listSubcription failed to actualise
            if ($prevListId != null && empty(array_diff($listId, $prevListId))) {
                throw new \Exception('listSubcription failed to actualize' . var_export([$listId, $prevListId], true));
            }
            $prevListId = $listId;

            foreach ($list as $item) {
                try {
                    $this->processFlowUbl($partn, (object) [
                        'flowId' => $item->fullId,
                        'flowType' => 'SupplierInvoice',
                        'flowProfile' => 'Basic',
                        'flowSyntax' => 'UBL',
                        'flowDirection' => 'In',
                        'submittedAt' => $item->creationTime,
                        'updatedAt' => $item->date,
                    ]);
                    $partn->getWs()->confirmMessage([$item->id]);
                    $report->processed++;
                } catch (\Exception $e) {
                    $report->failed++;
                    LogSysteme::prefixErrorLog((string) $e);
                }
            }
        }

        $logger->setLog(
            $report->failed ? 'E' : 'O',
            'EDICOM Partenaire processed ; ' . $report->failed . ' flow error',
            'Partenaire EDICOM (#' . $partn->getId() . ')'
            . ' ; processed: ' . $report->processed
            . ' ; failed: ' . $report->failed,
        );

        //_________________________
        // Process Status

        $prevListId = null;
        while ($list = $partn->listDocStatusItem()) {
            $listId = array_column($list, 'id');

            // Failsafe if listSubcription failed to actualise
            if ($prevListId != null && empty(array_diff($listId, $prevListId))) {
                throw new \Exception('listSubcription failed to actualize');
            }
            $prevListId = $listId;

            foreach ($list as $item) {
                try {
                    $doc = new \DOMDocument();
                    $b = $doc->loadXML($partn->getCdarFile($item->fullId)->getContents());
                    if ($b == false) {
                        throw new \Exception('Unreadable flow #'.$item->fullId);
                    }
                    $listUpdateStatus = $this->parseDocStatus($doc);
                    
                    $this->processStatusUpdate($partn, $listUpdateStatus);
                    $partn->getWs()->confirmMessage([$item->id]);
                    $report->processed++;
                } catch (\Exception $e) {
                    $report->failed++;
                    LogSysteme::prefixErrorLog((string) $e);
                }
            }
        }

        return $report->failed != 0 ? 'E':'O';
    }

    /**
     * @return array{factureId:string,statusCode:string,factureIssueDate:string}[]
     */
    protected function parseDocStatus(\DOMDocument $doc)
    {
        $xpath = new XPathHelper($doc);
        $listRefDoc = iterator_to_array(
            $xpath->mustQuery('./REFERENCED_DOCUMENT')
        );

        $tmp = [];
        foreach ($listRefDoc as $refDoc) {
            $xpath->setContext($refDoc);
            $factureId = $xpath->queryFirstValue('./Document_identifier');
            $statusCode = $xpath->queryFirstValue('./Process_condition_code');

            // Expect format YmdHis
            $date = substr($xpath->queryFirstValue('./Issue_date'), 0, 8);
            
            if (empty($factureId) || empty($statusCode) || empty($date)) {
                throw new \Exception('Invalid data');
            }

            $tmp[] = [
                'factureId' => $factureId,
                'statusCode' => $statusCode,
                'factureIssueDate' => $date,
            ];
        }
        return $tmp;
    }

    protected function createFlowEntityFromUBL(IFactDematReceiver $partn, object $flow) :array
    {
        $fileManager = new Gestion_Fic_PJ($this->getPdo());

        $pathUBL = $fileManager->insertFichier(
            'partn_' . $partn->getId() . '_flow_' . $flow->flowId . '_ubl.xml',
            $ublStream = $partn->getFlowFile($flow->flowId)
        );

        if ($pathUBL === false) {
            throw new \Exception('insertFichier failed');
        }

        // Human readable file is Attached document #999
        $doc = new \DOMDocument();
        if ($doc->loadXML($ublStream->getContents()) === false) {
            throw new \Exception('Unparsable UBL for flow "' . $flow->flowId . '"');
        }

        $xpather = (new XPathHelper($doc, null, [
            'cac' => 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
            'cbc' => 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2',
        ]));
        $node =
            $xpather
            ->mustQuery("./cac:AdditionalDocumentReference[cbc:ID='999']")
            ->item(0);

        if (!empty($node)) {
            $xpather->setContext($node);
            $base64 = $xpather->queryFirstValue("./cac:Attachment/cbc:EmbeddedDocumentBinaryObject");
            // Remove it to avoid future processing
            $node->parentElement->removeChild($node);
            
            $pathLisible = $fileManager->insertFichier(
                'partn_' . $partn->getId() . '_flow_' . $flow->flowId . '_lisible.pdf',
                base64_decode($base64),
            );
            
            if ($pathLisible === false) {
                throw new \Exception('insertFichier failed');
            }
        }
        
        return [
            'FIC_UBL'     => $pathUBL,
            'FIC_LISIBLE' => $pathLisible ?? '',
            'mappage' => $this->getMapper()->map($doc, $this->getPdo()),
        ];
    }
}
