<?php

namespace Modules\Consultation\Repository;

use Helpers\ChampsSupplementaire\ChampSupp;
use Helpers\Outil\ArrayHelper;
use Helpers\Outil\Sql;
use Helpers\QueryBuilder\AbsRepository;

class ExportComparatifRepo extends AbsRepository
{

    
    
    
    /**
     * @return null|array<mixed>
     */
    public function getInfoEntet(string $aoId)
    {
        $entet = $this->fetchAssocOne(
            "SELECT 
                AO_ENTET.*,
                MemoTitre.CONTMEMO as Titre,
                MemoComment.CONTMEMO as Commentaire,
                CENTRE.CODE_CTR as CentreEmet_CODE_CTR,
                CENTRE.RAIS_SOC as CentreEmet_RAIS_SOC,
                Acheteur.CODE as Acheteur_CODE,
                Acheteur.NOM as Acheteur_NOM,
                (
                    SELECT count(*) FROM AO_FOUR with(nolock)
                    WHERE AO_FOUR.NO_AO = AO_ENTET.NO_AO
                ) as CountFour,
                (
                    SELECT count(distinct TYPE_FG+CODE_FOU)
                    FROM AO_REP with(nolock)
                    WHERE NO_AO = AO_ENTET.NO_AO
                ) as CountRep
            FROM AO_ENTET with(nolock)
            LEFT JOIN CENTRE with(nolock) ON CENTRE.CODE_CTR = AO_ENTET.EMETTEUR
            LEFT JOIN HASTUTIL as Acheteur with(nolock) ON Acheteur.CODE = AO_ENTET.ACH
            LEFT JOIN MEMOS as MemoTitre with(nolock) ON MemoTitre.NODEMEMO = AO_ENTET.NM_TITRE
            LEFT JOIN MEMOS as MemoComment with(nolock) ON MemoComment.NODEMEMO = AO_ENTET.NM_COMENT

            WHERE AO_ENTET.NO_AO = :aoId",
            ['aoId' => $aoId]
        );
        if (empty($entet)) {
            return null;
        }

        $entet['ListDemandeur'] = $this->fetchAssocAll(
            "SELECT Demandeur.*
            FROM AO_LIGNE with(nolock)
            JOIN DA_ENTET with(nolock) ON DA_ENTET.NO_DA = AO_LIGNE.NO_DA
            JOIN HASTUTIL as Demandeur with(nolock)
                ON Demandeur.CODE = DA_ENTET.NO_DEM
            WHERE AO_LIGNE.NO_AO = :aoId",
            ['aoId' => $aoId]
        );

        $champsupRepo = new ChampSupp($this->pdo);

        $entet['MapChampSupp'] = array_column(
            $champsupRepo->getInfoValueCs('AO_ENTET', ChampSupp::autoCompletCodeEnr($aoId)),
            null,
            'NUM_FIX'
        );
        $entet['LigneMapChampSupp'] = array_column(
            $champsupRepo->getInfoCS('AO_LIGNE'),
            null,
            'NUM_FIX'
        );


        $listLigne = $this->getInfoLigne($aoId);
        $entet['Total.Budget'] = array_sum(array_column($listLigne, 'BUDGET'));
        $entet['Total.MontantRef'] = array_sum(array_column($listLigne, 'MontantRef'));
        
        
        //___________________
        // List rep

        $entet['ListFourRep'] = $this->listRep($aoId);

        //___________________
        // Totaux

        // Get RepLigne from list of AoLigne (in order to get RepLigne.EcartBest)
        $mapListRepLigne = ArrayHelper::indexBy(
            array_reduce($listLigne, function ($carry, $aoLigne) {
                return array_merge($carry, array_values($aoLigne['MapLignRep']));
            }, []),
            'RepId'
        );
        
        foreach ($entet['ListFourRep'] as &$rep) {
            $listRepLigne = $mapListRepLigne[ $rep['RepId'] ] ?? [];


        
            $rep['Total.Montant'] = array_sum(array_column($listRepLigne, 'Montant'));
            $rep['Total.EcartBest'] = array_sum(array_column($listRepLigne, 'EcartBest'));
        }
        unset($rep);

        $entet['Total.BestRep'] = $this->getBest($entet['ListFourRep'], 'Total.Montant');
        
        //___________________


        return $entet;
    }

