<?php
namespace API\GovZaCsd;

use API\GovZaCsd\GovZaCsdRepo;
use API\GovZaCsd\GovZaCsdWs;
use API\Partenaire\IBatch;
use API\Partenaire\IEntityApprovalCompare;
use API\Partenaire\IEntitySearch;
use API\Partenaire\IFournissCustomTab;
use API\Partenaire\IFournissLink;
use API\Partenaire\IPartenaire;
use API\Partenaire\Service\PartenaireService;
use Entity\Model\Country;
use Entity\Model\Dilispec;
use Entity\Model\Metadata\EntityMetaQb;
use Entity\Model\Fourniss;
use Entity\Model\Metadata\BatchGovZaCsdData;
use Entity\Model\Metadata\PartnFournissDemModif;
use Entity\Model\WftDemEnt;
use Helpers\ChampsSupplementaire\ChampSupp;
use Helpers\Log\LogSysteme;
use Helpers\Outil\ArrayHelper;
use Helpers\Outil\Date;
use Helpers\Outil\ObjectHelper;
use Helpers\Trad\ITrad;
use Modules\GlobalApplication\Repository\Workflow;
use Psr\Container\ContainerInterface;
use Skeleton\Router;

/**
 * Central Supplier Database for South African governement
 */
class GovZaCsdPartenaire implements IPartenaire, IEntitySearch, IBatch, IFournissCustomTab, IEntityApprovalCompare
{
    private ContainerInterface $container;
    private GovZaCsdWs $ws;

    
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

//_____________________________________________________________________________
// Accessor

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

    public function getLabel(bool $short = false) :string
    {
        return $short
            ? 'CSD'
            : 'South Africa - Central Supplier Database';
    }

    public function getWs() :GovZaCsdWs
    {
        return $this->ws ?? ($this->ws = GovZaCsdWs::createFromConfig($this->container));
    }

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

    public function getFournissCsdField() :string
    {
        return (new Dilispec($this->getPdo()))
            ->getMap('CSD')['FOURNISS_FIELD']
            ?? 'NO_SIRET';
    }

//_____________________________________________________________________________
// IEntitySearch

    public function entitySearch(string $entityClass, array $data) :?array
    {
        if ($entityClass != Fourniss::class) {
            return null;
        }

        $data['CODE_PAYS'] = trim($data['CODE_PAYS'] ?? '');
        // if ($data['CODE_PAYS'] != 'ZA' /* South Africa */) {
        //     return null;
        // }

        $filter = array_map(
            fn($v) => trim($v),
            ArrayHelper::filterByKey($data, [
                'RS', 'VILLE', $this->getFournissCsdField(),
            ])
        );

        // Case : all filters are empty
        if (empty(array_filter($filter))) {
            return null;
        }

        try {
            $ws = $this->getWs();
            $listSearchResult = $ws->search([
                'name' => $filter['RS'] ?? '',
                'city' => $filter['VILLE'] ?? '',
                'registrationNumber' => $filter[ $this->getFournissCsdField() ] ?? '',
            ]);
        } catch (\Throwable $e) {
            LogSysteme::prefixErrorLog($e->__tostring());
            return null;
        }

        $service = $this->getService();
        $config = $service->getMapping('FOURNISS', $data['CODE_PAYS']);

        return [
            'count' => null,
            'list' =>
                array_map(function ($item) use ($config, $data) {
                    return $this->formatEntityFromSearch($item, $config)
                        + [
                            'CODE_PAYS' => $data['CODE_PAYS'],
                            '_csdId' => $item->CSDNumber,
                        ];
                }, $listSearchResult),
            'hasMore' => count($listSearchResult) > 25 /* Page SIZE */
        ];
    }

    public function needDetail(string $entityClass, array $mapping) :bool
    {
        return true;
    }

