<?php
namespace Modules\Commande\Controller;

use Entity\Model\CaCritere;
use Entity\Model\CaEntet;
use Entity\Model\CdeCrit;
use Entity\Model\UserTheme;
use GuzzleHttp\Psr7\Response;
use Helpers\Outil\Mail;
use Helpers\Outil\Roles;
use Helpers\Outil\Sql;
use Skeleton\Controller\Controller;
use Helpers\Ariane\Ariane;
use Helpers\Log\LogSysteme;
use Helpers\Trad\Trad;
use Modules\Commande\MenuContextuel\NotationMenuContextuel;
use Modules\Commande\Repository\NotationCommande;

/**
 * @see \Modules\Commande\Rules\NotationCommande
 */
class NotationCommandeController extends Controller
{
    public function indexAction() : string
    {
        $post = $this->request->getParsedBody();
        $trad = $this->getTranslationService();

        if (!$post) {
            $post['filter_NOTMANU']='T';
            $post['tri_NO_CDE'] = 'asc';
        }
        
        $notation = new NotationCommande($this->pdo);
        $liste = $notation->listeCde($post);

        $menuContextuel = new NotationMenuContextuel($this->pdo, $this->router, $this->getTranslationService(), []);
        foreach ($liste as &$not) {
            $not['menuContextuel'] = $menuContextuel->generate($not);
        }
        
        Ariane::generate(0, $trad->t('Notation par Commande'), $post, count($liste));
        
        $paginator = new \Helpers\Pagination\Paginator();
        $infopagination = $paginator->generate($liste, 0, $post, $this->renderer);
        $tabManuelle = [
            '' => $trad->t('Toutes'),
            'T' => $trad->t('Manuelle'),
            'F' => $trad->t('Automatique'),
        ];

        /* **** */
        
        return $this->renderer->render('@Commande/Notation/index', [
            'menuActive'        => 'CollecteBesoins',
            'infoPagination'    => $infopagination,
            'liste'             => $liste,
            'tabManuelle'       => $tabManuelle,
            'post'              => $post,
        ]);
    }

    public function detailAction() :string
    {
        $commandeId = $this->request->getAttribute('commandeId');

        $commande =
            (new NotationCommande($this->pdo))
                ->getCommandeNotation($commandeId);

        if ($commande == null) {
            throw new \Exception('Invalid commande #'.$commandeId);
        }

        $trad = $this->getTranslationService();
        Ariane::generate(1, $trad->t('Visualisation notation commande'));
        ariane::setFocus($commandeId);

        return $this->renderer->render('@Commande/Notation/notationDetail', [
            'commande' => $commande,
        ]);
    }

    public function popupConfirmActivateAction() :Response
    {
        $commandeId = $this->request->getAttribute('commandeId');
        $trad = $this->getTranslationService();

        if (! Roles::droitsUtilisateur($this->pdo, 'HA_NOT_NM')) {
            return $this->createJsonResponse([
                'title' => $trad->t('Erreur'),
                'body' => Trad::strBuild(
                    $trad->t('Aucun processus de notation fournisseur n\'est activé pour la commande N° @1@.'),
                    $commandeId
                ),
            ]);
        }

        return $this->createJsonResponse([
            'title' => $trad->t('Notation fournisseur'),
            'body' => $this->renderer->render(
                '@Commande/Notation/activateConfirmPopup',
                ['commandeId' => $commandeId]
            ),
        ]);
    }

    public function activateAction() :Response
    {
        $commandeId = $this->request->getAttribute('commandeId');
        $repo = (new NotationCommande($this->pdo));
        $commande = $repo->getCommandeForActivation($commandeId);
        if ($commande == null) {
            throw new \Exception('Invalid commande #'.$commandeId);
        }
        if ($commande['NoteGlobal'] != null) {
            LogSysteme::prefixErrorLog('Trying to initialize a notation which already exist');
            return new Response(400);
        }

        //_____________________________
        // Process

        /** @phpstan-ignore-next-line */
        $repo->initialize($commande);

        //_____________________________
        // Post processes

        $this->sendEmailNotationAlert($commande);
        
        return new Response();
    }