    /**
     * @return array<array<mixed>> List of AoDaRep (Rep entet)
     */
    private function listRep(string $aoId) :array
    {
        $param = [];
        return $this->fetchAssocAll(
            "SELECT 
                AO_DAREP.*,
                MemoComment.CONTMEMO as Commentaire, 
                COALESCE(FOURNISS.RS, GRPFOU.RS) as OriginLabel,
                CONCAT_WS(
                    '|',
                    AO_DAREP.CODE_FOU,
                    AO_DAREP.TYPE_FG,
                    AO_DAREP.DTE_REP,
                    AO_DAREP.C_V
                ) as RepId
            FROM AO_DAREP with(nolock)
            JOIN AO_FOUR with(nolock)
                ON AO_FOUR.NO_AO = AO_DAREP.NO_AO
                AND AO_FOUR.CODE_FOU = AO_DAREP.CODE_FOU
                AND AO_FOUR.TYPE_FG = AO_DAREP.TYPE_FG
            JOIN (
                ".$this->getFilterAoDaRepSql($param, $aoId)."
            ) as FilterAoDaRep 
                ON FilterAoDaRep.NO_AO = AO_DAREP.NO_AO
                AND FilterAoDaRep.CODE_FOU = AO_DAREP.CODE_FOU
                AND FilterAoDaRep.TYPE_FG = AO_DAREP.TYPE_FG
                AND FilterAoDaRep.DTE_REP = AO_DAREP.DTE_REP
                AND FilterAoDaRep.C_V = AO_DAREP.C_V
            LEFT JOIN FOURNISS with(nolock) 
                ON FOURNISS.CODE = AO_DAREP.CODE_FOU
                AND AO_DAREP.TYPE_FG = 'F'
            LEFT JOIN GRPFOU with(nolock) 
                ON GRPFOU.CODE = AO_DAREP.CODE_FOU
                AND AO_DAREP.TYPE_FG = 'G'
            LEFT JOIN MEMOS as MemoComment 
                ON MemoComment.NODEMEMO = AO_DAREP.COMMENT1",
            $param
        );
    }

    /** @var array<array<array<mixed>>> Map of list of AoRep indexed by NO_AO */
    private static array $cacheListRep = [];
    /**
     * @return array<array<mixed>> List of AoRep
     */
    public function listRepLigne(string $aoId) :array
    {
        // TODO : use memoize instead
        if (isset(self::$cacheListRep[ $aoId ])) {
            return self::$cacheListRep[ $aoId ];
        }

        $param = [];
        $list = $this->fetchAssocAll(
            "SELECT 
                CONCAT_WS(
                    '|',
                    AO_REP.CODE_FOU,
                    AO_REP.TYPE_FG,
                    AO_REP.DTE_REP,
                    AO_REP.C_V
                ) as RepId,
                AO_REP.*,
                MemoDesComp.CONTMEMO as DesCompl
            FROM AO_REP with(nolock)
            LEFT JOIN MEMOS as MemoDesComp with(nolock) ON MemoDesComp.NODEMEMO = AO_REP.DESCOMPL
            JOIN (".$this->getFilterAoDaRepSql($param, $aoId).") as FilterAoDaRep
                ON FilterAoDaRep.NO_AO = AO_REP.NO_AO
                AND FilterAoDaRep.CODE_FOU = AO_REP.CODE_FOU
                AND FilterAoDaRep.TYPE_FG = AO_REP.TYPE_FG
                AND FilterAoDaRep.DTE_REP = AO_REP.DTE_REP
                AND FilterAoDaRep.C_V = AO_REP.C_V
            WHERE AO_REP.NO_AO = FilterAoDaRep.NO_AO",
            $param
        );

        return self::$cacheListRep[$aoId] = array_map(function ($rep) {
            return $rep + [
                'Montant' => floatval($rep['QTE']) * floatval($rep['PU']),
            ];
        }, $list);
    }

