<?php
namespace API\Provigis;

use API\Partenaire\IFournissDocExt;
use API\Partenaire\IPartenaire;
use API\Partenaire\Repository\PartenaireRepo;
use Entity\Model\Country;
use Entity\Model\Docatt;
use Entity\Model\FourPartenaireData;
use Entity\Model\ParamDico;
use GuzzleHttp\Psr7\Response;
use Modules\Fournisseur\Repository\DocAtt as DocAttRepo;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Require phpStan 1.10.12
 * phpstan-type ProvigisDoc object{name:string,status:string,validity:string}
 */
class ProvigisService implements IPartenaire, IFournissDocExt
{

    private \PDO $pdo;
    private ProvigisWsService $_webservice;

    public function __construct(ContainerInterface $container)
    {
        $this->_webservice = ProvigisWsService::createFromConfig($container);
        $this->pdo = $container->get('PDO.oxasql');
    }

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

    public function getLabel() :string
    {
        return 'Provigis';
    }

    /**
     * @return array{listFournissId:array<string>,listUnmatched:array<string>} list of fourniss code
     */
    public function updateProvigisFourniss() :?array
    {
        $this->pdo->beginTransaction();

        // Reset all local data
        $qbParamDico = new ParamDico($this->pdo);
        $qbParamDico->delete(['TYPEDICO' => 'PROVIGIS_KEY']);
        $qbFourPartn = new FourPartenaireData($this->pdo);
        $qbFourPartn->delete(['CODEPARTENAIRE' => 'PROVIGIS']);

        $pageIndex = 0;
        $listFournissId = [];
        $listUnmatchedId = [];
        do {
            // Get [siret => prov supplier id]
            $listSupplierProvigis = (array) $this->_webservice->getSupplierList($pageIndex++)->suppliers;
 
            foreach ($listSupplierProvigis as $supplier) {
                // Get related Fourniss
                $listFourniss =
                    (new PartenaireRepo($this->pdo))
                    ->searchFourniss(
                        array_filter([
                            strtoupper($supplier->siret),
                            strtoupper($supplier->intercommunityVAT), // TVA IC
                        ]),
                        ['NO_SIRET','NO_TVA_IC']
                    );
                
                $bMatched = false;
                foreach ($listFourniss as $fourniss) {
                    $founissId = $fourniss['CODE'];

                    // Skip if doesn't match
                    if (! (
                            (
                                $fourniss['CODE_PAYS'] == 'FR'
                                && $this->lowMatch($fourniss['NO_SIRET'], $supplier->siret ?? '')
                            )
                            || $this->lowMatch($fourniss['NO_TVA_IC'], $supplier->intercommunityVAT ?? '')
                        )
                    ) {
                        continue;
                    }
                    
                    $this->addFournissProvigisInfo(
                        $founissId,
                        $supplier->id
                    );
                    $listFournissId[] = $founissId;
                    $bMatched = true;
                }

                if (! $bMatched) {
                    $listUnmatchedId[] = $supplier->id;
                }
            }
        } while (count($listSupplierProvigis) >= 50 /* getSupplierList page size */);
        
        $this->pdo->commit();

        return [
            'listFournissId' => $listFournissId,
            'listUnmatched' => $listUnmatchedId,
        ];
    }

    private function lowMatch(string $a, string $b) :bool
    {
        return strtolower($a) == strtolower($b);
    }