    /**
     * @param array<mixed> $data
     */
    public function getDetail(string $entityClass, array $data) :array
    {
        if ($entityClass != Fourniss::class) {
            return $data;
        }

        if (!isset($data['CODE_PAYS']) || !isset($data['_csdId'])) {
            throw new \Exception('Invalid data '.var_export($data, true));
        }

        try {
            $item = $this->getWs()->companyInfo($data['_csdId']);
        } catch (\Throwable $e) {
            LogSysteme::prefixErrorLog($e->__tostring());
            return $data;
        }
        
        // Case : not found
        if (empty($item->SupplierIdentification->SupplierNumber)) {
            return $data + [
                'err' => 'CSD_NOT_FOUND',
            ];
        }

        // Persist date last modif for batch
        $lastEditDate = BatchGovZaCsdData::getLastEditDate($item);
        if (!empty($lastEditDate)) {
            $qb = new EntityMetaQb($this->getPdo());
            $qb->persist([new BatchGovZaCsdData(
                $item->SupplierIdentification->SupplierNumber,
                $lastEditDate
            )]);
        }
        
        $config = $this->getService()->getMapping('FOURNISS', $data['CODE_PAYS']);
        if (empty($config->mapping)) {
            return $data + [
                'err' => 'CSD_MAPPING_MISSING',
            ];
        }
        return $this->formatEntity($item, $config)
            + [
                'CODE_PAYS' => $data['CODE_PAYS'],
                '_csdId' => $item->SupplierIdentification->SupplierNumber,
            ];
    }

    /**
     * @param \stdClass $supplier
     * @param \stdClass $config
     * @return array<mixed> Entity FAC_ENT plus some metadata
     */
    private function formatEntityFromSearch(object $supplier, object $config)
    {
        $fourniss = [
            'isActive' => $supplier->isActive ?? $supplier->IsActive ?? false,
        ];
        foreach ($config->mapping as $field => $path) {
            if (empty($path)) {
                continue;
            }
            
            $value = $this->resolveMappingSearch($supplier, $path);

            $fourniss[ $field ] = $value;
        }
        return $fourniss;
    }

    /**
     * Format an Organization into a Fourniss
     * @param \stdClass $supplier CSD Supplier as returned by the API
     * @param \stdClass $config
     * @return array<mixed> Fourniss
     */
    private function formatEntity(object $supplier, object $config)
    {
        $fourniss = [
            'isActive' => $supplier->IsActive ?? false,
        ];
        $supplier->PreferredContact = ArrayHelper::findFirst(
            $supplier->Contacts,
            fn($item) => $item->IsPreferred
        );
        $supplier->PreferredAddresse = ArrayHelper::findFirst(
            $supplier->Addresses,
            fn($item) => $item->IsPreferred
        );
        $supplier->PreferredBankAccount = ArrayHelper::findFirst(
            $supplier->BankAccounts,
            fn($item) => $item->IsPreferred
        );
        foreach ($config->mapping as $field => $path) {
            if (empty($path)) {
                continue;
            }

            //_______________________
            // Case : Special fields

            if ($path == 'PreferredAddresse.AddressLine') {
                $fourniss[ $field ] = implode(' ', array_filter(array_map('trim', [
                    $this->resolveMapping($supplier, 'PreferredAddresse.AddressLine1'),
                    $this->resolveMapping($supplier, 'PreferredAddresse.AddressLine2'),
                ])));
                continue;
            }
            if ($path == 'PreferredAddresse.Country') {
                $fourniss[ $field ] = Country::iso3ToIso2(
                    $this->resolveMapping($supplier, 'PreferredAddresse.CountryCode')
                ) ?? '';
                continue;
            }
            // TODO : use resolveReference instead
            if ($path == 'PreferredAddresse.City') {
                $ref = include(__DIR__ . '/refCity.php');
                $fourniss[ $field ] = $ref[
                    $this->resolveMapping($supplier, 'PreferredAddresse.CityCode')
                ] ?? '';
                continue;
            }
            if ($path == 'PreferredAddresse.Province') {
                $ref = include(__DIR__ . '/refProvince.php');
                $fourniss[ $field ] = $ref[
                    $this->resolveMapping($supplier, 'PreferredAddresse.ProvinceCode')
                ] ?? '';
                continue;
            }
            
            //_______________________
            
            $value = $this->resolveMapping($supplier, $path);

            //_______________________
            // Reformat field
            switch ($path) {
                case 'BBBEE.CertificateExpiryDate':
                    $value = empty($value) ? '00000000' : Date::convert($value, null, 'Ymd');
                    break;
            }

            $fourniss[ $field ] = $value;
        }

        // CS : support for CS_EXTENTION
        foreach ($fourniss as $k => $v) {
            if (! str_starts_with($k, 'cs|')) {
                continue;
            }
            $numfix = explode('|', $k)[1];
            $fourniss[
                ChampSupp::getCsExtKey('FOURNISS', $numfix)
            ] = $v;
        }
        
        return $fourniss;
    }

    /**
     * @param \stdClass $item
     * @param string $path
     * @return mixed
     */
    private function resolveMapping(object $item, string $path)
    {
        return (string) ObjectHelper::pathAccess($item, $path, ['coalesce' => '']);
    }

