<?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;

class AccountingExporter
{
    protected DocumentSource $documentSource;

    protected ExportChecker $exportChecker;

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

    protected BulkDocumentProcessor $bulkDocumentProcessor;

    protected 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): array
    {
        $stats = [
            'nbInvoices' => 0,
            'nbSlips' => 0,
            'nbErrors' => 0,
            'errors' => [],
            'nbLines' => 0,
        ];

        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)
                    ;
                }
                $stats['nbLines'] += \count($lines);
            }
        }

        return $stats;
    }

    /**
     * @return resource
     */
    public function getCsv(\DateTimeImmutable $startDate, \DateTimeImmutable $endDate)
    {
        $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]);
                }

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

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

        \rewind($ret);

        return $ret;
    }
}