    /**
     * return object{name:string,status:string,validity:string}[]|null
     * @TODO : rename updateFourDoc
     * @return object[]|null
     */
    public function getFourDocAtt(string $fourCode) :?array
    {
        // // TEST / DEBUG
        // return [
        //     (object) [
        //         'name' => 'Toto1',
        //         'status' => 'up_to_date',
        //     ],
        //     (object) [
        //         'name' => 'Toto2',
        //         'status' => 'to_verify',
        //     ],
        //     (object) [
        //         'name' => 'Toto3',
        //         'status' => 'to_update',
        //     ],
        //     (object) [
        //         'name' => 'Toto4',
        //         'status' => 'missing',
        //     ]
        // ];

        
        $qbParamDico = new ParamDico($this->pdo);
        $paramDico = $qbParamDico->getProvigisLink($fourCode);
        if ($paramDico == null) {
            return null;
        }

        $provigisId = $paramDico['ID4'];
        $listDoc = $this->getSupplierDocLegalListFromWs($provigisId);
        
        $docAtt = new Docatt($this->pdo);
        
        // Case : Supplier no longer exist in Provigis
        if ($listDoc == null) {
            $this->removeFournissProvigisInfo($fourCode);
            return null;
        }

        // Update our DB
        $this->pdo->beginTransaction();
        $concordances = (new \Modules\Fournisseur\Repository\DocAtt($this->pdo))
            ->getCanvaForPartenaire($fourCode, 'PROVIGIS');
        foreach ($concordances as $concordanceExp) {
            foreach ($listDoc as $doc) {
                // Filter on loose compare
                /** @phpstan-ignore-next-line */
                if (strpos(strtolower($doc->name), strtolower($concordanceExp['Corresp'])) === false) {
                    continue;
                }
                
                /** @phpstan-ignore-next-line */
                $tmpId = 'provigis:'.$doc->name.':'.($doc->id ?? '');
                $docAttId = [
                    'NO_DA' => 'FOURNISS',
                    'COD_ENR' => $fourCode,
                    //!\ NOM FIC is not actually part of the primary key
                    'NOM_FIC' => $tmpId,
                ];
                /** @phpstan-ignore-next-line */
                $doc->tmpId = $tmpId;
                $docEntity = $docAtt->findAll($docAttId);

                // Missing doc must not be in DocAtt
                /** @phpstan-ignore-next-line */
                if ($doc->status == 'missing') {
                    if (!empty($docEntity)) {
                        $docAtt->delete($docAttId);
                    }
                    continue;
                }

                /** @phpstan-ignore-next-line */
                $dateCreate = new \DateTime($doc->date ?? date('Y-m-d'));
                /** @phpstan-ignore-next-line */
                $dateValidity = empty($doc->validity) ?
                    '00000000' :
                    (new \DateTime($doc->validity))->format('Ymd');

                // Persist DocAtt
                if (empty($docEntity)) {
                    $docAtt->insertForFourniss(
                        $concordanceExp,
                        $docAttId + [
                            'NO_DA' => 'FOURNISS',
                            'COD_ENR' => $fourCode,
                            'TITRE' => $concordanceExp['TITRE_DOC'],
                            'NOM_FIC' => $docAttId['NOM_FIC'],
                            'DT_PARUT' => $dateCreate->format('Ymd'),
                            'DT_VALID' => $dateValidity,
                            'GERE_PAR_PARTENAIRE' => true,
                            'COPIE' => false,
                        ]
                    );
                    continue;
                }
                $docAtt->update(
                    [
                        'DT_PARUT' => $dateCreate->format('Ymd'),
                        'DT_VALID' => $dateValidity,
                        'GERE_PAR_PARTENAIRE' => true,
                    ],
                    $docAttId
                );
            }
        }
        $this->pdo->commit();

        return $listDoc;
    }

    /**
     * Export an Oxalys Fourniss to Provigis
     * @param array<mixed> $fourniss Fourniss entity
     * @param array<mixed> $contfour ContFour entity
     */
    public function importFourniss(array $fourniss, array $contfour) :void
    {
        if ($fourniss['CODE_PAYS'] == 'FR') {
            $provigisId = $this->_webservice->createSupplier(
                $fourniss['NO_SIRET'],
                'OXALYS_'.$fourniss['CODE'],
                $fourniss['RS'],
                $contfour['NOM'],
                $contfour['EMAIL'],
                $contfour['TELPORT'],
                $contfour['TEL'],
                $contfour['FONCTION'],
            );
        } else {
            $provigisId = $this->_webservice->createSupplierForeign(
                Country::matchCodeToIso2($fourniss['CODE_PAYS'])['iso2'],
                $fourniss['NO_TVA_IC'],
                'OXALYS_'.$fourniss['CODE'],
                $fourniss['RS'],
                $contfour['NOM'],
                $contfour['EMAIL'],
                $contfour['TELPORT'],
                $contfour['TEL'],
                $contfour['FONCTION'],
            );
        }
        
        $this->addFournissProvigisInfo(
            $fourniss['CODE'],
            $provigisId
        );

        $this->getFourDocAtt($fourniss['CODE']);
    }