    /** @var array<array<array<mixed>>> Map of list of AoLigne indexed by NO_AO */
    private static array $cacheListAoLigne = [];
    
    /**
     * @return array<array<mixed>> List of AoLigne
     */
    public function listLigne(string $aoId) :array
    {
        if (isset(self::$cacheListAoLigne[ $aoId ])) {
            return self::$cacheListAoLigne[ $aoId ];
        }

        $listLign = $this->fetchAssocAll(
            "SELECT 
                AO_LIGNE.*,
                AO_LIGNE.PRIX_REF * AO_LIGNE.QTE as MontantRef,
                MemoTitre.CONTMEMO as Titre,
                MemoDesComp.CONTMEMO as DesComp
            FROM AO_LIGNE with(nolock)
            LEFT JOIN MEMOS as MemoDesComp with(nolock) ON MemoDesComp.NODEMEMO = AO_LIGNE.NM_DCOMP
            LEFT JOIN MEMOS as MemoTitre with(nolock) ON MemoTitre.NODEMEMO = AO_LIGNE.NM_ST_AV
            WHERE AO_LIGNE.NO_AO = :aoId
            ORDER BY AO_LIGNE.NO_PO",
            ['aoId' => $aoId]
        );

        foreach ($listLign as &$ligne) {
            $ligne['BUDGET'] = floatval($ligne['BUDGET']);
            $ligne['PRIX_REF'] = floatval($ligne['PRIX_REF']);
            $ligne['MontantRef'] = floatval($ligne['MontantRef']);
        }
        unset($ligne);


        return self::$cacheListAoLigne[$aoId] = $listLign;
    }

    /**
     * @return null|array<mixed>
     */
    public function getInfoLigne(string $aoId)
    {
        $listLign = $this->listLigne($aoId);

        $param = [];
        $mapListRep = ArrayHelper::indexBy(
            $this->listRepLigne($aoId),
            'NO_PO_AO'
        );

        foreach ($listLign as &$ligne) {
            $ligne['MapLignRep'] = array_column(
                $mapListRep[ $ligne['NO_PO'] ] ?? [],
                null,
                'RepId'
            );

            $ligne['MontantPrixRef'] = $ligne['PRIX_REF'] * $ligne['QTE'];

            // TODO : improve
            $ligne['MapChampSupp'] = array_column(
                (new ChampSupp($this->pdo))
                    ->getInfoValueCs(
                        'AO_LIGNE',
                        ChampSupp::autoCompletCodeEnr($aoId, $ligne['NO_PO'])
                    ),
                null,
                'NUM_FIX'
            );
        }
        unset($ligne);

        //  Calculate AoLigne.BestRep and RepLigne.EcartBest
        $this->calculateBestField($listLign);

        // Calcul sous-totaux
        foreach ($listLign as &$ligne) {
            $ligne['ST_APLIG'] = Sql::toBool($ligne['ST_APLIG']);
            if (!$ligne['ST_APLIG']) {
                continue;
            }
            $listSubligne = array_filter(
                $listLign,
                function ($otherligne) use ($ligne) {
                    return $otherligne['NO_PO'] <= $ligne['NO_PO'];
                }
            );
            $ligne['SousTotal.Budget'] = array_sum(array_column($listSubligne, 'BUDGET'));
            $ligne['SousTotal.MontantRef'] = array_sum(array_column($listSubligne, 'MontantRef'));

            foreach ($ligne['MapLignRep'] as $repId => &$ligneRep) {
                // Get list of previous RepLigne for this Rep
                $listSubRepLigne = array_reduce(
                    $listSubligne,
                    function ($carry, $ligne) use ($repId) {
                        return array_merge(
                            $carry,
                            [$ligne['MapLignRep'][ $repId ] ?? null]
                        );
                    },
                    []
                );
                $ligneRep['SousTotalMontant'] = array_sum(array_column($listSubRepLigne, 'Montant'));
                $ligneRep['SousTotalEcartBest'] = array_sum(array_column($listSubRepLigne, 'EcartBest'));
            }
            unset($ligneRep);

            $ligne['SousTotal.BestRep'] = $this->getBest($ligne['MapLignRep'], 'SousTotalMontant');
        }
        unset($ligne);

        //___________________
        // Rep Sous Ligne

        /** @var array<array<mixed>> */
        $mapListRepSousLigne = ArrayHelper::indexBy(
            $this->fetchAssocAll(
                "SELECT 
                    CONCAT_WS(
                        '|',
                        AO_REPSL.CODE_FOU,
                        AO_REPSL.TYPE_FG,
                        AO_REPSL.DTE_REP,
                        AO_REPSL.C_V
                    ) as RepId,
                    CONCAT_WS(
                        '|',
                        AO_REPSL.CODE_FOU,
                        AO_REPSL.TYPE_FG,
                        AO_REPSL.DTE_REP,
                        AO_REPSL.C_V,
                        AO_REPSL.NO_PO_AO
                    ) as LigneRepId,
                    AO_REPSL.*
                FROM AO_REPSL with(nolock)
                JOIN (".$this->getFilterAoDaRepSql($param, $aoId).") as FilterAoDaRep
                    ON FilterAoDaRep.NO_AO = AO_REPSL.NO_AO
                    AND FilterAoDaRep.CODE_FOU = AO_REPSL.CODE_FOU
                    AND FilterAoDaRep.TYPE_FG = AO_REPSL.TYPE_FG
                    AND FilterAoDaRep.DTE_REP = AO_REPSL.DTE_REP
                    AND FilterAoDaRep.C_V = AO_REPSL.C_V",
                $param
            ),
            'LigneRepId'
        );

        foreach ($listLign as &$ligne) {
            foreach ($ligne['MapLignRep'] as &$rep) {
                $rep['MapSousLigne'] = array_column(
                    array_map(function (array $sousLigne) use ($rep) {
                        return [
                            'Montant' =>
                                (float) $sousLigne['QTE_PROP']
                                * (float) $sousLigne['PU']
                                * (float) $rep['QTE'],
                        ] + $sousLigne;
                    }, $mapListRepSousLigne[ $rep['RepId'] . '|'. $ligne['NO_PO'] ] ?? []),
                    null,
                    'NO_SSLIG'
                );
            }
            unset($rep);
        }
        unset($ligne);

        return $listLign;
    }