    public function formCreateAction() :string
    {
        $commandeId = $this->request->getAttribute('commandeId');

        $commande =
            (new NotationCommande($this->pdo))
                ->getCommandeNotation($commandeId);

        if ($commande == null) {
            throw new \Exception('Invalid commande #'.$commandeId);
        }


        //_________
        // Calc : NoteTheme.isEditable

        $listUserThemeCode = array_column(
            (new UserTheme($this->pdo))->findAll([
                'COD_UTIL' => $_SESSION['Session_Login'],
            ]),
            'COD_THEM'
        );
        foreach ($commande->mapNoteTheme as $noteTheme) {
            $noteTheme->isEditable = $this->isEditable(
                $_SESSION['Session_Login'],
                $noteTheme,
                $commande,
                $listUserThemeCode
            );
        }

        //_________

        $trad = $this->getTranslationService();

        Ariane::generate(1, $trad->t('Saisie des notes'));
        ariane::setFocus($commandeId);

        return $this->renderer->render('@Commande/Notation/notationFormCreate', [
            'commande' => $commande,
            'arianeBack' => ariane::getBackPath(),
        ]);
    }

    public function createAction() :Response
    {
        $commandeId = $this->request->getAttribute('commandeId');
        $commande = (object) (new CaEntet($this->pdo))->find($commandeId);
        if ($commande == null) {
            throw new \Exception('Invalid commande #'.$commandeId);
        }

        $commande->listCaCritere = (new CaCritere($this->pdo))->findAll([
            'NO_CDE' => $commandeId,
        ]);

        $commande->mapNoteTheme = (new NotationCommande($this->pdo))
            ->listFormNotationCommande($commandeId);

        $listUserThemeCode = array_column(
            (new UserTheme($this->pdo))->findAll([
                'COD_UTIL' => $_SESSION['Session_Login'],
            ]),
            'COD_THEM'
        );
        foreach ($commande->mapNoteTheme as $noteTheme) {
            $noteTheme->isEditable = $this->isEditable(
                $_SESSION['Session_Login'],
                $noteTheme,
                $commande,
                $listUserThemeCode
            );
        }

        $post = $this->request->getParsedBody();

        //_____________________________
        // Validation

        // Filter un-editable theme
        foreach ($commande->mapNoteTheme as $noteTheme) {
            if (! $noteTheme->isEditable) {
                unset($post['critere'][ $noteTheme->COD_THEM ]);
            }
        }

        // Filter invalid Critere value
        $mapNote = []; // GRILLES indexed by COD_THEM;COD_CRIT;LIBNOT
        foreach (($post['critere'] ?? []) as $codeTheme => $listCritereValue) {
            foreach ($listCritereValue as $codeCrit => $value) {
                $critere = $commande
                    ->mapNoteTheme[ $codeTheme ]
                    ->Theme
                    ->listCritere[ $codeCrit ];
                
                if (! in_array($value, array_column($critere->listChoix, 'LIBNOT'))) {
                    throw new \Exception('Invalid value "'.$value.'"');
                }

                $mapNote[ implode(';', [$codeTheme, $codeCrit, $value]) ] =
                    array_column($critere->listChoix, null, 'LIBNOT');
            }
        }

        //_____________________________
        // Update DB

        $this->pdo->beginTransaction();

        // Create/update 'Answers' (CaCrit alias CDE__CRIT)
        $qbCaCrit = (new CdeCrit($this->pdo));
        foreach (($post['critere'] ?? []) as $codeTheme => $listCritereValue) {
            foreach ($listCritereValue as $codeCrit => $value) {
                // TODO : merge into one query
                $qbCaCrit->merge([
                    'NO_CDE' => $commande->NO_CDE,
                    'COD_THEM' => $codeTheme,
                    'COD_CRIT' => $codeCrit,
                    'NOTLIB' => $value,
                ]);
            }
        }

        (new NotationCommande($this->pdo))->updateNoteAverage($commande->NO_CDE);

        $this->pdo->commit();

        return new Response();
    }

    public function popupConfirmDevalidateAction() :Response
    {
        $commandeId = $this->request->getAttribute('commandeId');
        $trad = $this->getTranslationService();

        return $this->createJsonResponse([
            'title' => $trad->t('Notation fournisseur'),
            'body' => $this->renderer->render(
                '@Commande/Notation/devalidateConfirmPopup',
                [
                    'commandeId' => $commandeId,
                ]
            ),
        ]);
    }

