<?php

namespace Myvetshop\Module\Clinique\Accounting\Export;

use Myvetshop\Module\Clinique\Accounting\Export\Bulk\BulkDocumentProcessor;
use Myvetshop\Module\Clinique\Accounting\Export\Check\ExportChecker;
use Myvetshop\Module\Clinique\Accounting\Export\Exporter\DocumentExporterInterface;
use Myvetshop\Module\Clinique\Accounting\Export\Exporter\OrderInvoiceExporter;
use Myvetshop\Module\Clinique\Accounting\Export\Exporter\OrderSlipExporter;
use Myvetshop\Module\Clinique\Accounting\Export\Iterator\DocumentChunkIterator;
use Myvetshop\Module\Clinique\Accounting\Export\Iterator\OrderInvoiceChunkIterator;
use Myvetshop\Module\Clinique\Accounting\Export\Iterator\OrderSlipChunkIterator;
use Myvetshop\Module\Clinique\Accounting\Export\Model\ExportLine;
use Myvetshop\Module\Clinique\Accounting\Export\PostProcess\GlobalPostProcessorInterface;

class AccountingExporter
{
    private DocumentSource $documentSource;

    private ExportChecker $exportChecker;

    /**
     * @var array{
     *     'OrderInvoice' : DocumentExporterInterface<\OrderInvoice>,
     *     'OrderSlip' : DocumentExporterInterface<\OrderSlip>
     * }
     */
    private array $documentExporters;

    private BulkDocumentProcessor $bulkDocumentProcessor;

    private function getDocumentIterator(
        \DateTimeImmutable $startDate,
        \DateTimeImmutable $endDate
    ): DocumentChunkIterator {
        $slipIterator = new OrderSlipChunkIterator(
            $this->documentSource,
            $startDate,
            $endDate
        );

        $invoiceIterator = new OrderInvoiceChunkIterator(
            $this->documentSource,
            $startDate,
            $endDate
        );

        return new DocumentChunkIterator([$slipIterator, $invoiceIterator]);
    }

    public function __construct(
        DocumentSource $documentSource,
        ExportChecker $exportChecker,
        OrderInvoiceExporter $orderInvoiceExporter,
        OrderSlipExporter $orderSlipExporter,
        BulkDocumentProcessor $bulkDocumentProcessor
    ) {
        $this->documentSource = $documentSource;
        $this->documentExporters = [
            'OrderInvoice' => $orderInvoiceExporter,
            'OrderSlip' => $orderSlipExporter,
        ];
        $this->exportChecker = $exportChecker;
        $this->bulkDocumentProcessor = $bulkDocumentProcessor;
    }

    /**
     * @return array{nbInvoices: int, nbSlips: int, nbErrors: int, nbLines: int, errors: string[]}
     */
    public function getStats(
        \DateTimeImmutable $startDate,
        \DateTimeImmutable $endDate,
        ?GlobalPostProcessorInterface $postProcessor = null
    ): array {
        $stats = [
            'nbInvoices' => 0,
            'nbSlips' => 0,
            'nbErrors' => 0,
            'errors' => [],
            'nbLines' => 0,
        ];

        if ($postProcessor) {
            $postProcessor->reset();
        }

        foreach ($this->getDocumentIterator($startDate, $endDate) as $chunk) {
            $orders = $this->documentSource->getOrders($chunk);

            /** @var array{0: \OrderInvoice|\OrderSlip, 1: \Order} $docWithOrder */
            foreach ($this->bulkDocumentProcessor->processChunk($chunk, $orders) as $docWithOrder) {
                if ($docWithOrder[0] instanceof \OrderInvoice) {
                    ++$stats['nbInvoices'];
                    $lines = $this->documentExporters['OrderInvoice']->export($docWithOrder[0], $docWithOrder[1]);
                } else {
                    ++$stats['nbSlips'];
                    $lines = $this->documentExporters['OrderSlip']->export($docWithOrder[0], $docWithOrder[1]);
                }

                if (!$this->exportChecker->checkExport($lines)) {
                    ++$stats['nbErrors'];
                    $stats['errors'][] =
                        ($docWithOrder[0] instanceof \OrderInvoice ? 'Facture' : 'Avoir')
                        . '#'
                        . ($docWithOrder[0] instanceof \OrderInvoice ? $docWithOrder[0]->number : $docWithOrder[0]->id)
                    ;
                }

                if ($postProcessor) {
                    $postProcessor->addLines($lines);
                } else {
                    $stats['nbLines'] += \count($lines);
                }
            }
        }

        if ($postProcessor) {
            $stats['nbLines'] = \count($postProcessor->getLines());
        }

        return $stats;
    }

