<?php


namespace App\Classes\Modules\Transactions\Processors;

use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\PackingLists\Services\FetchesPackingList;
use App\Classes\Modules\SegmentConstants\Services\FetchesSegmentConstant;
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\Documents\Services\CreatesDocument;
use App\Classes\Modules\Documents\Services\CreatesFiles;
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionDetailObject;

use App\Classes\ValueObjects\Constants\OrderRoleTypes;
use App\Classes\ValueObjects\Constants\PackingListType;
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\SegmentConstants;
use App\Classes\ValueObjects\Constants\TransactionDetailType;

use App\Models\CompanyModule;
use App\Models\Document;
use App\Models\PackingList;
use App\Models\Transaction;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Mccarlosen\LaravelMpdf\Facades\LaravelMpdf;

class CreateInvoiceTransactionProcessor
{

    /** @var FetchesPackingList */
    private $fetchesPackingList;

    /** @var FetchesSegmentConstant */
    private $fetchesSegmentConstant;

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

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

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

    /**
     * @param FetchesPackingList $fetchesPackingList
     * @param FetchesSegmentConstant $fetchesSegmentConstant
     * @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
     * @param CreatesTransaction $createsTransaction
     * @param CreatesTransactionDetail $createsTransactionDetail
     */
    public function __construct(FetchesPackingList $fetchesPackingList, FetchesSegmentConstant $fetchesSegmentConstant, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatesTransaction $createsTransaction, CreatesTransactionDetail $createsTransactionDetail)
    {
        $this->fetchesPackingList = $fetchesPackingList;
        $this->fetchesSegmentConstant = $fetchesSegmentConstant;
        $this->generatesTransactionBillNumber = $generatesTransactionBillNumber;
        $this->createsTransaction = $createsTransaction;
        $this->createsTransactionDetail = $createsTransactionDetail;
    }

