<?php

declare(strict_types=1);

require_once __DIR__
    . \DIRECTORY_SEPARATOR . '..'
    . \DIRECTORY_SEPARATOR . '..'
    . \DIRECTORY_SEPARATOR . 'classes'
    . \DIRECTORY_SEPARATOR . 'OrderExporter.php';
require_once __DIR__
    . \DIRECTORY_SEPARATOR . '..'
    . \DIRECTORY_SEPARATOR . '..'
    . \DIRECTORY_SEPARATOR . 'classes'
    . \DIRECTORY_SEPARATOR . 'OrderSlipExporter.php';

class AdminMyVetShopExportComptableController extends ModuleAdminController
{
    /**
     * Créé des chunks de 1 semaine maximum à partir de $start jusqu'à $end.
     *
     * @return array<int,array<int, bool|DateTimeImmutable>>
     */
    protected function chunkize(DateTimeImmutable $start, DateTimeImmutable $end): array
    {
        $chunks = [];

        $chunkStart = $start;
        do {
            $chunkEnd = $chunkStart->add(new \DateInterval('P7D'));
            if ($chunkEnd > $end) {
                $chunkEnd = $end;
            }

            // Ajout du morceau de 1 semaine
            $chunks[] = [
                $chunkStart,
                $chunkEnd,
            ];

            // On déplace le prochain chunk
            $chunkStart = $chunkEnd;
        } while ($chunkStart < $end);

        return $chunks;
    }

    /**
     * Récupère toutes les commandes ayant une facture ou un avoir compris dans l'interval indiqué.
     *
     * @return OrderInvoice[]
     */
    protected function getInvoicesForChunk(DateTimeInterface $start, DateTimeInterface $end): array
    {
        $result = Db::getInstance(false)
            ->executeS(
                'SELECT `oi`.*'
                . ' FROM `ps_orders` o'
                . ' INNER JOIN `ps_order_invoice` oi ON `oi`.`id_order` = `o`.`id_order`'
                . ' LEFT JOIN `ps_order_slip` os ON `os`.`id_order` = `o`.`id_order`'
                . ' WHERE (`o`.`invoice_date` >= "' . $start->format('Y-m-d H:i:s') . '" AND `o`.`invoice_date` < "' . $end->format('Y-m-d H:i:s') . '")'
                . ' OR (`os`.`date_add` >= "' . $start->format('Y-m-d H:i:s') . '" AND `os`.`date_add` < "' . $end->format('Y-m-d H:i:s') . '")'
            );

        if (!is_array($result)) {
            $result = [];
        }

        return ObjectModel::hydrateCollection(
            OrderInvoice::class, $result
        );
    }

    /**
     * Récupère toutes les commandes ayant une facture ou un avoir compris dans l'interval indiqué.
     *
     * @return OrderSlip[]
     */
    protected function getOrderSlipForChunk(DateTimeInterface $start, DateTimeInterface $end): array
    {
        $result = Db::getInstance(false)
            ->executeS(
                'SELECT `os`.*'
                . ' FROM `ps_order_slip` os'
                . ' WHERE (`os`.`date_add` >= "' . $start->format('Y-m-d H:i:s') . '" AND `os`.`date_add` < "' . $end->format('Y-m-d H:i:s') . '")'
            );

        if (!is_array($result)) {
            $result = [];
        }

        return ObjectModel::hydrateCollection(
            OrderSlip::class, $result
        );
    }

    public function __construct()
    {
        parent::__construct();

        $this->bootstrap = true;

        Module::getInstanceByName('myvetshopclinique');
    }

