<?php
namespace API\Partenaire\Service;

use API\Agena\AgenaPartenaire;
use API\Altares\IntuizPartenaire;
use API\ChorusPro\ChorusPartenaire;
use API\DirectPlus\DirectPlusPartenaire;
use API\Edicom\EdicomPartenaire;
use API\FactDematDefault\FactDematDefaultPartenaire;
use API\GovZaCsd\GovZaCsdPartenaire;
use API\InseeSiret\InseeSiretPartenaire;
use API\OnceForAll\OnceForAllPartenaire;
use API\Partenaire\IFactDematReceiver;
use API\Partenaire\IPartenaire;
use API\Partenaire\Repository\PartenaireRepo;
use API\Provigis\ProvigisService;
use API\Sage\SagePartenaire;
use Entity\Model\Alias\ParamFourniss;
use Entity\Model\Dilispec;
use Entity\Model\FourPartenaireData;
use Helpers\Log\LogSysteme;
use Helpers\Parametres\Constante;
use Psr\Container\ContainerInterface;

class PartenaireService
{

    protected \PDO $pdo;
    protected ContainerInterface $container;

    /** @var IPartenaire[]|null */
    private ?array $mapPartenaire;

    private PartenaireRepo $repo;

    public function __construct(ContainerInterface $container)
    {
        $this->pdo = $container->get(\PDO::class);
        $this->container = $container;
        $this->repo = new PartenaireRepo($this->pdo);

        $this->mapPartenaire = null;
    }

//_____________________________________________________________________________
// Accessor

    /**
     * @return array<IPartenaire>
     */
    public function listActifPartenaire() :array
    {
        return array_values($this->getAll());
    }

    /**
     * @template T
     * @param class-string<T> $interface
     * @return array<IPartenaire&T>
     */
    public function listWithInterface(string $interface) :array
    {
        return array_filter(
            $this->listActifPartenaire(),
            function ($partn) use ($interface) {
                return $partn instanceof $interface;
            }
        );
    }

    /**
     * @return array<IPartenaire>
     */
    public function getListByFourniss(string $fournissId) :array
    {
        $listFourData = (new FourPartenaireData($this->pdo))
            ->findAll(['CODE_FOU' => $fournissId]);
        return array_filter(array_map(function ($item) {
            return $this->getByCode($item['CODEPARTENAIRE']);
        }, $listFourData));
    }

    /**
     * @return IPartenaire[]
     */
    public function getAll() :array
    {
        if ($this->mapPartenaire === null) {
            $this->mapPartenaire = [];
            foreach ($this->repo->getListActivated() as $codeDili) {
                if (!in_array($codeDili, ['CSD', 'ALTARES', 'ALTADIR', 'WS_SIRET', 'OFA', 'PROVIGIS'])) { // TODO : remove once every api uses interfaces
                    continue;
                }
                try {
                    $this->addPartenaire($this->createPartenaire($codeDili));
                } catch (\Throwable $e) {
                    LogSysteme::prefixErrorLog((string)$e);
                }
            }
        }
        return $this->mapPartenaire;
    }

    public function getByCode(string $s) :?IPartenaire
    {
        return $this->getAll()[ $s ] ?? null;
    }

//_____________________________________________________________________________
// Entity search import

