<?php

namespace App\Modules\FactReceipt\Application\Send;

use App\Modules\Credential\Application\List\ListCredentialQuery;
use App\Modules\Credential\Application\List\ListCredentialResponse;
use App\Modules\Credential\Domain\CredentialList;
use App\Modules\FactCurrency\Application\Index\FactCurrencyIndexer;
use App\Modules\FactCurrency\Domain\FactCurrency;
use App\Modules\FactPaymentMethod\Application\Index\FactPaymentMethodIndexer;
use App\Modules\FactPaymentMethod\Domain\FactPaymentMethod;
use App\Modules\FactReceipt\Application\Index\FactReceiptIndexer;
use App\Modules\FactReceipt\Application\UpdateStatus\FactReceiptStatusUpdater;
use App\Modules\FactReceipt\Domain\DTODatosComprobante;
use App\Modules\FactReceipt\Domain\DtoFactReceiptRepository;
use App\Modules\FactReceipt\Domain\DTOItemVentaMovil;
use App\Modules\FactReceipt\Domain\FactReceipt;
use App\Modules\FactReceipt\Domain\FactReceiptResponseMessage;
use App\Modules\FactReceipt\Domain\IdFactReceipt;
use App\Modules\FactReceipt\Domain\UpdateStatusFactReceipt;
use App\Modules\FactReceiptDetail\Application\List\FactReceiptDetailLister;
use App\Modules\FactReceiptDetail\Domain\FactReceiptDetail;
use App\Modules\FactTypeDocument\Application\Index\FactTypeDocumentIndexer;
use App\Modules\FactTypeDocument\Domain\FactTypeDocument;
use App\Modules\FactTypeReceipt\Application\Index\FactTypeReceiptIndexer;
use App\Modules\FactTypeReceipt\Domain\FactTypeReceipt;
use App\Modules\Shared\Application\Util;
use App\Modules\Shared\Domain\Bus\Query\QueryBus;
use App\Modules\Shared\Domain\ValueObject\Uuid;
use DateTime;
use DateTimeZone;
use Throwable;

final class FactReceiptSender
{
    public function __construct(
        private DtoFactReceiptRepository $repository,
        private FactReceiptIndexer $indexer,
        private FactReceiptStatusUpdater $statusUpdater,
        private FactTypeDocumentIndexer $typeDocumentIndexer,
        private FactTypeReceiptIndexer $typeReceiptIndexer,
        private FactPaymentMethodIndexer $paymentMethodIndexer,
        private FactCurrencyIndexer $currencyIndexer,
        private FactReceiptDetailLister $detailLister,
        private QueryBus $queryBus
    ) {
    }

    public function __invoke(IdFactReceipt $idFactReceipt)
    {
        $factReceipt = $this->indexer->__invoke($idFactReceipt);
        $factReceipt->send();
        $this->updateStatus($factReceipt);
        $typeReceipt = $this->typeReceiptIndexer->__invoke($factReceipt->idFactTypeReceipt());
        $typeDocument = $this->typeDocumentIndexer->__invoke($factReceipt->idFactTypeDocument());
        $paymentMethod = $this->paymentMethodIndexer->__invoke($factReceipt->idFactPaymentMethod());
        $currency = $this->currencyIndexer->__invoke($factReceipt->idFactCurrency());
        $factReceiptDetails = $this->detailLister->__invoke($idFactReceipt, 'active');
        /** @var ListCredentialResponse $response */
        $response = $this->queryBus->ask(new ListCredentialQuery());
        $credentialList = new CredentialList($response->response());

        $dto = $this->generateDto(
            $factReceipt,
            $typeReceipt,
            $typeDocument,
            $paymentMethod,
            $currency,
            $factReceiptDetails,
            $credentialList
        );
        try {
            $response = $this->repository->create($dto, $credentialList);
            $path = $this->saveAsZip(
                $response,
                date('YmdHis') . '-' . $factReceipt->businessName()->value() . '-' . $factReceipt->noDocument()->value()
                    . '-' . Util::generateString(4)
            );
            $factReceipt->receive(new FactReceiptResponseMessage($path));
            return $this->updateStatus($factReceipt);
        } catch (Throwable $ex) {
            $factReceipt->error(new FactReceiptResponseMessage($ex->getMessage()));
            return $this->updateStatus($factReceipt);
        }
    }

    private function formatName(string $name)
    {
        return preg_replace('/([^A-Za-z0-9-])/', '', $name);
    }

    private function saveAsZip(string $value, string $name): string
    {
        $base64 = $this->fromBase64($value);
        $path = config('var.PATH_PRIVATE') . 'files/zip/' . $this->formatName($name) . '.zip';
        $publicPath = config('var.PATH_PUBLIC') . 'files/zip/' . $this->formatName($name) . '.zip';
        file_put_contents($path, $base64);
        return $publicPath;
    }

    private function fromBase64(string $base64): string
    {
        return base64_decode($base64);
    }

    private function updateStatus(FactReceipt $factReceipt)
    {
        return $this->statusUpdater->__invoke(new UpdateStatusFactReceipt(
            $factReceipt->id(),
            $factReceipt->responseMessage(),
            $factReceipt->status(),
        ));
    }

    /**
     * @param FactReceiptDetail[] $details
     */
    private function generateDto(
        FactReceipt $factReceipt,
        FactTypeReceipt $typeReceipt,
        FactTypeDocument $typeDocument,
        FactPaymentMethod $paymentMethod,
        FactCurrency $currency,
        array $details,
        CredentialList $credentialList
    ) {
        $total = array_reduce($details, fn ($carry, FactReceiptDetail $detail) => $carry + $detail->total()->value(), 0);
        return new DTODatosComprobante(
            null,
            $typeReceipt->abrv()->value(),
            null,
            $typeDocument->abrv()->value(),
            strtoupper($factReceipt->noDocument()->value()),
            strtoupper($factReceipt->businessName()->value()),
            strtoupper($factReceipt->address()->value()),
            $paymentMethod->abrv()->value(),
            $total,
            $currency->abrv()->value(),
            $factReceipt->email()->value(),
            0,
            new DateTime($factReceipt->dateEmission()->value(), new DateTimeZone('America/Lima')),
            new DateTime($factReceipt->dateEmission()->value(), new DateTimeZone('America/Lima')),
            0.0,
            0,
            0.0,
            0.0,
            null,
            array_map(fn (FactReceiptDetail $detail) => new DTOItemVentaMovil(
                $credentialList->getValue('Billing_Item_Id'),
                $credentialList->getValue('Billing_Item_IdProducto'),
                strtoupper($detail->idFactProduct()->code()->value()),
                strtoupper($detail->idFactProduct()->name()->value()),
                $detail->unit->abrv()->value(),
                1,
                $detail->unitPrice()->value(),
                $detail->amount()->value(),
                $detail->total()->value(),
                (object)[],
                false,
                ''
            ), $details)
        );
    }
}
