<?php

namespace Modules\Facture\Repository;

use Helpers\QueryBuilder\AbsRepository;
use Helpers\Outil\Sql;

/**
 * 'FAC_ENT' => ['TX_DEVISE','TYPE'],
                // 'FAC_LIG' => [
                //     'NUM_FACT','NUM_LIG',
                //     'NUM_CDE','NUM_BR','NUM_LIBR','NUM_LICDE',
                //     'QUANTITE','PU_REMISE','MONTANT',
                // ],
                // 'CA_LIGNE' => ['PU','REMISE','PU_REMISE','QTE_FACTU','QTE_COMM','SOM_FACTU'],
                // 'CA_ENTET' => ['VAL_DEVIS'],
                // 'BR_LIGNE' => ['QTE_FACT','QTE_ACCEP','MNT_REC'],
 * @phpstan-type Facture array{
 *     TYPE:string,
 *     TX_DEVISE:float
 * }
 * @phpstan-type FacLigne array{
 *     NUM_FACT:int, NUM_LIG:int, NUM_CDE:int,
 *     NUM_BR:int, NUM_LIBR:int, NUM_LICDE:int,
 *     QUANTITE:float,PU_REMISE:float,MONTANT:float
 * }
 * @phpstan-type CaLigne array{
 *     PU:float, REMISE:float, QTE_FACTU:float,QTE_COMM:float,SOM_FACTU:float
 * }
 * @phpstan-type CaEntet array{
 *     VAL_DEVIS:float
 * }
 * @phpstan-type BrLigne array{
 *     QTE_FACT:float,QTE_ACCEP:float,MNT_REC:float
 * }
 */
abstract class AbsFacture extends AbsRepository
{

    /**
     * @return array{ecartPrixUnitaire:float,ecartMontant:float,ecartQte:float}
     */
    public function getValeursEcartLigneBAP(string $num_fact) :array
    {
        $ecartQte = 0;
        $ecartPrixUnitaire = 0;
        $ecartMontant = 0;

        $listFacLigne = $this->listCalcEcart($num_fact);
        
        foreach ($listFacLigne as $ligne) {
            // On parcoure toutes les lignes pour avoir les informations sur la ligne passée en paramètre
            $ecartQte += $this->getEcartLigneMontant($ligne['Facture'], $ligne);
            $ecartPrixUnitaire += $this->getEcartLignePrixUnitaire($ligne['Facture'], $ligne);
            $ecartMontant += $this->getEcartLigneQte($ligne);
        }

        return [
            'ecartPrixUnitaire' => $ecartPrixUnitaire,
            'ecartMontant' => $ecartQte,
            'ecartQte' => $ecartQte,
        ];
    }

    /**
     * Load
     * - FacLigne.Facture
     * - FacLigne.CaLigne
     * - FacLigne.CaEntet
     * - FacLigne.BrLigne
     * - FacLigne.BrLigne.SumMontantFacture
     * return array<FacLigne&array{Facture:Facture,CaLigne:CaLigne,CaEntet:CaEntet,BrLigne:BrLigne&array{SumMontantFacture:float}}>
     * @return array<array<mixed>>
     */
    public function listCalcEcart(string $factureId) :array
    {

        $fromNivConf = '';
        $whereNivConf = '';
        
        $query = 'SELECT 
            '.$this->generateFieldList([
                'FAC_ENT' => ['TX_DEVISE','TYPE'],
                'FAC_LIG' => [
                    'NUM_FACT','NUM_LIG',
                    'NUM_CDE','NUM_BR','NUM_LIBR','NUM_LICDE',
                    'QUANTITE','PU_REMISE','MONTANT',
                ],
                'CA_LIGNE' => ['PU','REMISE','PU_REMISE','QTE_FACTU','QTE_COMM','SOM_FACTU'],
                'CA_ENTET' => ['VAL_DEVIS'],
                'BR_LIGNE' => ['QTE_FACT','QTE_ACCEP','MNT_REC'],
            ]).',
            (
                SELECT SUM(FAC_LIG.MONTANT) 
                FROM FAC_LIG as sumFac
                WHERE sumFac.NUM_BR = BR_LIGNE.NUM_BR 
                    AND sumFac.NUM_LIBR = FAC_LIG.NUM_LIBR
            ) as \'BR_LIGNE.SumMontantFacture\'
        FROM FAC_LIG with(nolock) 
        JOIN FAC_ENT with(nolock) ON FAC_LIG.NUM_FACT = FAC_ENT.NUM_FACT
        LEFT JOIN CA_ENTET with(nolock) ON CA_ENTET.NO_CDE = FAC_LIG.NUM_CDE 
        LEFT JOIN CA_LIGNE with(nolock) 
            ON CA_LIGNE.CODE_STE = FAC_LIG.CODE_STE 
            AND CA_LIGNE.NO_CDE = FAC_LIG.NUM_CDE 
            AND CA_LIGNE.NO_PO = FAC_LIG.NUM_LICDE 
        LEFT JOIN BR_LIGNE with(nolock) 
            ON BR_LIGNE.NUM_BR = FAC_LIG.NUM_BR 
            AND BR_LIGNE.NO_PO_BR = FAC_LIG.NUM_LIG 
            AND BR_LIGNE.NUM_CDE = FAC_LIG.NUM_CDE 
            AND BR_LIGNE.NO_PO_CDE = FAC_LIG.NUM_LICDE
        '
        . $fromNivConf
        . ' 
        
        WHERE FAC_LIG.NUM_FACT = :num_fact '
        . $whereNivConf;
        $listRow = $this->fetchAssocAll($query, ['num_fact' => $factureId]);

        $listFacLigne = [];
        foreach ($listRow as $row) {
            $split = $this->splitRowByTable($row);

            $split['BR_LIGNE']['SumMontantFacture'] = floatval($split['BR_LIGNE']['SumMontantFacture']);

            $listFacLigne[] = $this->formatFactureLigne($split['FAC_LIG']) + [
                'BrLigne' => $this->formatBonRecepLigne($split['BR_LIGNE']),
                'CaLigne' => $this->formatCaLigne($split['CA_LIGNE']),
                'CaEntet' => $this->formatCaEntet($split['CA_ENTET']),
                'Facture' => $this->formatFacture($split['FAC_ENT']),
            ];
        }

        return $listFacLigne;
    }