    /**
     * @return array<string, mixed>
     *
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    public function getTemplateViewVars(): array
    {
        $start = Tools::getValue('start');
        $end = Tools::getValue('end');
        $type = Tools::getValue('type', 'comptable');
        $infosExports = null;

        if ($start && $end) {
            $startDate = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $start . ' 00:00:00');
            $endDate = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $end . ' 00:00:00');

            if (!$startDate) {
                throw new Exception('Une erreur s\'est produite lors de l\'instanciation de la date de début');
            }

            if (!$endDate) {
                throw new Exception('Une erreur s\'est produite lors de l\'instanciation de la date de fin');
            }

            $endDate = $endDate->add(new DateInterval('P1D'));

            $chunks = $this->chunkize($startDate, $endDate);

            if ('comptable' == $type) {
                $infosExports = [
                    'nb_commandes' => 0,
                    'nb_success' => 0,
                    'nb_errors' => 0,
                    'nb_lignes' => 0,
                    'commandes_erreurs' => [],
                ];

                foreach ($chunks as $chunk) {
                    if (!isset($chunk[0]) || !isset($chunk[1]) || !$chunk[0] instanceof DateTimeInterface || !$chunk[1] instanceof DateTimeInterface) {
                        throw new Exception('Le format des données n\'est pas valide');
                    }

                    $invoices = $this->getInvoicesForChunk($chunk[0], $chunk[1]);
                    $exports = array_map(
                        fn ($orderInvoice) => new OrderExporter($orderInvoice), $invoices
                    );

                    $infosExports = array_reduce(
                        $exports,
                        function ($infosExports, $export) use ($chunk) {
                            /* @var OrderExporter $export */
                            ++$infosExports['nb_commandes'];

                            try {
                                $lignes = $export->getLignesComptables($chunk[0], $chunk[1]);
                                if (OrderExporter::ensureCoherent($lignes)) {
                                    ++$infosExports['nb_success'];
                                } else {
                                    // Tentative de correction de la commande / facture / avoir
                                    $orderCorrector = new OrderCorrector($export->getOrder());
                                    // Maximum 10 essais
                                    for ($r = 0; $r < 10 && !$orderCorrector->isCoherent() && $orderCorrector->isRepairable(); ++$r) {
                                        $orderCorrector->repair();
                                        $orderCorrector = new OrderCorrector($export->getOrder());
                                    }
                                    // Purge le cache
                                    Cache::getInstance()->delete('*');

                                    // Recharge l'export
                                    $export = new OrderExporter(new OrderInvoice($export->getOrderInvoice()->id));
                                    $lignes = $export->getLignesComptables($chunk[0], $chunk[1]);
                                    if (OrderExporter::ensureCoherent($lignes)) {
                                        ++$infosExports['nb_success'];
                                    } else {
                                        ++$infosExports['nb_errors'];

                                        $infosExports['commandes_erreurs'][] = $export->getOrder()->id;
                                    }
                                }
                                $infosExports['nb_lignes'] += count($lignes);
                            } catch (Exception $e) {
                                ++$infosExports['nb_errors'];
                                $infosExports['commandes_erreurs'][] = $export->getOrder()->id;
                            }

