<?php
namespace API\OnceForAll;

use API\Partenaire\Exception\WsInvalidResponse;
use API\Partenaire\IBatch;
use API\Partenaire\IFournissCustomAction;
use API\Partenaire\IFournissDocExt;
use API\Partenaire\IFournissLink;
use API\Partenaire\IPartenaire;
use API\Partenaire\Repository\PartenaireRepo;
use API\Partenaire\Service\PartenaireService;
use Entity\Model\Country;
use Entity\Model\Dilispec;
use Entity\Model\Docatt;
use Entity\Model\Fourniss;
use Entity\Model\FourPartenaireData;
use Entity\Model\Metadata\EntityMetaQb;
use Entity\Model\Metadata\PartnFournissExtId;
use Entity\Model\ParamDico;
use Entity\Model\Paravrac;
use Entity\Model\XtEve;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Utils;
use Helpers\Fichier\Fichier;
use Helpers\Log\LogSysteme;
use Helpers\Outil\ArrayHelper;
use Helpers\Outil\StringHelper;
use Helpers\Session\ParseBase1;
use Helpers\Trad\ITrad;
use Helpers\Trad\Trad;
use Modules\API\APIExterne\Repository\Partenaire;
use Psr\Container\ContainerInterface;
use Psr\SimpleCache\CacheInterface;
use Skeleton\Router;

/**
 *
 * @phpstan-import-type Pagination from OnceForAllWs
 * @phpstan-import-type SubcontractorInvite from OnceForAllWs
 */
class OnceForAllPartenaire implements IPartenaire, IBatch, IFournissDocExt, IFournissCustomAction, IFournissLink
{
    private ContainerInterface $container;
    private OnceForAllWs $webservice;

    /**
     * Set with webservice
     * Soletenche: 6252851 en staging, 9467013 en production, correspondant au SIRET 56213454400153
     */
    private int $orgaId;


    /**
     * Numéro du champs taxe pour le codice fiscale
     */
    private string $codiceFiscaleField;
    
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

//_____________________________________________________________________________
// Accessor

    public function getCode() :string
    {
        return 'OFA';
    }

    public function getLabel(bool $short = false) :string
    {
        return 'Attestation Légale - Once For All';
    }

    public function getCodiceFiscaleField() :string
    {
        $this->getWs();// load config
        return $this->codiceFiscaleField;
    }

    public function getWs() :OnceForAllWs
    {
        if (! isset($this->webservice)) {
            /** @var \PDO */
            $pdo = $this->container->get('PDO.oxasql');
            $dilispec = (new Dilispec($pdo))->findAll(['TYPEDICO' => $this->getCode()]);
            
            $paramDB = array_column($dilispec, 'LIB01DICO', 'CODEDICO');
            $param = Utils::jsonDecode(
                Fichier::fileGetContents((new ParseBase1())->getRep()->etc.'API/externe/'.$this->getCode().'/Parametres.json'),
                true
            );
            /** @phpstan-ignore-next-line */
            $env = $param[ $paramDB['TYPE_ENVIRONNEMENT'] ];
            $this->webservice = new OnceForAllWs(
                $env['base_url'],
                $env['auth_url'],
                $paramDB['CLIENT_ID'],
                $paramDB['CLIENT_SECRET'],
                $this->container->get(CacheInterface::class)
            );
            $this->orgaId = intval($paramDB['ORGA_ID']);
            $this->codiceFiscaleField = $paramDB['CODICE_FISCALE'];
        }
        return $this->webservice;
    }

    protected function getService() :PartenaireService
    {
        return $this->container->get(PartenaireService::class);
    }

    protected function getPdo() :\PDO
    {
        return $this->container->get('PDO.oxasql');
    }

    protected function getRouter() :Router
    {
        return $this->container->get(Router::class);
    }

    protected function getTrad() :ITrad
    {
        return $this->container->get(ITrad::class);
    }

//_____________________________________________________________________________
// Batch