//_____________________________________________________________________________
// Sub-routine calc ecart
    
    /**
     * @param Facture $facture
     * param FacLigne&array{CaLigne:CaLigne,BrLigne:BrLigne} $ligne
     * @param array<mixed> $ligne
     */
    public function getEcartLigneMontant(array $facture, array $ligne) :float
    {
                
        // Uniquement sur les lignes liées à une ligne de commande (FAC_LIG.NUM_LICDE<>0)
        if ($ligne['NUM_LICDE'] == 0) {
            return 0;
        }
        
        // Si FAC_LIG.QUANTITE = 0, nons sommes en montant
        if ($ligne['QUANTITE'] == 0) {
            // Réception en montant
            if ($ligne['MONTANT'] <= 0) {
                return 0;
            }
            
            $caLigne = $ligne['CaLigne'];
            $ecartMontant2 = round(
                $caLigne['SOM_FACTU']
                - (
                    $caLigne['QTE_COMM']
                    * $caLigne['PU']
                    * (1 - $caLigne['REMISE'] / 100)
                ),
                2
            );
            if ($ligne['NUM_BR'] != 0 && $ligne['NUM_LIBR'] != 0) {
                $brLigne = $ligne['BrLigne'];
                $ecartMontant1 = round(
                    $brLigne['SumMontantFacture'] - $brLigne['MNT_REC'],
                    2
                );

                if ($ecartMontant1 <= 0 && $ecartMontant2 <= 0) {
                    return 0;
                }
                $baseCalcul = ($ecartMontant1 > $ecartMontant2)?
                    $ecartMontant1 : $ecartMontant2;
                return $baseCalcul * $ligne['VAL_DEVIS'] * $ligne['TX_DEVISE'];
            }
            
            if ($ecartMontant2 <= 0) {
                return 0;
            }
            return $ecartMontant2
                    * $ligne['VAL_DEVIS']
                    * $ligne['TX_DEVISE'];
        }

        $ecartDevise = $this->getEcartDevise($facture, $ligne);
        $ecartComm = $this->getEcartFactureQtComm($ligne['CaLigne']);

        $factureLignPu = $this->getFactureLignePu($facture, $ligne);
        $caLigne = $ligne['CaLigne'];
        $ecartQt2 = $caLigne['PU']
            * (1 - $caLigne['REMISE'] / 100)
            * $ligne['CaEntet']['VAL_DEVIS'] / (
                $facture['TX_DEVISE'] == 0 ? 1 : $facture['TX_DEVISE']
            );

        $ecartQt1 = $ligne['QUANTITE'] * ($factureLignPu- $ecartQt2);
        
        // Réception en quantité
        if (0 != $ligne['NUM_BR'] && 0!= $ligne['NUM_LIBR']) {
            $ecartFacture = $this->getEcartFactureQtAcc($ligne['Brligne']);

            // Ligne de cde et ligne de BR, réception en Qté :
            if ($factureLignPu == $ecartDevise
                && $ecartFacture != 0
                && $ecartComm != 0
            ) {
                return 0;
            }
            $baseCalcul = ($ecartFacture > $ecartComm)?
                $ecartFacture :
                $ecartComm;
            return $ecartQt1 + $baseCalcul  * $ecartQt2;
        }
        
        if ($ecartComm == 0
            && $ligne['PU_REMISE'] == $ecartDevise
        ) {
            return 0;
        }
        return $ecartQt1 + $ecartComm * $ecartQt2;
    }

    /**
     * param FacLigne&array{CaLigne:CaLigne} $ligne
     * @param array<mixed> $ligne
     */
    public function getEcartLigneQte(array $ligne) :float
    {
        if ($ligne['NUM_LICDE'] == 0 // Uniquement sur les lignes liées à une ligne de commande (FAC_LIG.NUM_LICDE<>0)
            || $ligne['QUANTITE'] == 0  // Si FAC_LIG.QUANTITE = 0, nons sommes en montant
        ) {
            return 0;
        }

        return $this->getEcartFactureQtComm($ligne['CaLigne']);
    }

    /**
     * param Facture $facture
     * @param array<mixed> $facture
     * param FacLigne&array{Brligne:Brligne,CaLigne:CaLigne} $ligne
     * @param array<mixed> $ligne
     */
    public function getEcartLignePrixUnitaire(array $facture, array $ligne) :float
    {
        if ($ligne['NUM_LICDE'] == 0 // Uniquement sur les lignes liées à une ligne de commande (FAC_LIG.NUM_LICDE<>0
            || $ligne['QUANTITE'] == 0 // Si FAC_LIG.QUANTITE = 0, nons sommes en montant
            || $ligne['NUM_BR'] == 0 || $ligne['NUM_LIBR'] == 0 // Réception en quantité
        ) {
            return 0;
        }

        $facLignePu = $this->getFactureLignePu($facture, $ligne);
        
        // Ligne de cde et ligne de BR, réception en Qté :
        if ($facLignePu == $this->getEcartDevise($facture, $ligne)
            && $this->getEcartFactureQtAcc($ligne['Brligne']) == 0
            && $this->getEcartFactureQtComm($ligne['CaLigne']) == 0
        ) {
            return 0;
        }

        return $facLignePu - (
            $ligne['CaLigne']['PU']
            * (1.0 - round($ligne['CaLigne']['REMISE'], 2) / 100.0)
        );
    }


    /**
     * param Facture $facture
     * @param array<mixed> $facture
     * param FacLigne&array{CaEntet:CaEntet} $ligne
     * @param array<mixed> $ligne
     */
    private function getEcartDevise(array $facture, array $ligne) :float
    {
        return $ligne['PU']
            * (1 - $ligne['REMISE'] / 100)
            * $ligne['CaEntet']['VAL_DEVIS'] / (
                $facture['TX_DEVISE'] != 0 ?
                    $facture['TX_DEVISE'] : 1
            );
    }

    /**
     * param BrLigne $brLigne
     * @param array<mixed> $brLigne
     */
    private function getEcartFactureQtAcc(array $brLigne) :float
    {
        if ($brLigne['QTE_FACT'] == 0) {
            return 0;
        }
        return $brLigne['QTE_FACT'] - $brLigne['QTE_ACCEP'];
    }

    /**
     * param CaLigne $caLigne
     * @param array<mixed> $caLigne
     */
    private function getEcartFactureQtComm(array $caLigne) :float
    {
        if ($caLigne['QTE_FACTU'] == 0) {
            return 0;
        }
        return $caLigne['QTE_FACTU'] - $caLigne['QTE_COMM'];
    }

    /**
     * param Facture $facture
     * @param array<mixed> $facture
     * param FacLigne $ligne
     * @param array<mixed> $ligne
     */
    private function getFactureLignePu(array $facture, array $ligne) :float
    {
        return ($facture['TYPE'] == 'F' ? 1 : -1) * $ligne['PU_REMISE'];
    }

