<?php


namespace App\Classes\Modules\Transactions\Processors;

use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\Modules\Orders\Services\FetchesOrder;
use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber;
use App\Classes\Modules\Transactions\Services\CreatesTransaction;
use App\Classes\Modules\Transactions\Services\CreatesTransactionDetail;
use App\Classes\Modules\Transactions\Services\UpdatesTransaction;
use App\Classes\Modules\Transactions\Services\FetchesTransaction;
use App\Classes\Modules\Transactions\Services\UpdatesTransactionDetail;
use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus;
use App\Classes\Modules\Transactions\Services\DeletesGroup;
use App\Classes\Modules\Billplzs\Services\DeletesBillplzBill;
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionDetailObject;


use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\PaymentMethodType;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\PackageType;
use App\Classes\ValueObjects\Constants\PackingListType;
use App\Classes\ValueObjects\Constants\TransactionDetailType;
use App\Http\Resources\TransactionWithStorageResource;
use App\Models\Order;
use App\Models\PackingList;
use App\Models\Transaction;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;


class CheckStorageInvoiceTransactionProcessor
{
    /** @var FetchesOrder */
    private $fetchesOrder;

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

    /** @var FetchesTransaction */
    private $fetchesTransaction;

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

    /** @var CreatesTransactionDetail */
    private $createsTransactionDetail;

    /** @var UpdatesTransaction */
    private $updatesTransaction;

    /** @var UpdatesTransactionDetail */
    private $updatesTransactionDetail;

    /** @var UpdatesTransactionStatus */
    private $updatesTransactionStatus;

    /** @var DeletesGroup */
    private $deletesGroup;

    /** @var DeletesBillplzBill */
    private $deletesBillplzBill;

    /**
     * @param FetchesOrder $fetchesOrder
     * @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
     * @param FetchesTransaction $fetchesTransaction
     * @param CreatesTransaction $createsTransaction
     * @param CreatesTransactionDetail $createsTransactionDetail
     * @param UpdatesTransaction $updatesTransaction
     * @param UpdatesTransactionDetail $updatesTransactionDetail
     * @param UpdatesTransactionStatus $updatesTransactionStatus
     * @param DeletesGroup $deletesGroup
     * @param DeletesBillplzBill $deletesBillplzBill
     */
    public function __construct(FetchesOrder $fetchesOrder, GeneratesTransactionBillNumber $generatesTransactionBillNumber, FetchesTransaction $fetchesTransaction, CreatesTransaction $createsTransaction, CreatesTransactionDetail $createsTransactionDetail, UpdatesTransaction $updatesTransaction, UpdatesTransactionDetail $updatesTransactionDetail, UpdatesTransactionStatus $updatesTransactionStatus, DeletesGroup $deletesGroup, DeletesBillplzBill $deletesBillplzBill)
    {
        $this->fetchesOrder = $fetchesOrder;
        $this->generatesTransactionBillNumber = $generatesTransactionBillNumber;
        $this->fetchesTransaction = $fetchesTransaction;
        $this->createsTransaction = $createsTransaction;
        $this->createsTransactionDetail = $createsTransactionDetail;
        $this->updatesTransaction = $updatesTransaction;
        $this->updatesTransactionDetail = $updatesTransactionDetail;
        $this->updatesTransactionStatus = $updatesTransactionStatus;
        $this->deletesGroup = $deletesGroup;
        $this->deletesBillplzBill = $deletesBillplzBill;
    }

    /**
     * @throws MalformedRequestException
     */
    public function execute(int $orderReference)
    {
        $order = $this->fetchesOrder->execute(['reference' => $orderReference, 'with_packing_lists' => true]);
        return $this->executeOrder($order);
    }