    /**
     * @throws MalformedRequestException
     */
    public function execute(PackingList $packingList)
    {
        $packing_list = PackingList::where('reference', $packingList->reference)->where('type', PackingListType::SHIPPING_PACKING_LIST)->first();

        $billable_packing_list = $packing_list->packingLists()->first();

        $billable_packing_list = $billable_packing_list ? $billable_packing_list : $packing_list;

        $cbm = round($billable_packing_list->packages->where('type', '!=', PackageType::OVER_WEIGHT)->sum(function($package) {
            return ($package->width / 100) * ($package->height / 100) *($package->length / 100) * ($package->quantity);
        }), 3);

        $over_weight_cbm = round($billable_packing_list->packages->where('type', PackageType::OVER_WEIGHT)->sum(function($package) {
            return ($package->width / 100) * ($package->height / 100) *($package->length / 100) * ($package->quantity);
        }), 3);

        $order =  $packing_list->owner;
        $companyModule = $order->companyModule;
        $connection = $companyModule->connections()->first();
        $address = $order->addresses()->first();

        $base_price_constant = $this->fetchesSegmentConstant->execute(['segment_id' => 1, 'reference' => SegmentConstants::BASE_PRICE]);
        $warehouse_rate_constant = $this->fetchesSegmentConstant->execute(['segment_id' => 1, 'reference' => SegmentConstants::WAREHOUSE_RATE]);
        $state_rate_constant = $this->fetchesSegmentConstant->execute(['segment_id' => 1, 'reference' => SegmentConstants::STATE_RATE]);
        $center_postcode_constant = $this->fetchesSegmentConstant->execute(['segment_id' => 1, 'reference' => SegmentConstants::CENTER_POSTCODE]);
        $outstation_postcode_constant = $this->fetchesSegmentConstant->execute(['segment_id' => 1, 'reference' => SegmentConstants::OUTSTATION_POSTCODE]);

        $noMinimumCharge = $connection->segments()->where('segments.id', 2)->first();

        $minimum_cbm = 0.3;
        $state_select = '';

        $segment_price = $connection->segments()->whereHas('constants', function($query){
            return $query->where('reference', SegmentConstants::CUSTOM_PRICE);
        })->get()->map(function($segment){
            return (float) $segment->constants()->first()->value[0];
        })->sort()->first();
        $segment_price = $segment_price ? $segment_price : 0;

        $arrivalTransport = PackingList::where('reference', $packing_list->reference)->where('type', 1)->first()->transports->first();
        if(!$arrivalTransport) {
            throw new MalformedRequestException('Arrival date is unknow can\'t generate invoice');
        }
        $packing_list_drop_date = $arrivalTransport->drop_date->format('Y-m-d');
        $base_price = $this->getConstantByKey($base_price_constant, $packing_list_drop_date);

        $warehouseId = $order->orderRoles()->where('role_id', OrderRoleTypes::ORIGIN_WAREHOUSE)->first()->company_module_id;
        $selected_warehouse_rate = $this->getConstantByKey($warehouse_rate_constant, $warehouseId);
        $warehouse_rate = is_object($selected_warehouse_rate) ? $selected_warehouse_rate->amount : 0;

        $stateId = $address->state_id;
        $state_rate_constant = $this->getConstantByKey($state_rate_constant, $stateId);

        // get $state_rate
        $postcode = $address->postcode;
        $this->checkPostcodeExistInConstant($center_postcode_constant, $postcode) === true ? $state_select = 'center' : '' ;
        $this->checkPostcodeExistInConstant($outstation_postcode_constant, $postcode) === true ? $state_select = 'outstation' : '' ;
        $state_rate_constant = (array)$state_rate_constant;
        $state_rate = $state_select == '' ? 0 : $state_rate_constant[$state_select];

        $container = $packing_list->containers()->first();
        $containerPackingLists = collect();
        $hasMinimumCharge = null;

        foreach ($container->packingLists as $packingList){
            if($packingList->owner instanceof CompanyModule) continue;
            if($packingList->owner->companyModule->id !== $companyModule->id) continue;
            $containerPackingLists->push($packingList);

            if(!$hasMinimumCharge) {
                $hasMinimumCharge = $packingList->transactions()->whereHas('transactionDetails', function ($query){
                    $query->where('reference', 'MIN_CBM_CHARGES');
                })->first();
            }

        }

        $totalContainerCbm = $containerPackingLists->sum(function($packingList){
            $billable_packing_list = $packingList->packingLists()->first();
            $billable_packing_list = $billable_packing_list ? $billable_packing_list : $packingList;
            return $billable_packing_list->packages->sum(function($package) {
                return ($package->width / 100) * ($package->height / 100) *($package->length / 100) * ($package->quantity);
            });
        });

        $minimum_charge = $minimum_cbm - $totalContainerCbm;
        $minimum_charge = ($minimum_charge < 0) ? 0 : $minimum_charge;
        $minimum_charge = $noMinimumCharge ? 0 : $minimum_charge;

        $minimum_charge = $noMinimumCharge ? 0 : round($minimum_charge, 3);


        if($hasMinimumCharge) {
            $minimum_charge = 0;
        }

        $price_cbm = $base_price + $segment_price + $warehouse_rate + $state_rate;

        $total_cbm = $price_cbm * ($cbm + $minimum_charge + $over_weight_cbm);

        $billNumber = $this->generatesTransactionBillNumber->execute('SHIP-');

        $object = new TransactionObject(
            $billNumber,
            TransactionType::SHIPPING_INVOICE,
            1,
            $order->company_module_id,
            1,
            PaymentMethodType::CASH,
            $total_cbm,
            $total_cbm,
            1,
            1,
            0,
            0,
            0,
            null,
            ApprovalStatus::PENDING_SUBMISSION
        );

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

        $object_detail = new TransactionDetailObject(
            'SHIPPING_FEE',
            TransactionDetailType::SHIPPING_FEE.'<br>'.round($packing_list->packages->where('type', '!=', PackageType::OVER_WEIGHT)->sum('quantity'), 3).' CTNS - '.round($cbm, 3).' CBM',
            $cbm,
            $price_cbm
        );

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

        if ($over_weight_cbm > 0) {
            $object_detail = new TransactionDetailObject(
                'OVER_WEIGHT_CHARGES',
                TransactionDetailType::OVER_WEIGHT_CHARGES,
                $over_weight_cbm,
                $price_cbm
            );

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

        if ($minimum_charge > 0) {
            $object_detail = new TransactionDetailObject(
                'MIN_CBM_CHARGES',
                TransactionDetailType::MIN_CBM_CHARGES,
                $minimum_charge,
                $price_cbm
            );

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

        return;
    }

    function getConstantByKey($segmentConstantObject, $key) {
        if ($segmentConstantObject) {
            $base_rate = (array) $segmentConstantObject->value;
            $base_rate = array_key_exists($key, $base_rate) === true ? $base_rate[$key] : 0;

            return $base_rate;
        }
    }

    function checkPostcodeExistInConstant($segmentConstantObject, $postcode) {
        $segmentConstantObject = $segmentConstantObject->value;
        if (!empty($segmentConstantObject)) {
            return in_array($postcode, $segmentConstantObject);
        } else {
            return false;
        }
    }

}
