<?php

namespace App\Classes\Modules\Transactions\Processors;

use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\Modules\Bookings\Services\CalculatesBookingPayableAmount;
use App\Classes\Modules\Bookings\Services\CalculatesBookingTransferredAmount;
use App\Classes\Modules\Bookings\Services\CalculatesBookingRefundAmount;
use App\Classes\Modules\ServiceTypes\Services\FetchesServiceConfigurations;
use App\Classes\Modules\Transactions\Services\ListsTransactions;
use App\Classes\Modules\Transactions\Services\CreatesTransaction;
use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber;
use App\Classes\Modules\Bookings\Services\CalculatesBookingPaidAmount;
use App\Classes\Modules\Bookings\Services\CalculatesBookingCurrencyAverageRate;
use App\Classes\Modules\Companies\Services\FetchesCompany;
use App\Classes\Modules\Bookings\Services\UpdatesBookingStatus;
use App\Classes\Modules\Bookings\Services\UpdatesBookingInvoiceStatus;
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
use App\Classes\Modules\Bookings\DataTransferObjects\UpdateBookingInvoiceStatusObject;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\SegmentConstants;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Classes\ValueObjects\Constants\KVPKey;
use App\Models\Booking;
use App\Models\SegmentConstant;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Facades\Log;

class CreateInvoiceTransactionV2Processor
{

    /** @var CreatesTransaction */
    private $createsTransaction;

    /** @var GeneratesTransactionBillNumber */
    private $generatesTransactionBillNumber;

    /** @var CalculatesBookingPaidAmount */
    private $calculatesBookingPaidAmount;

    /** @var CalculatesBookingPayableAmount */
    private $calculatesBookingPayableAmount;

    /** @var CalculatesBookingTransferredAmount */
    private $calculatesBookingTransferredAmount;

    /** @var CalculatesBookingCurrencyAverageRate */
    private $calculatesBookingCurrencyAverageRate;

    /** @var FetchesCompany */
    private $fetchesCompany;

    /** @var UpdatesBookingStatus */
    private $updatesBookingStatus;

    /** @var UpdatesBookingInvoiceStatus */
    private $updatesBookingInvoiceStatus;

    /** @var CreateInvoiceDocumentProcessor */
    private $invoiceDocumentProcessor;

    /** @var CalculatesBookingRefundAmount */
    private $calculatesBookingRefundAmount;


    /**
     * CreateInvoiceTransactionV2Processor constructor.
     * @param ListsTransactions $listsTransactions
     * @param CreatesTransaction $createsTransaction
     * @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
     * @param CalculatesBookingPaidAmount $calculatesBookingPaidAmount
     * @param CalculatesBookingPayableAmount $calculatesBookingPayableAmount
     * @param CalculatesBookingTransferredAmount $calculatesBookingTransferredAmount
     * @param FetchesServiceConfigurations $fetchesServiceConfigurations
     * @param CalculatesBookingCurrencyAverageRate $calculatesBookingCurrencyAverageRate
     * @param FetchesCompany $fetchesCompany
     * @param UpdatesBookingStatus $updatesBookingStatus
     * @param CreateInvoiceDocumentProcessor $invoiceDocumentProcessor
     * @param CalculatesBookingRefundAmount $calculatesBookingRefundAmount
     * @param UpdatesBookingInvoiceStatus $updatesBookingInvoiceStatus
     */
    public function __construct(ListsTransactions $listsTransactions, CreatesTransaction $createsTransaction, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CalculatesBookingPaidAmount $calculatesBookingPaidAmount, CalculatesBookingPayableAmount $calculatesBookingPayableAmount, CalculatesBookingTransferredAmount $calculatesBookingTransferredAmount, FetchesServiceConfigurations $fetchesServiceConfigurations, CalculatesBookingCurrencyAverageRate $calculatesBookingCurrencyAverageRate, FetchesCompany $fetchesCompany, UpdatesBookingStatus $updatesBookingStatus, CreateInvoiceDocumentProcessor $invoiceDocumentProcessor, CalculatesBookingRefundAmount $calculatesBookingRefundAmount, UpdatesBookingInvoiceStatus $updatesBookingInvoiceStatus)
    {
        $this->createsTransaction = $createsTransaction;
        $this->generatesTransactionBillNumber = $generatesTransactionBillNumber;
        $this->calculatesBookingPaidAmount = $calculatesBookingPaidAmount;
        $this->calculatesBookingPayableAmount = $calculatesBookingPayableAmount;
        $this->calculatesBookingTransferredAmount = $calculatesBookingTransferredAmount;
        $this->calculatesBookingCurrencyAverageRate = $calculatesBookingCurrencyAverageRate;
        $this->fetchesCompany = $fetchesCompany;
        $this->updatesBookingStatus = $updatesBookingStatus;
        $this->invoiceDocumentProcessor = $invoiceDocumentProcessor;
        $this->calculatesBookingRefundAmount = $calculatesBookingRefundAmount;
        $this->updatesBookingInvoiceStatus = $updatesBookingInvoiceStatus;
    }