    public function devalidateAction() :Response
    {
        $commandeId = $this->request->getAttribute('commandeId');
        $repo = (new NotationCommande($this->pdo));

        //_____________________________
        // Process

        $repo->devalidationNotation($commandeId);

        //_____________________________
        // Post processes

        $commande = (new NotationCommande($this->pdo))
            ->getCommandeForActivation($commandeId);
        $this->sendEmailNotationAlert($commande);

        return new Response();
    }

//_____________________________________________________________________________
// Sub-routine

    /**
     * param object{Theme:object{TH_AUT:string,TH_ACHET:string,TH_DEM:string}} $noteTheme
     * @param \stdClass $noteTheme
     * param object{DEMANDEUR:string,ACH:string} $commande
     * @param \stdClass $commande
     * @param array<string> $listUserThemeCode
     */
    private function isEditable(
        string $user,
        object $noteTheme,
        object $commande,
        array $listUserThemeCode
    ) :bool {
        if (Sql::toBool($noteTheme->Theme->TH_ACHET)
            && $commande->ACH == $user
        ) {
            return true;
        }
        if (Sql::toBool($noteTheme->Theme->TH_AUT)
            && in_array($noteTheme->Theme->COD_THEM, $listUserThemeCode)
        ) {
            return true;
        }
        if (Sql::toBool($noteTheme->Theme->TH_DEM)
            && $commande->DEMANDEUR == $user
        ) {
            return true;
        }
        return false;
    }

    /**
     * @see NotationCommande::getCommandeForActivation()
     * @param array<mixed> $commande as loaded from getCommandeForActivation
     * @return array<array{CODE:string,NOM:string,EMAIL:string,COD_LNG:string}>
     */
    private function getEmailActivateRecipientList(array $commande) :array
    {
        $listRecipient = [];

        // Case : commande est soldée physiquement
        if ($commande['ETAT_CDE'] == 'S') {
            $listRecipient = array_merge($listRecipient, $commande['listSoldeurPhysique']);
        }

        // Case : commande est soldée financièrement
        if ($commande['ETAT_FIN'] == 'F') {
            $listRecipient = array_merge($listRecipient, $commande['listSoldeurFinance']);
        }

        // Case : Theme "Demandeur"
        if (! empty(array_filter(
            $commande['listTheme'],
            function ($theme) {
                return Sql::toBool($theme['TH_DEM']);
            }
        ))
            && !empty($commande['Demandeur'])
        ) {
            $listRecipient[] = $commande['Demandeur'];
            $listRecipient = array_merge($listRecipient, $commande['listDaDemandeur']);
        }

        // Case : Theme "Acheteur"
        if (! empty(
            array_filter(
                $commande['listTheme'],
                function ($theme) {
                    return Sql::toBool($theme['TH_ACHET']);
                }
            )
        )) {
            $listRecipient[] = $commande['Acheteur'];
        }

        // Case : Every BonReceps have been created by the same user
        if (count($commande['listAuthorBonRecep']) == 1) {
            $listRecipient[] = reset($commande['listAuthorBonRecep']);
        }

        // Case : Every Factures have been created by the same user
        if (count($commande['listAuthorFacture']) == 1) {
            $listRecipient[] = reset($commande['listAuthorFacture']);
        }

        // Force unique
        return array_column($listRecipient, null, 'CODE');
    }
    
    /**
     * @param array<mixed> $commande
     */
    private function sendEmailNotationAlert(array $commande) :void
    {
        $listRecipient = $this->getEmailActivateRecipientList($commande);

        foreach ($listRecipient as $recipient) {
            Mail::generateMail(
                $this->pdo,
                $this->getTranslationService(),
                $recipient['EMAIL'],
                'CDE_NOTE_ALERT',
                $recipient['COD_LNG'],
                [
                    'RS' => $commande['Fourniss']['RS'],
                    'COD_FOU' => $commande['Fourniss']['CODE'],
                    'NO_CDE' => $commande['NO_CDE'],
                    'OBJET_CA' => $commande['OBJET_CA'],
                    'NOM' => $recipient['NOM'],
                ]
            );
        }
    }
}