//_____________________________________________________________________________
// Sub-routine format entity
// TODO : move somewhere else, use entity definition instead of hardcoded list

    /**
     * @param array<mixed> $entity
     * @return array<mixed>
     */
    private function formatFacture(array $entity) :array
    {
        // Parse bool
        foreach ([
            'ENT_VALID','AFF_ENREG','AFF_VALID','BON_A_PAY','ENT_COMPT',
            'AFF_COMPT','BAP_COMPT','TVA_COMPT','LITIGE','ATT_SIG','AFF_TMP',
            'FAC_LAD','REFUSEE','FLG_ARCHIVE','WF_ECART','REJET_COMPTA',
        ] as $field) {
            if (array_key_exists($field, $entity)) {
                $entity[ $field ] = Sql::toBool($entity[ $field ]);
            }
        }

        // Parse float
        foreach ([
            'TX_DEVISE', 'TOTAL_HT', 'TOTAL_TTC', 'CUM_HT_LI', 'CUM_TTCLI',
            'TX_DEVSTE', 'MT_TOTREG',
        ] as $field) {
            if (array_key_exists($field, $entity)) {
                $entity[ $field ] = floatval($entity[ $field ]);
            }
        }
        return $entity;
    }

    /**
     * @param array<mixed> $entity
     * @return array<mixed>
     */
    private function formatFactureLigne(array $entity) :array
    {
        // Parse bool
        foreach ([
            'TP_QUANT','AFF_TMP','LIG_ERR',
        ] as $field) {
            if (array_key_exists($field, $entity)) {
                $entity[ $field ] = Sql::toBool($entity[ $field ]);
            }
        }

        // Parse float
        foreach ([
            'QUANTITE','PU_REMISE','MONTANT','TAUX_TVA','MT_TVA',
        ] as $field) {
            if (array_key_exists($field, $entity)) {
                $entity[ $field ] = floatval($entity[ $field ]);
            }
        }
        return $entity;
    }

    /**
     * @param array<mixed> $entity
     * @return array<mixed>
     */
    private function formatBonRecepLigne(array $entity) :array
    {
        // Parse bool
        foreach ([
            'BR_VAL','A_CONTROL','CTRL_FAIT','EN_MNT',
        ] as $field) {
            if (array_key_exists($field, $entity)) {
                $entity[ $field ] = Sql::toBool($entity[ $field ]);
            }
        }

        // Parse float
        foreach ([
            'QTE_RECUE','QTE_COMPT','QTE_ACCEP','QTE_REPLO','QTE_FACT','QTECOLIS',
            'COEFCOL','QTERETOUR','MNT_REC','MNT_RET',
        ] as $field) {
            if (array_key_exists($field, $entity)) {
                $entity[ $field ] = floatval($entity[ $field ]);
            }
        }
        return $entity;
    }

    /**
     * @param array<mixed> $entity
     * @return array<mixed>
     */
    private function formatCaLigne(array $entity) :array
    {
        // Parse bool
        foreach ([
            'SAISIEREC','SSTOAPR','MASQ_QTE','MASQ_UNI','MASQ_PU','MASQ_DS',
            'SOLD_PHYS','RECEP_AUTO'
        ] as $field) {
            if (array_key_exists($field, $entity)) {
                $entity[ $field ] = Sql::toBool($entity[ $field ]);
            }
        }

        // Parse float
        foreach ([
            'PU','TVA','REMISE','QTE_COMM','RAL','QTE_FACTU','QTEFACVAL',
            'SOM_FACTU','SOMFACVAL','POIDS','LONGU','LARG','HAUT','VOLUME',
            'POIDSNET',
        ] as $field) {
            if (array_key_exists($field, $entity)) {
                $entity[ $field ] = floatval($entity[ $field ]);
            }
        }
        return $entity;
    }

    /**
     * @param array<mixed> $entity
     * @return array<mixed>
     */
    private function formatCaEntet(array $entity) :array
    {
        // Parse bool
        foreach ([
            'SIGNEE','REFUSEE','SAISIEREC','CDE_EXP','PREACH','RE_ACH','CTRLENTR',
            'FRANCTVA','FRNEXPORT','CHARGFOU','ATTDAU','AR_NCONF','INTERFA',
            'EXP_SOLDEES','RECEP_AUTO','CAEN_ECART_LIVR'
        ] as $field) {
            if (array_key_exists($field, $entity)) {
                $entity[ $field ] = Sql::toBool($entity[ $field ]);
            }
        }

        // Parse float
        foreach ([
            'VAL_DEVSTE','REM_GLOB','VAL_DEVIS','TOT_CDE','TOT_CDEHT','TOT_TTC',
            'DEJA_FAC','SOMFACVAL','VALDEVTR','MTSIMFACT',
        ] as $field) {
            if (array_key_exists($field, $entity)) {
                $entity[ $field ] = floatval($entity[ $field ]);
            }
        }
        return $entity;
    }
}