    public function batch(string $numeroTraitement = '') :string
    {
        $pdo = $this->getPdo();
        $log = new \Helpers\Log\OxaEventDet($pdo, $numeroTraitement);
        
        $ws = $this->getWs();

        $qbFourniss = new Fourniss($pdo);
        $qbMeta = new EntityMetaQb($pdo);
        $qbFourPartenaireData = new FourPartenaireData($pdo);

        //____________________
        // Link fourniss to subcontractor
        
        $pairFournissOrga = [];

        /** @var Pagination */
        $page = 0;
        $pageLast = null;
        while (($pageLast ?? 0) >= $page) {
            $payload = $ws->listSubcontractor($this->orgaId, [
                'page' => 0,
                'size' => 1500,
            ]);
            
            $pageLast = $payload->pagination->last;

            $mapOrga = ArrayHelper::indexBy(
                $payload->list,
                function ($orga) {
                    return strtoupper($orga->registrationNumber);
                }
            );

            $pdo->beginTransaction();
            
            $listFourniss = (new PartenaireRepo($pdo))->searchFourniss(
                array_keys($mapOrga),
                ['NO_SIRET','NO_TVA_IC']
            );

            // Link Fourniss to subcontractor
            foreach ($listFourniss as $fourniss) {
                $numberRegis = strtoupper(
                    in_array($fourniss['CODE_PAYS'], ['FR', 'DE'])
                    ? $fourniss['NO_SIRET'] : $fourniss['NO_TVA_IC']
                );
                
                $listOrga = $mapOrga[ $numberRegis ] ?? null;
                if ($listOrga === null) {
                    continue;
                }
                
                $fournissCountryIso = Country::matchCodeToIso2($fourniss['CODE_PAYS']);
                $orga = ArrayHelper::findFirst($listOrga, function ($orga) use ($fournissCountryIso) {
                    return $fournissCountryIso['iso2'] == $orga->country;
                });

                if ($orga === null) {
                    continue;
                }
    
                $qbMeta->persist([new PartnFournissExtId(
                    $fourniss['CODE'],
                    $this->getCode(),
                    $orga->id
                )]);

                $qbFourPartenaireData->insertEntity(
                    $fourniss['CODE'],
                    $this->getCode(),
                    true,
                    false
                );
    
                $pairFournissOrga[] = [
                    'fourniss' => $fourniss['CODE'],
                    'subcontractor' => $orga->id,
                ];
            }
            $pdo->commit();

            $page++;
        }

        //____________________
        // TODO : update doc status
    // TODO : try-catch

        foreach ($pairFournissOrga as $pair) {
            $this->updateDocStatus($pair['fourniss'], $pair['subcontractor']);
        }
        
        if ($numeroTraitement) {
            $log->setLog(
                'O',
                'Mise à jour de '.count($pairFournissOrga).' fournisseur(s)'
            );
        }
        
        return 'O';
    }

    private function updateDocStatus(string $fournissId, int $subcontId) :void
    {
        $pdo = $this->getPdo();
        $qbDocAtt = new Docatt($pdo);
        $mapDocMapping = array_column(
            (new \Modules\Fournisseur\Repository\DocAtt($pdo))
            ->getCanvaForPartenaire($fournissId, $this->getCode()),
            null,
            'Corresp'
        );

        if (empty($mapDocMapping)) {
            return;
        }

        // TODO : try-catch
        $listDoc = $this->getWs()->listSubcontractorDocument(
            $this->orgaId,
            $subcontId,
            ['code' => array_keys($mapDocMapping)]
        );

        $pdo->beginTransaction();
        foreach ($listDoc as $doc) {
            $mappingCanva = $mapDocMapping[ $doc->type->code ] ?? null;
            if (empty($mappingCanva)) {
                continue;
            }

            $tmpId = StringHelper::toCsv([
                $this->getCode(),
                $doc->id,
                ($doc->downloadUrl ?? '')
            ]);

            // Fail-safe
            if (strlen($tmpId) > 200) {
                LogSysteme::prefixErrorLog('File id "'.$tmpId.'" is too long');
                continue;
            }

            $docAttId = [
                'NO_DA' => 'FOURNISS',
                'COD_ENR' => $fournissId,
                //!\ TITRE is not actually part of the primary key, but in the context of the DocCanva, it is used as one
                'TITRE' => $mappingCanva['TITRE_DOC'],
            ];
            $docAttOld = $qbDocAtt->find($docAttId);
            $docAttId['NO_ORDRE'] = $docAttOld['NO_ORDRE'] ?? null;

            // Missing doc must not be in DocAtt
            /** @phpstan-ignore-next-line */
            if (! in_array($doc->state, ['SIGNED', 'VALID', 'ALMOST_EXPIRED', 'EXPIRED'])) {
                if (!empty($docAttOld)) {
                    $qbDocAtt->delete($docAttId);
                }
                continue;
            }

            $docAttNew = $docAttId + [
                'NOM_FIC' => $tmpId,
                //'DT_PARUT' => (new \DateTime($doc->depositDate ?? date('Y-m-d')))->format('Ymd'),
                'DT_PARUT' =>
                    empty($doc->validityStartDate)
                    ? '00000000'
                    : (new \DateTime($doc->validityStartDate))->format('Ymd'),
                'DT_VALID' =>
                    empty($doc->validityEndDate)
                    ? '00000000'
                    : (new \DateTime($doc->validityEndDate))->format('Ymd'),
                'GERE_PAR_PARTENAIRE' => true,
                'COPIE' => false,
                'CODE_CAT' => $mappingCanva['CODE_CATEGORIE'],
            ];

            if (empty($docAttOld)) {
                $qbDocAtt->insertForFourniss($mappingCanva, $docAttNew);
            } else {
                $qbDocAtt->update($docAttNew, $docAttId);
            }
        }

        $pdo->commit();
    }

//_____________________________________________________________________________
// IFournissLink
// Fournisseur are linked automatically by the batch