    private function resolveMappingSearch(object $item, string $path) :?string
    {
        // Rewrite mapping path to fit Search structure
        $path = match ($path) {
            'SupplierIdentification.LegalName' => 'LegalName',
            'SupplierIdentification.TradingName' => 'TrandingName', // Not a typo
            'SupplierIdentification.SupplierNumber' => 'CSDNumber',
            'SupplierIdentification.SACompanyNumber' => 'RegistrationNumber',
            'Tax.VATNumber' => 'VATNumber',
            'PreferredContact.TelephoneNumber' => 'PhoneNumber',
            'PreferredContact.EmailAddress' => 'EmailAddress',
            'PreferredContact.Name' => 'ContactName',
            // TODO '' => 'Province',
            // TODO => 'Suburb',
            // TODO => 'City',

            default => '',
        };
        if (empty($path)) {
            return null;
        }
        return (string) ObjectHelper::pathAccess($item, $path, ['coalesce' => '']);
    }


    /**
     * Return true if mapping require an organization from the endpoint companyInfo
     * @return array<mixed>
     */
    public function listMappingChoice(
        string $entityClass,
        string $field,
        ITrad $trad
    ): ?array {
        if ($entityClass != Fourniss::class) {
            return null;
        }

        $listValue = [
            '',
            'SupplierIdentification.SupplierNumber', // CSD number
            'SupplierIdentification.LegalName',
            'SupplierIdentification.TradingName',
            'SupplierIdentification.SACompanyNumber',
            'SupplierIdentification.CountryOfOriginCode',

            'PreferredAddresse.AddressLine',
            'PreferredAddresse.City', // TODO : CityCode
            'PreferredAddresse.PostalCode',
            'PreferredAddresse.Province', // TODO : ProvinceCode
            // 'PreferredAddresse.Country',

            'PreferredContact.WebsiteAddress',
            'PreferredContact.Name',
            'PreferredContact.Surname',
            'PreferredContact.EmailAddress',
            'PreferredContact.TelephoneNumber',
            'PreferredContact.FaxNumber',
            'PreferredContact.CellphoneNumber',
            
            'Tax.VATNumber',
            'Tax.IncomeTaxNumber',

            'PreferredBankAccount.BankName',
            'PreferredBankAccount.BranchNumber',
            'PreferredBankAccount.AccountHolder',
            'PreferredBankAccount.BankAccountTypeCode',
            'PreferredBankAccount.AccountNumber',
            
 // TODO : B-BBEE Rating
            'BBBEE.BlackOwnership',
            'BBBEE.BlackWomanOwnership',
            'BBBEE.CertificateExpiryDate',
            'BBBEE.CertificateTypeCode', // TODO : decode ?
        ];

        if ($trad != null) {
            return array_combine($listValue, [
                '-',

                $trad->t('Numéro CSD'),
                $trad->t('Raison sociale'),
                $trad->t('Nom commercial'),
                $trad->t('Numéro d’enregistrement SA'),
                $trad->t('Pays d’origine'),

                $trad->t('Adresse - Rue'),
                $trad->t('Adresse - Ville'),
                $trad->t('Adresse - Code postal'),
                $trad->t('Adresse - Province'),

                $trad->t('Contact - Site  web'),
                $trad->t('Contact - Nom'),
                $trad->t('Contact - Prénom'),
                $trad->t('Contact - Email'),
                $trad->t('Contact - Téléphone'),
                $trad->t('Contact - Fax'),
                $trad->t('Contact - Téléphone mobile'),

                $trad->t('Numéro de TVA'),
                $trad->t('Numéro d’impôt sur le revenu'),

                $trad->t('Banque - Nom'),
                $trad->t('Banque - Numéro de branche'),
                $trad->t('Compte bancaire - Titulaire'),
                $trad->t('Compte bancaire - Type'),
                $trad->t('Compte bancaire - Numéro'),

                $trad->t('BBBEE - Propriété noire'),
                $trad->t('BBBEE - Propriété des femmes noires'),
                $trad->t('BBBEE - Date d’expiration'),
                $trad->t('BBBEE - Type'),
            ]);
        }

        return $listValue;
    }

//_____________________________________________________________________________
// IFournissCustomTab

    public function getCustomTabLabel(ITrad $trad, array $fourniss) :string
    {
        return $this->getLabel();
    }