    /**
     * @param array<mixed> $param
     */
    private function getFilterAoDaRepSql(array &$param, string $aoId) :string
    {
        $param['aoId'] = $aoId;
        return "SELECT 
            AO_DAREP.NO_AO,
            AO_DAREP.CODE_FOU,
            AO_DAREP.TYPE_FG,
            AO_DAREP.C_V,
            MAX(AO_DAREP.DTE_REP) as DTE_REP
        FROM AO_DAREP with(nolock)
        WHERE AO_DAREP.NO_AO = :aoId
        GROUP BY 
            AO_DAREP.NO_AO,
            AO_DAREP.CODE_FOU,
            AO_DAREP.TYPE_FG,
            AO_DAREP.C_V";
    }

    /**
     * Calculate AoLigne.BestRep and RepLigne.EcartBest
     * @param array<array<mixed>> $listAoLigne Liste of AoLigne with AoLigne.MapLignRep
     */
    private function calculateBestField(array &$listAoLigne) :void
    {
        foreach ($listAoLigne as &$aoLigne) {
            $aoLigne['BestRep'] = $this->getBest($aoLigne['MapLignRep'], 'Montant');

            foreach ($aoLigne['MapLignRep'] as &$repLigne) {
                $repLigne['EcartBest'] =
                    $repLigne['Montant']
                    - ($aoLigne['BestRep']['Montant'] ?? 0);
            }
            unset($repLigne);
        }
        unset($aoLigne);
    }

    /**
     * @param array<array<mixed>> $a
     * @return null|array<mixed>
     */
    private function getBest(array $a, string $field) :?array
    {
        return array_reduce(
            $a,
            function ($carry, $item) use ($field) {
                $montant = $item[ $field ];

                if ($montant <= 0) {
                    return $carry;
                }
                if ($carry === null) {
                    return $item;
                }
                return $montant < $carry[ $field ] ? $item : $carry;
            },
            null
        );
    }
}