    public function removeFourniss(string $fournissCode) :bool
    {
        $provigisId =
            (new ParamDico($this->pdo))
                ->getProvigisLink($fournissCode)['ID4'] ?? null;
        if ($provigisId === null) {
            return false;
        }

        $this->_webservice->removeSupplier($provigisId);

        $this->removeFournissProvigisInfo($fournissCode);
        return true;
    }
    public function getDocument(string $id) :ResponseInterface
    {
        return $this->_webservice->downloadDoc($id);
    }


//_____________________________________________________________________________
// Fourniss doc

    /**
     * @param array<mixed> $fourniss Entity Fourniss
     * @return void
     */
    public function preprocessDocAtt(array $fourniss) :void
    {
        // Do nothing
    }

    /**
     * @param string $fournissCode
     * @param array<mixed> $listDocAtt
     * @return array<mixed>
     */
    public function processListDocAtt(string $fournissCode, array $listDocAtt) :array
    {
        $listDoc = $this->getFourDocAtt($fournissCode);
        if ($listDoc === null) {
            return $listDocAtt;
        }
        /** @var array<\stdClass> */
        $mapProvigisDoc = array_column($listDoc, null, 'tmpId');

        foreach ($listDocAtt as &$docAtt) {
            // Here NOM_FIC is a provigis identifier "provigis:{name}[:{id}]"
            $docAtt['ProvigisStatus'] = $mapProvigisDoc[ $docAtt['NOM_FIC'] ]->status;
        }
        return $listDocAtt;
    }

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

    
    public function downloadDoc(string $fournissId, int $docNum) :Response
    {
        return new Response(
            400,
            [],
            (string) json_encode([
                'Error' => 'Not implemented'
            ])
        );
    }

//_____________________________________________________________________________
// Sub-routine

    /**
     * return array<ProvigisDoc>|null
     * @return array<object>|null
     */
    private function getSupplierDocLegalListFromWs(string $provigisId) :?array
    {
        try {
            $supplierInfo = $this->_webservice->getSupplierInfo($provigisId);
        } catch (\Exception $e) {
            return [];
        }
        if ($supplierInfo == null) {
            return null;
        }
        
        $list = array_merge(
            /** @phpstan-ignore-next-line */
            $supplierInfo->suppliers->{$provigisId}->documents->legal ?? [],
            /** @phpstan-ignore-next-line */
            $supplierInfo->suppliers->{$provigisId}->documents->specifics ?? [],
            array_reduce(
                /** @phpstan-ignore-next-line */
                $supplierInfo->suppliers->{$provigisId}->documents->sectors ?? [],
                function ($carry, $sector) {
                    return array_merge($carry, $sector->docs);
                },
                []
            ),
            array_reduce(
                /** @phpstan-ignore-next-line */
                $supplierInfo->suppliers->{$provigisId}->documents->contracts ?? [],
                function ($carry, $contract) {
                    return array_merge($carry, $contract->documents);
                },
                []
            ),
        );

        // Ignore malformed doc
        return array_filter(
            $list,
            function ($item) {
                return isset($item->name)
                    && isset($item->status)
                ;
            }
        );
    }

    private function addFournissProvigisInfo(string $fournissCode, string $provigisId) :void
    {
        $isIntransac = $this->pdo->inTransaction();
        if (! $isIntransac) {
            $this->pdo->beginTransaction();
        }
        
        (new ParamDico($this->pdo))->addProvigisLink(
            $fournissCode,
            $provigisId
        );

        (new FourPartenaireData($this->pdo))->insertEntity(
            $fournissCode,
            'PROVIGIS',
            true,
            false
        );

        if (! $isIntransac) {
            $this->pdo->commit();
        }
    }

    private function removeFournissProvigisInfo(string $fournissCode) :void
    {
        $this->pdo->beginTransaction();
        (new ParamDico($this->pdo))->removeProvigisLink($fournissCode);
        (new FourPartenaireData($this->pdo))->deleteEntity(
            $fournissCode,
            'PROVIGIS'
        );
        (new DocAttRepo($this->pdo))->deleteProvigisFile($fournissCode);

        $this->pdo->commit();
    }
}