    public function executeOrder(Order $order, bool $isBackDoorCheck = false){
        $results = [];
        $marking = $order->companyModule->inviters()->withPivot('invitee_reference')->first()->pivot->invitee_reference;
        $is_credit_term = $order->companyModule->inviters()->withPivot('is_credit_term')->first()->pivot->is_credit_term;
        if(!$is_credit_term){
            Log::channel('storage_invoices')->info('orderId: '.$order->id.', orderReference: '.$order->reference.', marking: '.$marking.', is_credit_term: '.$is_credit_term);
            $packingLists = $order->destinationWarehousePackages;

            foreach ($packingLists as $packingList){
                $arrivalDateAtChinaWarehouse = $this->getArrivalDateAtChinaWarehoue($packingList);
                Log::channel('storage_invoices')->info('arrivalDateAtChinaWarehouse: '.json_encode($arrivalDateAtChinaWarehouse));
                Log::channel('storage_invoices')->info('destinationWarehousePackage: '.json_encode($packingList));
                $eta = $this->getEtaFromPackingList($packingList);
                if($arrivalDateAtChinaWarehouse && $eta){
                    $transactions = $packingList->transactions()->where('transactions.type', TransactionType::SHIPPING_INVOICE)->whereIn('transactions.status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])->get();
                    // $transactions = $destinationWarehousePackage->transactions()->where('transactions.type', TransactionType::SHIPPING_INVOICE)->where('transactions.status', ApprovalStatus::APPROVED)->get();

                    /** @var Transaction $invoice_transaction */
                    foreach ($transactions as $invoice_transaction){
                        $result =  $this->processSingleTransactionOfTypeShippingInvoice($invoice_transaction, $packingList, $order->company_module_id, $eta, $isBackDoorCheck);
                        if($result){
                            $results[] = $result;
                        }
                    }
                }
            }
        }
        return $results;
    }

    private function getArrivalDateAtChinaWarehoue($packingList){
        if($packingList->type === PackingListType::SHIPPING_PACKING_LIST){
            $receive_packing_list = PackingList::where('reference', $packingList->reference)->where('type', PackingListType::WAREHOUSE_RECEIVE_LIST)->first();
            Log::channel('storage_invoices')->info('receive_packing_list: '.json_encode($receive_packing_list->transports));
            $transport = $receive_packing_list->transports->first();
            if ($transport) {
                $arrivalDateAtChinaWarehouse = Carbon::parse($transport->drop_date);
                $dateToCompare = Carbon::parse(env('STORAGE_FEE_LAUNCH_DATE', '2023-12-11 00:00:00'));
                // dd($dateToCompare);
                if ($arrivalDateAtChinaWarehouse->isAfter($dateToCompare)) {
                    Log::channel('storage_invoices')->info('transport: '.json_encode($transport));
                    Log::channel('storage_invoices')->info('dateToCompare: '.$dateToCompare.', arrivalDateAtChinaWarehouse: '.$arrivalDateAtChinaWarehouse);
                    return $arrivalDateAtChinaWarehouse;
                }
            }
        }
        return null;
    }

    private function getEtaFromPackingList($packingList){
        if ($packingList) {
            $package = $packingList->packages->first();
            if ($package) {
                $container = $package->container()->first();
                if ($container) {
                    $transport = $container->transports->first();
                    if ($transport) {
                        $schedule = $transport->schedules->last();
                        if ($schedule) {
                            return $schedule->eta;
                        }
                    }
                }
            }
        }
        return null;
    }

    private function processSingleTransactionOfTypeShippingInvoice($transaction, $destinationWarehousePackage, $company_module_id, $eta, $isBackDoorCheck){
        $pricePerCBM = 3;
        $resultNumberOfDaysFree = 10;
        $dt1 = $eta->copy()->addDay()->startOfDay();
        $resultStartDate =  $dt1->format('Y-m-d');
        $currentDatetime = Carbon::now();
        $dt2 = $currentDatetime->copy()->addDay()->startOfDay();
        $resultCurrentDate = $dt2->format('Y-m-d H:i:s');
        $interval = Carbon::parse($dt2)->diff($dt1);

        $resultNumberOfDaysExceeded = $interval->days - $resultNumberOfDaysFree;
        $storageInvoice = $destinationWarehousePackage->transactions()->where('transactions.type', TransactionType::STORAGE_INVOICE)->first();
        $storageInvoiceId = 0;
        $transactionDetailsItems = $transaction->transactionDetails()->get();
        $cbm = 0.00;
        foreach ($transactionDetailsItems as $tdItem){
            $quantity = $tdItem->quantity;
            if($tdItem->price < 0.00){
                $quantity = $quantity * -1;
            }
            $cbm =  $cbm + $quantity;
        }

        $price_cbm = $pricePerCBM * $cbm * $resultNumberOfDaysExceeded;
        Log::channel('storage_invoices')->info('storageInvoice: '.json_encode($storageInvoice).', $transaction->status: '.$transaction->status);
        if(!$storageInvoice && $resultNumberOfDaysExceeded  > 0 && $transaction->status != ApprovalStatus::COMPLETED){
            Log::channel('storage_invoices')->info('Created $transaction->id: '.$transaction->id);
            $billNumber = $this->generatesTransactionBillNumber->execute('STOR-');
            $storageInvoice = $this->createStorageInvoiceTransaction($destinationWarehousePackage, $billNumber, $company_module_id, $price_cbm);
            $storageInvoiceId = $storageInvoice->id;
            $this->createStorageInvoiceTransactionDetails($storageInvoice, $destinationWarehousePackage, $cbm, $pricePerCBM, $resultNumberOfDaysExceeded);
        }
        else if($storageInvoice){
            //Additional handling for giving selected storage invoice a waiver
            if(isset($storageInvoice->is_waived) && $storageInvoice->is_waived){
                $pricePerCBM = 0;
                $price_cbm = $pricePerCBM * $cbm * $resultNumberOfDaysExceeded;
            }

            //Additional handling for calculation of numberOfDaysExceeded in the event of the storage already paid
            $paymentStorageTransaction = $storageInvoice->transactions()->where('transactions.type', TransactionType::PAYMENT)->where('transactions.status', ApprovalStatus::APPROVED)->first();
            if($paymentStorageTransaction){
                $dateStorageInvoicePaid = $paymentStorageTransaction->created_at->copy()->addDay()->startOfDay();
                Log::channel('storage_invoices')->info('dateStorageInvoicePaid: '.$dateStorageInvoicePaid.', resultCurrentDate: '.$resultCurrentDate);
                $intervalRecalculate = Carbon::parse($dateStorageInvoicePaid)->diff($dt1);
                $resultNumberOfDaysExceeded = $intervalRecalculate->days - $resultNumberOfDaysFree;
                $price_cbm = $pricePerCBM * $cbm * $resultNumberOfDaysExceeded;
            }

            $amount = $storageInvoice->amount;
            $epsilon = 0.0001; // Tolerance for the comparison
            Log::channel('storage_invoices')->info('Update $transaction->id: '.$transaction->id);
            Log::channel('storage_invoices')->info('$storageInvoice->status: '.$storageInvoice->status);
            Log::channel('storage_invoices')->info('price_cbm: '.$price_cbm."-".gettype($price_cbm));
            Log::channel('storage_invoices')->info('amount: '.$amount);

            $proceedToUpdate = false;

            if(abs($price_cbm - $amount) > $epsilon && $storageInvoice->status !== ApprovalStatus::COMPLETED){
                $paymentTransactions = $storageInvoice->transactions()->where('transactions.type', TransactionType::PAYMENT)->where('transactions.status', ApprovalStatus::PENDING_SUBMISSION)->get();
                if(!$isBackDoorCheck){
                    if(count($paymentTransactions) > 0){
                        $this->updatePaymentTransactionViaNonGroupPayment($paymentTransactions);
                    }
                    else{
                        $this->updatePaymentTransactionViaGroupPayment($storageInvoice);
                    }
                }

                $proceedToUpdate = true;
            }

            if($proceedToUpdate || ($pricePerCBM == 0 && $amount != 0)){
                Log::channel('storage_invoices')->info('proceedToUpdate');
                $storageInvoice = $this->updateStorageInvoiceTransaction($storageInvoice, $price_cbm);
                $invoiceTransactionDetails = $storageInvoice->transactionDetails()->first();
                $this->updateStorageInvoiceTransactionDetails($invoiceTransactionDetails, $destinationWarehousePackage, $cbm, $pricePerCBM, $resultNumberOfDaysExceeded);
            }

            $storageInvoiceId = $storageInvoice->id;
        }

        if($storageInvoiceId !== 0){
            $result = [
                'parentInvoiceId' => $transaction->id,
                'storageInvoiceId' => $storageInvoiceId,
                'numberOfDaysExceeded' => $resultNumberOfDaysExceeded,
                'numberOfDaysFree' => $resultNumberOfDaysFree,
                'startDate' => $resultStartDate,
                'currentDate' => $resultCurrentDate,
                'cbm' => $cbm,
                'pricePerCBM' => $pricePerCBM,
                'storageInvoice' => new TransactionWithStorageResource($storageInvoice)
            ];
            return $result;
        }

    }

    private function updatePaymentTransactionViaGroupPayment($storageInvoice){
        Log::channel('storage_invoices')->info('updatePaymentTransactionViaGroupPayment');
        $groups = $storageInvoice->groups()->get();
        foreach ($groups as $grp){
            $groupReference = $grp->reference;
            $walletTransaction = $this->fetchesTransaction->execute(['payment_reference' => $groupReference]);
            if($walletTransaction->status === ApprovalStatus::PENDING_SUBMISSION || $walletTransaction->status === ApprovalStatus::PENDING_VERIFICATION){
                $grp->status = ApprovalStatus::EXPIRED;
                $grp->save();
                $this->deletesGroup->execute($grp);
                $this->updatesTransactionStatus->execute($walletTransaction, ApprovalStatus::EXPIRED);
                if($walletTransaction->payment_reference){
                    $deletedBillplzBill = $this->deletesBillplzBill->execute($walletTransaction->payment_reference);
                    Log::channel('storage_invoices')->info('deletedBillplzBill TransactionType::WALLET: '.json_encode($deletedBillplzBill));
                }
            }
        }
    }

    private function updatePaymentTransactionViaNonGroupPayment($paymentTransactions){
        Log::channel('storage_invoices')->info('updatePaymentTransactionViaNonGroupPayment');
        foreach ($paymentTransactions as $paymentTransaction){
            $this->updatesTransactionStatus->execute($paymentTransaction, ApprovalStatus::EXPIRED);
            if($paymentTransaction->payment_reference){
                $deletedBillplzBill = $this->deletesBillplzBill->execute($paymentTransaction->payment_reference);
                Log::channel('storage_invoices')->info('deletedBillplzBill TransactionType::WALLET: '.json_encode($deletedBillplzBill));
            }
        }
    }

    private function updateStorageInvoiceTransaction(Transaction $transaction, float $totalAmount){

        $object = new TransactionObject(
            $transaction->bill_no,
            TransactionType::STORAGE_INVOICE,
            1,
            $transaction->receiver,
            1,
            PaymentMethodType::CASH,
            $totalAmount,
            $totalAmount,
            1,
            1,
            0,
            0,
            0,
            null,
            ApprovalStatus::APPROVED
        );

        /** @var Transaction $invoice_transaction */
        $invoice_transaction = $this->updatesTransaction->execute($transaction, $object);

        return $invoice_transaction;
    }

    private function updateStorageInvoiceTransactionDetails($invoice_transaction_details, PackingList $packing_list, $cbm, $pricePerCBM, $numberOfDays){
        $object_detail = new TransactionDetailObject(
            'STORAGE_FEE',
            TransactionDetailType::STORAGE_FEE.' for '.$numberOfDays. ' days * RM'.$pricePerCBM.'<br>'.round($packing_list->packages->where('type', '!=', PackageType::OVER_WEIGHT)->sum('quantity'), 3).' CTNS - '.round($cbm, 3).' CBM',
            $cbm,
            $pricePerCBM * $numberOfDays
        );

        $this->updatesTransactionDetail->execute($invoice_transaction_details, $object_detail);
    }

    private function createStorageInvoiceTransaction(PackingList $packing_list, string $billNumber, int $companyModuleId, float $totalAmount){

        $object = new TransactionObject(
            $billNumber,
            TransactionType::STORAGE_INVOICE,
            1,
            $companyModuleId,
            1,
            PaymentMethodType::CASH,
            $totalAmount,
            $totalAmount,
            1,
            1,
            0,
            0,
            0,
            null,
            ApprovalStatus::APPROVED
        );

        /** @var Transaction $invoice_transaction */
        $invoice_transaction = $this->createsTransaction->execute($packing_list, $object);

        return $invoice_transaction;
    }

    private function createStorageInvoiceTransactionDetails($invoice_transaction, PackingList $packing_list, $cbm, $pricePerCBM, $numberOfDays){
        $object_detail = new TransactionDetailObject(
            'STORAGE_FEE',
            TransactionDetailType::STORAGE_FEE.' for '.$numberOfDays. ' days * RM'.$pricePerCBM.'<br>'.round($packing_list->packages->where('type', '!=', PackageType::OVER_WEIGHT)->sum('quantity'), 3).' CTNS - '.round($cbm, 3).' CBM',
            $cbm,
            $pricePerCBM * $numberOfDays
        );

        $this->createsTransactionDetail->execute($invoice_transaction, $object_detail);
    }
}