                            return $infosExports;
                        },
                        $infosExports
                    );
                }
            } elseif ('remboursement' == $type) {
                $infosExports = [
                    'nb_remboursements' => 0,
                    'nb_errors' => 0,
                    'nb_lignes' => 0,
                ];

                foreach ($chunks as $chunk) {
                    if (!isset($chunk[0]) || !isset($chunk[1]) || !$chunk[0] instanceof DateTimeInterface || !$chunk[1] instanceof DateTimeInterface) {
                        throw new Exception('Le format des données n\'est pas valide');
                    }

                    $invoices = $this->getOrderSlipForChunk($chunk[0], $chunk[1]);
                    $exports = array_map(
                        fn ($orderSlip) => new OrderSlipExporter($orderSlip), $invoices
                    );

                    $infosExports = array_reduce(
                        $exports,
                        function ($infosExports, $export) {
                            /* @var OrderSlipExporter $export */
                            ++$infosExports['nb_remboursements'];

                            try {
                                $lignes = $export->getLignesComptables();
                                $infosExports['nb_lignes'] += count($lignes);
                            } catch (Exception $e) {
                                ++$infosExports['nb_errors'];
                            }

                            return $infosExports;
                        },
                        $infosExports
                    );
                }
            }
        }

        return [
            'start' => $start,
            'end' => $end,
            'type' => $type,
            'info_exports' => $infosExports,
        ];
    }

    /**
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    public function renderList(): string
    {
        $helper = new HelperView();
        $this->setHelperDisplay($helper);
        $helper->tpl_vars = $this->getTemplateViewVars();
        if (null !== $this->base_tpl_view) {
            $helper->base_tpl = $this->base_tpl_view;
        }
        $view = $helper->generateView();

        return $view;
    }

    /**
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    public function processDownloadComptable(): void
    {
        $start = \strval(\Tools::getValue('start'));
        $end = \strval(\Tools::getValue('end'));

        $startDate = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $start . ' 00:00:00');
        $endDate = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $end . ' 00:00:00');

        if (!$startDate) {
            throw new Exception('Une erreur s\'est produite lors de l\'instanciation de la date de début');
        }

        if (!$endDate) {
            throw new Exception('Une erreur s\'est produite lors de l\'instanciation de la date de fin');
        }

        $endDate = $endDate->add(new DateInterval('P1D'));

        $csv = \fopen('php://temp', 'w');
        if (!$csv) {
            throw new \Exception('Erreur générale');
        }
        \fputcsv($csv, ['date', 'journal', 'compte', 'intitule', 'nofacture', 'debit', 'credit'], ';');

        $chunks = $this->chunkize($startDate, $endDate);

        foreach ($chunks as $chunk) {
            if (!isset($chunk[0]) || !isset($chunk[1]) || !$chunk[0] instanceof DateTimeInterface || !$chunk[1] instanceof DateTimeInterface) {
                throw new Exception('Le format des données n\'est pas valide');
            }

            $invoices = $this->getInvoicesForChunk($chunk[0], $chunk[1]);
            $exports = \array_map(
                fn ($orderInvoice) => new OrderExporter($orderInvoice), $invoices
            );

            foreach ($exports as $export) {
                try {
                    $lignes = $export->getLignesComptables($chunk[0], $chunk[1]);

                    foreach ($lignes as $ligne) {
                        \fputcsv($csv, $ligne, ';');
                    }
                } catch (Exception $e) {
                    // Inhibiteur d'erreur
                }
            }
        }

        // Rembobine le flux
        \rewind($csv);

        \header('Content-Type: text/csv');
        \header(
            'Content-Disposition: attachment; filename="export-comptable-'
            . $startDate->format('Y-m-d') . '-' . $endDate->format('Y-m-d') . '.csv"'
        );
        \fpassthru($csv);
        \fclose($csv);

        exit(0);
    }

    /**
     * @throws Exception
     */
    public function processDownloadRemboursement(): void
    {
        $start = Tools::getValue('start');
        $end = Tools::getValue('end');

        $startDate = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $start . ' 00:00:00');
        $endDate = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $end . ' 00:00:00');

        if (!$startDate) {
            throw new Exception('Une erreur s\'est produite lors de l\'instanciation de la date de début');
        }

        if (!$endDate) {
            throw new Exception('Une erreur s\'est produite lors de l\'instanciation de la date de fin');
        }

        $endDate = $endDate->add(new DateInterval('P1D'));

        $csv = fopen('php://temp', 'w');
        if (!$csv) {
            var_dump('Erreur générale');
            exit;
        }
        fputcsv($csv, ['numero_avoir', 'date_avoir', 'id_commande', 'date_facture', 'clinique', 'centrale', 'entrepot', 'produits_ht', 'port_ht', 'total_ttc'], ';');

        $chunks = $this->chunkize($startDate, $endDate);

        foreach ($chunks as $chunk) {
            if (!isset($chunk[0]) || !isset($chunk[1]) || !$chunk[0] instanceof DateTimeInterface || !$chunk[1] instanceof DateTimeInterface) {
                throw new Exception('Le format des données n\'est pas valide');
            }

            $invoices = $this->getOrderSlipForChunk($chunk[0], $chunk[1]);
            $exports = array_map(
                fn ($orderSlip) => new OrderSlipExporter($orderSlip), $invoices
            );

            foreach ($exports as $export) {
                try {
                    $lignes = $export->getLignesComptables();

                    foreach ($lignes as $ligne) {
                        fputcsv($csv, $ligne, ';');
                    }
                } catch (Exception $e) {
                    // Inhibiteur d'erreur
                }
            }
        }

        // Rembobine le flux
        rewind($csv);

        header('Content-Type: text/csv');
        header('Content-Disposition: attachment; filename="export-remboursement-' . $startDate->format('Y-m-d') . '-' . $endDate->format('Y-m-d') . '.csv"');
        fpassthru($csv);
        fclose($csv);

        exit(0);
    }
}