    public function getCustomTabPath(Router $router, array $fourniss) :?string
    {
        // Require CSD number (Fourniss.taxe X)
        if (empty($fourniss[ $this->getFournissCsdField() ])) {
            return null;
        }

        // Case : Workflow dem
        if (!empty($fourniss['CODE_DEMANDE'])) {
            return $router->generateUri(
                'API.GovZaCsd.supplierDemDetail',
                ['demId' => $fourniss['CODE_DEMANDE']]
            );
        }

        return $router->generateUri(
            'API.GovZaCsd.supplierDetail',
            ['fournissId' => $fourniss['CODE']]
        );
    }

//_____________________________________________________________________________
// IEntityApprovalCompare

    public function getApprovalComparePath(
        Router $router,
        array $entity
    ) :?string {
        
        if ($entity['TYPE_ENT'] != 'FOURNISS') {
            return null;
        }
        
        // Require CSD number (Fourniss.taxe X)
        $fourniss = (new Workflow($this->getPdo()))->getFournissDemande($entity['CODE_DEMANDE']);
        if (empty($fourniss[ $this->getFournissCsdField() ])) {
            return null;
        }

        return $router->generateUri(
            'API.GovZaCsd.formDemCompare',
            ['demId' => $entity['CODE_DEMANDE']]
        );
    }

//_____________________________________________________________________________
// Batch

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

        //___________________
        // Update register (monitored duns list)

        $countFournissProcessed = 0;
        foreach ((new GovZaCsdRepo($pdo))->listBatchInfoFourniss() as $fourniss) {
            try {
                $b = $this->processBatchFourniss($fourniss);
                if ($b) {
                    $countFournissProcessed++;
                }
            } catch (\Exception $e) {
                LogSysteme::prefixErrorLog((string) $e);
            }
            sleep(1); // Temporisation
        }
        
        //___________________

        $log->setLog('O', $countFournissProcessed . ' notification(s) créé');
        
        return 'O';
    }

    /**
     * @param array<mixed> $fourniss
     */
    private function processBatchFourniss(array $fourniss) :bool
    {
        $csdNum = $fourniss[ $this->getFournissCsdField() ];
        $fournissId = $fourniss['CODE'];

        /** @var \StdClass (For phpstan) */
        $supplier = $this->getWs()->companyInfo($csdNum);

        if ($supplier == null) {
            return false;
        }

        $updatedFourniss = $this->formatEntity(
            $supplier,
            $this->getService()->getMapping('FOURNISS', $fourniss['CODE_PAYS'])
        );
        // TODO : compare date last modif to check for modification

        $qb = new EntityMetaQb($this->getPdo());

        $batchData = $qb->find(GovZaCsdPartenaire::class, $csdNum, BatchGovZaCsdData::class, 0);
        // Case : This fourniss was probably not created using the API
        // TODO : handle anyway ?
        if ($batchData == null) {
            return false;
        }

        $lastEditDate = BatchGovZaCsdData::getLastEditDate($supplier);

        // Case : not updated since
        if ($batchData->getLastUpdatedDate() >= $lastEditDate) {
            return false;
        }
        
        //___________________
        // Update metadata

        $batchData->setLastUpdatedDate($lastEditDate);
        $meta =
            $qb->find(Fourniss::class, $fournissId, PartnFournissDemModif::class, 0)
            ?? (new PartnFournissDemModif(
                $fournissId,
                $updatedFourniss
            ));
        $meta->addDelta($updatedFourniss);

        $qb->persist([$batchData, $meta]);

        return true;
    }


//_____________________________________________________________________________
// Sub-routine

    /**
     * @return \stdClass
     */
    public static function resolveReference(\stdClass $supplier)
    {
        if ($supplier->SupplierIdentification->SupplierTypeCode) {
            $ref = include(__DIR__ . '/refSupplierType.php');
            $supplier->SupplierIdentification->SupplierType = $ref[ $supplier->SupplierIdentification->SupplierTypeCode ] ?? '';
        }
        if ($supplier->SupplierIdentification->SupplierSubTypeCode) {
            $ref = include(__DIR__ . '/refSupplierSubType.php');
            $supplier->SupplierIdentification->SupplierSubType = $ref[ $supplier->SupplierIdentification->SupplierSubTypeCode ] ?? '';
        }
        if (is_array($supplier->Addresses)) {
            $ref = include(__DIR__ . '/refCity.php');
            foreach ($supplier->Addresses as $addr) {
                $addr->City = $ref[ $addr->CityCode ] ?? '';
            }
            $ref = include(__DIR__ . '/refProvince.php');
            foreach ($supplier->Addresses as $addr) {
                $addr->Province = $ref[ $addr->ProvinceCode ] ?? '';
            }
        }

        return $supplier;
    }
}