    /**
     * @param Booking $booking
     * @param String $invoiceNo
     * @param Illuminate\Support\Collection<\App\Models\KeyValuePair> $kvpCopies
     * @param array $options
     * @return void
     * @throws MalformedRequestException
     */
    public function execute(Booking $booking, String $invoiceNo= "", $kvpCopies = null, array $options = [])
    {
        $generateEInvoice = $options['generateEInvoice'] ?? false;
        $generateEInvoiceWithNormalInvoiceTemplate = $options['generateEInvoiceWithNormalInvoiceTemplate'] ?? false;
        $generateEInvoiceRefund = $options['generateEInvoiceRefund'] ?? false;
        $bookingOriginalStatus = $options['bookingOriginalStatus'] ?? null;

        $bookingOriginalStatus = null; //cief todo: 151 - getting ready to get rid of bookingOriginalStatus as an options parameter

        // Log::info('CreateInvoiceTransactionV2Processor generateEInvoice:' . json_encode($generateEInvoice));
        // Log::info('CreateInvoiceTransactionV2Processor generateEInvoiceWithNormalInvoiceTemplate: ' . json_encode($generateEInvoiceWithNormalInvoiceTemplate));
        // Log::info('CreateInvoiceTransactionV2Processor generateEInvoiceRefund: ' . json_encode($generateEInvoiceRefund));
        // Log::info('CreateInvoiceTransactionV2Processor bookingOriginalStatus: ' . json_encode($bookingOriginalStatus));

        $proceed = true;

        if ($booking->status === ApprovalStatus::COMPLETED && !$generateEInvoiceRefund) {
            Log::info('CreateInvoiceTransactionV2Processor Check 1 Bypass New Business Logic Update for booking ' . $booking->id);
            // return;
        }

        $payable_amount = $this->calculatesBookingPayableAmount->execute($booking, $booking->fix_currency_id, $generateEInvoiceRefund);
        $refund_amount = $this->calculatesBookingRefundAmount->execute($booking, $booking->fix_currency_id);
        $booking_amount = $booking->fix_amount;

        if ((float) $booking_amount === (float) $refund_amount && !$generateEInvoiceRefund) {
            Log::info('CreateInvoiceTransactionV2Processor Check 3 Bypass New Business Logic Update for booking ' . $booking->id);
            //return;
        }

        // confirm that booking amount has been fully paid
        if ((float) $booking_amount > (float) $payable_amount) {
            Log::info('CreateInvoiceTransactionV2Processor return 1');
            $proceed = false;
            return;
        }
        // confirm that all payments has been transferred
        if ($this->calculatesBookingTransferredAmount->execute($booking) !== $this->calculatesBookingPaidAmount->execute($booking)) {
            Log::info('CreateInvoiceTransactionV2Processor Check 2 Bypass New Business Logic Update for booking ' . $booking->id);
            // return;
        }

        $completedPurchaseOrder = $booking->transactions()
            ->where('type', TransactionType::PURCHASE_ORDER)
            ->complete()
            ->first();
        $purchaseOrder = $completedPurchaseOrder;

        if($this->calculatesBookingTransferredAmount->execute($booking) === $this->calculatesBookingPaidAmount->execute($booking) && $completedPurchaseOrder) {
            $bookingOriginalStatus = ApprovalStatus::COMPLETED;
        }
        else{
            $bookingOriginalStatus = ApprovalStatus::APPROVED;
        }

        if($proceed){
            if(!$purchaseOrder && $generateEInvoiceRefund){
                $purchaseOrder = $booking->transactions()
                    ->where('type', TransactionType::PURCHASE_ORDER)
                    ->where('status', ApprovalStatus::PENDING_SUBMISSION)
                    ->first();
            }

            $constants = SegmentConstant::where('reference', SegmentConstants::SERVICE_TYPE)->where('detail->id', $booking->service->id)->first();
            // if ($constants->detail->is_billable && !$purchaseOrder && !$generateEInvoiceRefund) {
            //     return;
            // }

            $transaction = $booking->transactions()
                ->where('type', TransactionType::PAYMENT)
                ->latest()->get()[0];
            $supplier = $this->fetchesCompany->execute(['id' => $transaction->receiver]);

            // Check if eInvoice implementation has started and company opted in for eInvoice
            $eInvoice = false;
            $eInvoiceStartDate = Carbon::parse(env('E_INVOICE_START_DATE', '2025-07-01 00:00:00'));
            $bookingCreatedDate = Carbon::parse($booking->created_at);
            $eInvoiceRequestedDate = Carbon::parse($supplier->e_invoice_requested_at);

            if ($bookingCreatedDate->isAfter($eInvoiceStartDate) && ($bookingCreatedDate->isAfter($eInvoiceRequestedDate) || $bookingCreatedDate->diffInMinutes($eInvoiceRequestedDate) <= 60) && $supplier->e_invoice === 1) {
                $eInvoice = true;
            }
            $kvp = $booking->attributesKVP()->where('key', KVPKey::BOOKING_EINVOICE_ELIGIBLE)->first();
            if($kvp){
                $eInvoice = true;
            }

            if($generateEInvoiceWithNormalInvoiceTemplate){
                $invoiceNo = ""; //July 2025 workaround generate normal invoice instead of E-Invoice
            }

            if($invoiceNo){
                $billNumber = $invoiceNo;
            }
            else{
                $billNUmberPrefix = $eInvoice ? 'EINV-' : 'INV-';
                if($generateEInvoiceWithNormalInvoiceTemplate){
                    $billNUmberPrefix = 'INV-'; //July 2025 workaround generate normal invoice instead of E-Invoice
                }
                $billNumber = $this->generatesTransactionBillNumber->execute($billNUmberPrefix);
            }

            $booking_currency_average_rate = $this->calculatesBookingCurrencyAverageRate->execute($booking, TransactionType::PAYMENT, $generateEInvoiceRefund);

            if($generateEInvoiceRefund){
                $total_service_charge = $booking->transactions()
                    ->where('type', TransactionType::PAYMENT)
                    ->whereIn('status', [ApprovalStatus::REFUNDED])
                    ->sum('service_charge');

                $total_tax = $booking->transactions()
                    ->where('type', TransactionType::PAYMENT)
                    ->whereIn('status', [ApprovalStatus::REFUNDED])
                    ->sum('tax');
            }
            else{
                $total_service_charge = $booking->transactions()
                    ->where('type', TransactionType::PAYMENT)
                    ->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])
                    ->sum('service_charge');

                $total_tax = $booking->transactions()
                    ->where('type', TransactionType::PAYMENT)
                    ->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])
                    ->sum('tax');
            }

            $invoice_transaction = null;
            // if($booking->status === ApprovalStatus::APPROVED){
            //     //NEW INVOICE for non-einvoice user applicable only when booking is completed
            //     $invoice_transaction = $booking->transactions()
            //         ->where('type', TransactionType::INVOICE)
            //         ->complete()
            //         ->latest()
            //         ->first();
            // }

            $invoice_transaction = $booking->transactions()
                ->whereIn('type', [TransactionType::INVOICE])
                ->complete()
                ->latest()
                ->first();

            if(!$invoice_transaction) {
                $transaction_object = new TransactionObject(
                    $billNumber,
                    TransactionType::INVOICE,
                    $transaction->issuer,
                    $transaction->receiver,
                    $transaction->recipient_bank_account_id,
                    $transaction->payment_method,
                    $payable_amount,
                    $booking_amount,
                    $transaction->currency_id,
                    $transaction->original_currency_id,
                    $booking_currency_average_rate,
                    $total_tax,
                    $total_service_charge,
                    null,
                    ApprovalStatus::APPROVED
                );
                $invoice_transaction = $this->createsTransaction->execute($purchaseOrder->booking ?? $booking, $transaction_object);
            }

            if ($kvpCopies) {
                foreach ($kvpCopies as $kvp) {
                    $invoice_transaction->attributesKVP()->save($kvp);
                }
            }

            $voucherRedemption = $transaction->voucherRedemption;

            if($generateEInvoiceRefund){
                $metadata = $booking->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE)->first();
                if($metadata){
                $generateEInvoice = true;
                }
                else{
                    $metadata = $transaction->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE)->first();
                    if($metadata){
                        $generateEInvoice = true;
                    }
                }
            }

            if($purchaseOrder){
                // purchase order
                if(!$generateEInvoiceRefund){
                    $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::PURCHASE_ORDER, $voucherRedemption, $generateEInvoiceWithNormalInvoiceTemplate);
                }

                // deliver order
                if(!$generateEInvoiceRefund){
                    $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::DELIVER_ORDER, $voucherRedemption, $generateEInvoiceWithNormalInvoiceTemplate);
                }

                // e-invoice
                if ($eInvoice)
                {
                    if($generateEInvoice){
                        $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::EINVOICE, $voucherRedemption, $generateEInvoiceWithNormalInvoiceTemplate, $generateEInvoiceRefund);
                    }
                }
                // invoice
                else
                {
                    $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::INVOICE, $voucherRedemption, $generateEInvoiceWithNormalInvoiceTemplate, $generateEInvoiceRefund);
                }

                if(!$generateEInvoiceRefund){
                    $billNumber = $this->generatesTransactionBillNumber->execute('SPDO-');

                    $booking_currency_average_rate = $this->calculatesBookingCurrencyAverageRate->execute($booking, TransactionType::BILL, $generateEInvoiceRefund);

                    $paymentTransaction = $booking->transactions()->payments()->where('status', ApprovalStatus::COMPLETED)->first();

                    $transactionTypeBill= null;
                    if($paymentTransaction){
                        $transactionTypeBill = $paymentTransaction->transactions()->where('type', TransactionType::BILL)->first();
                    }
                    else{ // Special handling for refund cases (When a refund is deleted via DeleteRefundTransactionLogic, a booking payment transaction is set to ApprovalStatus::APPROVED)
                        $paymentTransactionTemp = $booking->transactions()->payments()->where('status', ApprovalStatus::APPROVED)->first();

                        // Lets check if there is a full refund case
                        if(!$paymentTransactionTemp){
                            $paymentTransactionTemp = $booking->transactions()->payments()->where('status', ApprovalStatus::REFUNDED)->first();
                            Log::info('CreateInvoiceTransactionV2Processor full refund: ' . json_encode($paymentTransactionTemp));
                        }

                        // Lets check if there is a partial refund case
                        $refund = $paymentTransactionTemp->transactions()->refunds()->where('status', ApprovalStatus::APPROVED)->first();
                        if($refund){
                            Log::info('CreateInvoiceTransactionV2Processor partial refund: ' . json_encode($refund));
                            $transactionTypeBill = $paymentTransactionTemp->transactions()->where('type', TransactionType::BILL)->first();
                        }
                        else{
                            Log::info("CreateInvoiceTransactionV2Processor NO completed payment transaction nor refund transaction found for booking '$booking->id'.");
                        }
                    }

                    if($transactionTypeBill){
                        $supplier_deliver_order_transaction = $booking->transactions()
                            ->whereIn('type', [TransactionType::SUPPLIER_DELIVER])
                            ->complete()
                            ->latest()
                            ->first();

                        if(!$supplier_deliver_order_transaction){
                            $transaction_object = new TransactionObject(
                                $billNumber,
                                TransactionType::SUPPLIER_DELIVER,
                                $transactionTypeBill->issuer,
                                $transactionTypeBill->receiver,
                                $transactionTypeBill->recipient_bank_account_id,
                                $transactionTypeBill->payment_method,
                                $payable_amount,
                                $booking_amount,
                                $transactionTypeBill->currency_id,
                                $transactionTypeBill->original_currency_id,
                                $booking_currency_average_rate,
                                $total_tax,
                                $total_service_charge,
                                null,
                                ApprovalStatus::APPROVED
                            );
                            $supplier_deliver_order_transaction = $this->createsTransaction->execute($purchaseOrder->booking, $transaction_object);
                        }

                        // supply deliver order
                        $this->invoiceDocumentProcessor->execute($supplier_deliver_order_transaction, $purchaseOrder, $supplier, DocumentType::SUPPLIER_DELIVER_ORDER, null);
                    }
                }
            }
        }

        $this->updateStatuses($booking, $bookingOriginalStatus ?? 0);
    }

    //Update booking overall status and invoice status
    public function updateStatuses(Booking $booking, int $bookingStatus){
        $updateBookingInvoiceStatusObject = new UpdateBookingInvoiceStatusObject($booking->id, ApprovalStatus::APPROVED);
        $this->updatesBookingInvoiceStatus->execute($booking, $updateBookingInvoiceStatusObject);

        if($bookingStatus){
            $this->updatesBookingStatus->execute($booking, $bookingStatus);
        }
    }
}