    /**
     * @return resource
     */
    public function getCsv(
        \DateTimeImmutable $startDate,
        \DateTimeImmutable $endDate,
        ?GlobalPostProcessorInterface $postProcessor = null
    ) {
        if ($postProcessor) {
            $postProcessor->reset();
        }

        $ret = \fopen('php://temp', 'w');

        if (!$ret) {
            throw new \Exception('Création de fichier impossible');
        }

        \fputcsv(
            $ret,
            ['date', 'journal', 'compte', 'auxiliaire', 'intitule', 'nofacture', 'debit', 'credit', 'payment'],
            ';'
        );

        foreach ($this->getDocumentIterator($startDate, $endDate) as $chunk) {
            $orders = $this->documentSource->getOrders($chunk);

            /** @var array{0: \OrderInvoice|\OrderSlip, 1: \Order} $docWithOrder */
            foreach ($this->bulkDocumentProcessor->processChunk($chunk, $orders) as $docWithOrder) {
                if ($docWithOrder[0] instanceof \OrderInvoice) {
                    $lines = $this->documentExporters['OrderInvoice']->export($docWithOrder[0], $docWithOrder[1]);
                } else {
                    $lines = $this->documentExporters['OrderSlip']->export($docWithOrder[0], $docWithOrder[1]);
                }

                if ($postProcessor) {
                    $postProcessor->addLines($lines);
                } else {
                    $rawLines = \array_map(
                        function (ExportLine $exportLine): array {
                            return [
                                $exportLine->getDate()->format('d/m/Y'),
                                $exportLine->getJournal(),
                                $exportLine->getAccount(),
                                $exportLine->getAuxiliary(),
                                $exportLine->getEntitled(),
                                $exportLine->getDocNumber(),
                                \number_format($exportLine->getDebit(), 2, '.', ' '),
                                \number_format($exportLine->getCredit(), 2, '.', ' '),
                                $exportLine->getPaymentMethod(),
                            ];
                        },
                        \array_filter(
                            $lines,
                            fn (ExportLine $exportLine) => $exportLine->getDebit() > 0 || $exportLine->getCredit() > 0,
                        )
                    );

                    foreach ($rawLines as $line) {
                        \fputcsv(
                            $ret,
                            $line,
                            ';'
                        );
                    }
                }
            }
        }

        if ($postProcessor) {
            $rawLines = \array_map(
                function (ExportLine $exportLine): array {
                    return [
                        $exportLine->getDate()->format('d/m/Y'),
                        $exportLine->getJournal(),
                        $exportLine->getAccount(),
                        $exportLine->getAuxiliary(),
                        $exportLine->getEntitled(),
                        $exportLine->getDocNumber(),
                        \number_format($exportLine->getDebit(), 2, '.', ' '),
                        \number_format($exportLine->getCredit(), 2, '.', ' '),
                        $exportLine->getPaymentMethod(),
                    ];
                },
                \array_filter(
                    $postProcessor->getLines(),
                    fn (ExportLine $exportLine) => $exportLine->getDebit() > 0 || $exportLine->getCredit() > 0,
                )
            );

            foreach ($rawLines as $line) {
                \fputcsv(
                    $ret,
                    $line,
                    ';'
                );
            }
        }

        \rewind($ret);

        return $ret;
    }
}