    /**
     * @return \stdClass{source:string,mapping:array<string,string|array<string,string>>}
     */
    public function getCurrentMapping() :object
    {
        $data = array_column(
            (new Dilispec($this->pdo))->findAll(['TYPEDICO' => 'ExtEntSrc']),
            'LIB01DICO',
            'CODEDICO'
        );
        
        $mapping = [];
        foreach ($data as $pathFourniss => $pathOrga) {
            // Exclude "non-mapping" field
            if (in_array($pathFourniss, ['current_src'])) {
                continue;
            }
            list($entity, $field) = explode('|', (string) $pathFourniss, 2);
            

            
            // Case : field taxe by country
            // Process key like ["FOURNISS|taxe|FR.TAXE2" => 'value']
            // into ["FOURNISS"]["taxe"]["FR"]["TAXE2" => 'value']
            if (str_starts_with($field, 'taxe|')) {
                list(, $countryTaxe) = explode('|', $field, 2);
                list($country, $fieldTaxe) = explode('.', $countryTaxe, 2);
                $mapping[ $entity ][ 'taxe' ][ $country ][ $fieldTaxe ] = $pathOrga;
                continue;
            }
            $mapping[ $entity ][ $field ] = $pathOrga;

            // list($countryTaxeKey, $taxeKey) = explode('.', (string)$key);
            // $data[ $countryTaxeKey ][ $taxeKey ] = $value;
            // unset($data[ $key ]);
        }

        // TODO : parse FOURNISS|taxe.TOTO
        return (object) [
            'source' => $data['current_src'] ?? '',
            'mapping' => $mapping,
        ];
    }

    /**
     * @return object{source:string,mapping:array<string,string>}
     * @return \stdClass
     */
    public function getMapping(string $entity, string $paysCode) :object
    {
        $config = $this->getCurrentMapping();
        $default = (new ParamFourniss($this->pdo))->getPaysInstall();

        // Resolve taxe for given country
        $mapping = $config->mapping[ $entity ];
        if (isset($mapping['taxe'])) {
            $mapping = array_merge(
                $mapping,
                $mapping['taxe'][ $paysCode ]
                ?? $mapping['taxe'][ $default ]
            );
            
            unset($mapping['taxe']);
        }
        
        $config->mapping = $mapping;
        return $config;
    }

    /**
     * @param array<string,mixed> $mapping
     */
    public function setExtEntSrc(string $partnCode, array $mapping) :void
    {
        $this->pdo->beginTransaction();
        $dili = new Dilispec($this->pdo);
        $dili->setValue('ExtEntSrc', 'current_src', $partnCode);
        foreach ($mapping as $key => $value) {
            // Case : Group FOURNISS|taxe|{country}
            if (is_array($value)) {
                foreach ($value as $taxeKey => $v) {
                    $dili->setValue('ExtEntSrc', 'FOURNISS|' . $key.'.'.$taxeKey, $v);
                }
                continue;
            }
            $dili->setValue('ExtEntSrc', 'FOURNISS|' . $key, $value);
        }
        // TODO : delete empty value
        $this->pdo->commit();
    }

//_____________________________________________________________________________
// partenaire Fact Demat

    /**
     * @param array<mixed> $pdpConfig
     */
    public function getFactDematPartn(array $pdpConfig) :IFactDematReceiver
    {
        return match ($pdpConfig['PARTN']) {
            'AGN3' => AgenaPartenaire::getFromConfig($this->container, $pdpConfig),
            'EDIC' => EdicomPartenaire::getFromConfig($this->container, $pdpConfig),
            'CHOR' => ChorusPartenaire::getFromConfig($this->container, $pdpConfig),
            'SAGE' => SagePartenaire::getFromConfig($this->container, $pdpConfig),
            default => FactDematDefaultPartenaire::getFromConfig($this->container, $pdpConfig),
        };
    }

//_____________________________________________________________________________
// Sub-routine

    private function addPartenaire(IPartenaire $partner) :void
    {
        $this->mapPartenaire[ $partner->getCode() ] = $partner;
    }

    private function createPartenaire(string $codeDilipec) :IPartenaire
    {
        switch ($codeDilipec) {
            // TODO EAT, HIVEO, etc...
            case 'ALTARES':
                return new IntuizPartenaire($this->container);
            case 'ALTADIR':
                return new DirectPlusPartenaire($this->container);
            case 'WS_SIRET':
                return new InseeSiretPartenaire($this->container);
            case 'OFA':
                return new OnceForAllPartenaire($this->container);
            case 'PROVIGIS':
                return new ProvigisService($this->container);
            case 'CSD':
                return new GovZaCsdPartenaire($this->container);
        }
        throw new \Exception('Invalid code "'.$codeDilipec.'"');
    }
}
