<?php
namespace API\Provigis;

use Entity\Model\Docatt;
use Entity\Model\Fourniss;
use Entity\Model\FourPartenaireData;
use Entity\Model\ParamDico;
use GuzzleHttp\Psr7\Response;
use Modules\API\APIExterne\Repository\Partenaire;
use Modules\Fournisseur\Repository\DocAtt as DocAttRepo;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\SimpleCache\CacheInterface;

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

    private \PDO $pdo;
    private ProvigisWsService $_webservice;
    private CacheInterface $_cache;

    const CACHEKEY_MD5 = 'PROVIGIS_ProviIdBySiretMD5';

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

    /**
     * @return array<string>|null list of fourniss code
     */
    public function updateProvigisFourniss() :?array
    {
        // Get [siret => prov supplier id]
        $mapProviIdBySiret = $this->getMapProviIdBySiretFromWs();
        // $mapProviIdBySiret = ['84122605300012' => '0123456789'];

        // Check if response has changes using a cached md5
        $md5New = md5((string)json_encode($mapProviIdBySiret));
        $md5Old = $this->_cache->get(self::CACHEKEY_MD5);
        
        // Case: no change -> skip process
        if ($md5Old !== null && $md5Old == $md5New) {
            return null;
        }

        $concordances = (new Partenaire($this->pdo))->concordance('EAT');

        $this->_cache->set(self::CACHEKEY_MD5, $md5New);

        // Get related Fourniss
        $listFournissProvigis = (new Fourniss($this->pdo))->findAll(
            [ 'NO_SIRET' => array_keys($mapProviIdBySiret) ],
            [ 'CODE','NO_SIRET' ]
        );
        $mapListFounissIdBysiret = [];
        foreach ($listFournissProvigis as $fourniss) {
            $mapListFounissIdBysiret[ $fourniss['NO_SIRET'] ] = $fourniss['CODE'];
        }

        $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',
        ]);

        // Add row
        $listFournissId = [];
        foreach ($mapProviIdBySiret as $siret => $provigisId) {
            $founissId = $mapListFounissIdBysiret[ $siret ] ?? null;

            // Case : Fourniss not found in our DB
            if ($founissId === null) {
                continue;
            }

            $this->addFournissProvigisInfo(
                $founissId,
                $provigisId
            );

            $listFournissId[] = $founissId;
        }
        $this->pdo->commit();

        return $listFournissId;
    }

    /**
     * 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);
        $docAttRepository = new DocAttRepo($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 Partenaire($this->pdo))->concordance('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 = new \DateTime($doc->validity);

                // Persist DocAtt
                if (empty($docEntity)) {
                    $docAttRepository->insertDocPartenaire(
                        $fourCode,
                        $concordanceExp['LIBELLE'],
                        $dateCreate->format('Ymd'),
                        $dateValidity->format('Ymd'),
                        $docAttId['NOM_FIC'],
                        true,
                        false
                    );
                    continue;
                }
                $docAtt->update(
                    [
                        'DT_PARUT' => $dateCreate->format('Ymd'),
                        'DT_VALID' => $dateValidity->format('Ymd'),
                        'GERE_PAR_PARTENAIRE' => true,
                    ],
                    $docAttId
                );
            }
        }
        $this->pdo->commit();

        return $listDoc;
    }

    /**
     * @param array<mixed> $fourniss Fourniss entity
     * @param array<mixed> $contfour ContFour entity
     */
    public function importFourniss(array $fourniss, array $contfour) :void
    {
        $provigisId = $this->_webservice->createSupplier(
            $fourniss['NO_SIRET'],
            '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);
    }

//_____________________________________________________________________________
// Sub-routine

    /**
     * @return array<string> provigis supplier id indexed by siret
     */
    public function getMapProviIdBySiretFromWs() :array
    {
        /** @phpstan-ignore-next-line */
        $listSupplierProgedis = $this->_webservice->getSupplierList()->suppliers;
        // Get map of provigis id indexed by siret
        $mapProviIdBySiret = [];
        foreach ($listSupplierProgedis as $provigisId => $supplier) {
            $mapProviIdBySiret[ $supplier->siret ] = $provigisId;
        }
        return $mapProviIdBySiret;
    }

    /**
     * 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)
                    && isset($item->validity)
                ;
            }
        );
    }

    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();
    }
}