    public function getRouteDetail() :?string
    {
        return null;
    }
    
    public function getRouteLink() :string
    {
        throw new \Exception('Not implemented, link by batch only');
    }
    public function getRouteUnlink() :string
    {
        throw new \Exception('Not implemented, link by batch only');
    }

    public function isLinkable(array $fourniss) :bool
    {
        return false;
    }

    public function linkFourniss(array $fourniss) :void
    {
        throw new \Exception('Not implemented, link by batch only');
    }

    public function unlinkFourniss(string $fournissCode) :void
    {
        throw new \Exception('Not implemented, unlink by batch only');
    }

//_____________________________________________________________________________
// Fourniss doc

    /**
     * @param array<mixed> $fourniss Entity Fourniss
     * @return void
     */
    public function preprocessDocAtt(array $fourniss) :void
    {
        $qbMeta = new EntityMetaQb($this->getPdo());

        // Get external Id
        $meta = $qbMeta->findByEntityByStruct(
            Fourniss::class,
            $fourniss['CODE'],
            PartnFournissExtId::class
        )[0] ?? null;
        if (empty($meta)) {
            return;
        }
        try {
            $this->updateDocStatus($fourniss['CODE'], intval($meta->getExternalId()));
        } catch (\Exception $e) {
            LogSysteme::prefixErrorLog($e);
            return;
        }
    }

    /**
     * @param string $fournissCode
     * @param array<mixed> $listDocAtt
     * @return array<mixed>
     */
    public function processListDocAtt(string $fournissCode, array $listDocAtt) :array
    {
        return array_map([$this, 'processDocAtt'], $listDocAtt);
    }

    /**
     * @param array<mixed> $canvaDoc
     * @return array<mixed>
     */
    private function processDocAtt(array $canvaDoc) :array
    {
        if ($canvaDoc['CodePartenaire'] != $this->getCode()) {
            return $canvaDoc;
        }
        $url = str_getcsv($canvaDoc['NOM_FIC'])[2] ?? null; // Get doc url
        return [
            'disableRelance' => true,
            'urlDownload' =>
                ! empty($url)
                    ? $this->getRouter()
                    ->generateUri('API.OnceForAll.docDownload', [
                        'fournissId' => $canvaDoc['COD_ENR'],
                        'ordreId' => $canvaDoc['NO_ORDRE'],
                    ]) : null,
            'menuContextuel' => [],
        ] + $canvaDoc;
    }

    public function getDocAttTemplate() :?string
    {
        return '@Fournisseur/DocFournisseur/_rowDocBase.twig';
    }

    public function downloadDoc(string $fournissId, int $docNum) :Response
    {
        $docAtt = (new Docatt($this->getPdo()))->find([
            'NO_DA' => 'FOURNISS',
            'COD_ENR' => $fournissId,
            'NO_ORDRE' => $docNum,
        ]);

        $trad = $this->getTrad();

        $url = str_getcsv($docAtt['NOM_FIC'])[2] ?? null; // Get doc url
        if (empty($url)) {
            return new Response(404, [], $trad->t('Ce document n\'est pas disponible'));
        }

        try {
            $resp = $this->getWs()->getDocumentFile($url);
        } catch (WsInvalidResponse $e) {
            $resp = $e->getResponse();
            if ($resp != null && $resp->getStatusCode() != 404) {
                LogSysteme::prefixErrorLog('Invalid url "'.$url.'" : '. $resp->getStatusCode());
            }
            return new Response(404, [], $trad->t('Ce document n\'est pas disponible'));
        }

        return new Response(
            $resp->getStatusCode(),
            $resp->getHeaders(),
            $resp->getBody()
        );
    }

//_____________________________________________________________________________
// Fourniss menucontextuel

    /**
     * @param array<mixed> $fourniss
     * @return array{libelle: string, lienJs: string}[]
     */
    public function getMenuContextuel(array $fourniss) :array
    {
        $trad = $this->getTrad();
        if ($fourniss['hasPartLink']) {
            return [];
        }
        return [
            [
                'libelle' => $trad->t('Inviter sur OnceForAll'),
                'lienJs' => 'ajaxPopup('
                    . json_encode($this->getRouter()->generateUri(
                        'API.OnceForAll.popupFormInvite',
                        ['fournissId' => $fourniss['CODE']]
                    )) . ','
                    . json_encode(Trad::strBuild(
                        $trad->t('Inviter @1@ (@2@) sur @3@'),
                        $fourniss['RS'],
                        $fourniss['CODE'],
                        'OnceForAll'
                    ))
                    . ')'
            ]
        ];
    }

//_____________________________________________________________________________
// Process

    /**
     * @param array<mixed> $subcontractor
     * _param SubcontractorInvite $subcontractor
     */
    public function inviteSubcontractor(array $subcontractor) :void
    {
        $this->getWs()->inviteSubcontractor($this->orgaId, $subcontractor);
    }
    
//_____________________________________________________________________________
// Sub-routine
}
