<?php

namespace App\Classes\Modules\PackingLists\Processors;

use App\Classes\Exceptions\ResourceNotFoundException;
use App\Classes\Modules\Companies\Services\FetchesCompanyModule;
use App\Classes\Modules\Orders\Services\FetchesDataFromYDPortal;

use App\Classes\Modules\Orders\Services\FetchesOrder;
use App\Classes\Modules\PackingLists\DataTransferObjects\PackageObject;
use App\Classes\Modules\PackingLists\DataTransferObjects\PackingListObject;
use App\Classes\Modules\PackingLists\Services\FetchesPackingList;
use App\Classes\Modules\Steps\DataTransferObjects\StepsObject;
use App\Classes\Modules\Steps\Services\CreatesStep;
use App\Classes\Modules\Transports\DataTransferObjects\TransportObject;
use App\Classes\Modules\Transports\Services\CreatesTransport;
use App\Classes\Modules\Unity\Processors\ActivateContractProcessor;
use App\Classes\Modules\Unity\Processors\AssignContractEntityProcessor;
use App\Classes\Modules\Unity\Processors\CreateContractEntityProcessor;
use App\Classes\Modules\Unity\Services\CreatesContract;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\OrderRoleTypes;
use App\Classes\ValueObjects\Constants\PackageType;
use App\Classes\ValueObjects\Constants\PackingListType;
use App\Classes\ValueObjects\Constants\TransportType;
use App\Models\Order;
use App\Models\PackingList;
use Carbon\Carbon;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class FetchPackingListsFromYdPortalProcessor
{
    /** @var FetchesDataFromYDPortal */
    private $fetchesDataFRomYDPortal;

    /** @var FetchesOrder */
    private $fetchesOrder;

    /** @var CreatePackingListProcessor */
    private $createPackingListProcessor;

    /** @var CreatePackageProcessor */
    private $createPackageProcessor;

    /** @var CreatesTransport */
    private $createsTransport;

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

    /** @var FetchesCompanyModule */

    private $fetchesCompanyModule;
    /** @var CreatesContract */
    private $unityCreateContract;

    /** @var AssignContractEntityProcessor */
    private $unityAssignContractEntity;

    /** @var CreateContractEntityProcessor */
    private $unityCreateContractEntity;

    /** @var CreatesStep */
    private $createsStep;

    /** @var ActivateContractProcessor */
    private $unityActivateContract;

    /**
     * @param FetchesDataFromYDPortal $fetchesDataFRomYDPortal
     * @param FetchesOrder $fetchesOrder
     * @param CreatePackingListProcessor $createPackingListProcessor
     * @param CreatePackageProcessor $createPackageProcessor
     * @param CreatesTransport $createsTransport
     * @param FetchesPackingList $fetchesPackingList
     * @param FetchesCompanyModule $fetchesCompanyModule
     * @param CreatesContract $unityCreateContract
     * @param AssignContractEntityProcessor $unityAssignContractEntity
     * @param CreateContractEntityProcessor $unityCreateContractEntity
     * @param CreatesStep $createsStep
     * @param ActivateContractProcessor $unityActivateContract
     */
    public function __construct(FetchesDataFromYDPortal $fetchesDataFRomYDPortal, FetchesOrder $fetchesOrder, CreatePackingListProcessor $createPackingListProcessor, CreatePackageProcessor $createPackageProcessor, CreatesTransport $createsTransport, FetchesPackingList $fetchesPackingList, FetchesCompanyModule $fetchesCompanyModule, CreatesContract $unityCreateContract, AssignContractEntityProcessor $unityAssignContractEntity, CreateContractEntityProcessor $unityCreateContractEntity, CreatesStep $createsStep, ActivateContractProcessor $unityActivateContract)
    {
        $this->fetchesDataFRomYDPortal = $fetchesDataFRomYDPortal;
        $this->fetchesOrder = $fetchesOrder;
        $this->createPackingListProcessor = $createPackingListProcessor;
        $this->createPackageProcessor = $createPackageProcessor;
        $this->createsTransport = $createsTransport;
        $this->fetchesPackingList = $fetchesPackingList;
        $this->fetchesCompanyModule = $fetchesCompanyModule;
        $this->unityCreateContract = $unityCreateContract;
        $this->unityAssignContractEntity = $unityAssignContractEntity;
        $this->unityCreateContractEntity = $unityCreateContractEntity;
        $this->createsStep = $createsStep;
        $this->unityActivateContract = $unityActivateContract;
    }


    /**
     * @param Carbon|null $start
     * @param Carbon|null $end
     * @return void
     * @throws GuzzleException
     */
    public function execute(?Carbon $start = null, ?Carbon $end = null)
    {
        $from = $end ? Carbon::today()->diff($end)->days : 0;
        $to = $start ? Carbon::today()->diff($start)->days : 10;

        for ($x = $from; $x <= $to; $x++) {
            try {
                $startDate = Carbon::today()->subDays($x);

                $startLimit = Carbon::parse('01-12-2021');

                if($startDate->isBefore($startLimit)){
                    $startDate = $startLimit;
                }

                $endDate = Carbon::today()->subDays($x - 1);

                $orderRequest = $this->fetchesDataFRomYDPortal->clientRequest('http://www.yd-wl.com/api/GetOrderList.ashx', 'GET', [
                    'begintime' => $startDate->timestamp,
                    'endtime' => $endDate->timestamp,
                ]);

                $rows = $this->fetchesDataFRomYDPortal->getResponseBody($orderRequest);

                if(!$rows->data) continue;

                foreach($rows->data as $row){
                    if(!is_numeric($row->goodcount)) continue;

                    $time_start = microtime(true);

                    $receiveDate = Carbon::parse(substr(preg_replace("/[^0-9]/", "", $row->expressno), 0, 8));

                    $customerno = preg_split('(-|\(|\)|\/)', $row->customerno);

                    $orderNumber = $customerno[array_key_last($customerno)];
                    $allow_contract = true;

                    try {
                        $order = $this->fetchesOrder->execute(['reference' => $orderNumber]);
                    } catch (ResourceNotFoundException $exception) {
                        try {
                            $order = $this->fetchesOrder->execute(['reference' => substr($orderNumber, -9)]);
                        } catch (ResourceNotFoundException $exception) {
                            $order = $this->fetchesCompanyModule->execute(['id' => 1]);
                            $allow_contract = false;
                        }

                    }

                    $packingListReference = $row->expressno;

                    try{
                        $packingList = $this->fetchesPackingList->execute(['reference' => $row->expressno, 'type' => PackingListType::SHIPPING_PACKING_LIST]);

                        $packingList->packages()->delete();
                        $replica = $packingList->packingLists()->where('type', PackingListType::SHIPPING_PACKING_LIST_REPLICA)->first();
                        if($replica) $replica->packages()->delete();

                        $warehouseReceiveList = $this->fetchesPackingList->execute(['reference' => $row->expressno, 'type' => PackingListType::WAREHOUSE_RECEIVE_LIST]);
                        $warehouseReceiveList->packages()->delete();
                        $replica = $warehouseReceiveList->packingLists()->where('type', PackingListType::WAREHOUSE_RECEIVE_LIST_REPLICA)->first();
                        if($replica) $replica->packages()->delete();

                    } catch (ResourceNotFoundException $exception) {

                        if (!$allow_contract) {
                            $appointee_id = 2307;
                        } else {
                            $appointee_id = $order->orderRoles()->where('role_id', '=', OrderRoleTypes::ORIGIN_FREIGHT_FORWARDER)->first()->appointee->id;
                        }

                        $warehouseReceiveObject = new PackingListObject($packingListReference, $appointee_id, PackingListType::WAREHOUSE_RECEIVE_LIST, $allow_contract ? ApprovalStatus::APPROVED : ApprovalStatus::REJECTED);

                        /** @var PackingList $warehouseReceiveList */
                        $warehouseReceiveList = $this->createPackingListProcessor->execute($warehouseReceiveObject, $order);

                        $transportObject = new TransportObject(TransportType::LAND, null, $row->kuaidilist, Carbon::parse($receiveDate), Carbon::parse($receiveDate), $allow_contract ? ApprovalStatus::APPROVED : ApprovalStatus::REJECTED);
                        $transport = $this->createsTransport->execute($transportObject, $warehouseReceiveList);

                        if ($allow_contract) {

                            $contract = $this->unityCreateContract->execute();

                            $contractReference = $contract->hash_id;
                            $contractObligations = $contract->contract_obligation_list;

                            $this->unityActivateContract->execute($contractReference);

                            $supervisorHashId = $order->orderRoles()->where('role_id', '=', OrderRoleTypes::SUPERVISOR)->first()->appointee->unity_hash_id;

                            $supervisorContractEntity = $this->unityCreateContractEntity->execute($contractReference, $supervisorHashId);
                            $order->orderRoles()->where('role_id', '=', OrderRoleTypes::SUPERVISOR)->first()->update(['entity_hash_id' => $supervisorContractEntity->hash_id, 'entity_signature' => $supervisorContractEntity->entity_signature_hash_id]);

                            $importerContractEntity = $this->unityCreateContractEntity->execute($contractReference, $order->orderRoles()->where('role_id', '=', OrderRoleTypes::IMPORTER)->first()->appointee->unity_hash_id);
                            $order->orderRoles()->where('role_id', '=', OrderRoleTypes::IMPORTER)->first()->update(['entity_hash_id' => $importerContractEntity->hash_id, 'entity_signature' => $importerContractEntity->entity_signature_hash_id]);

                            $this->unityAssignContractEntity->execute($supervisorContractEntity->hash_id, $contractObligations);
                        }

                        $packingListObject = new PackingListObject($packingListReference, $appointee_id, PackingListType::SHIPPING_PACKING_LIST, ApprovalStatus::SUSPENDED, !$allow_contract ? null : $contractReference);

                        /** @var PackingList $packingList */
                        $packingList = $this->createPackingListProcessor->execute($packingListObject, $order);

                        if ($allow_contract) {
                            foreach ($contractObligations as $obligation) {
                                $stepObject = new StepsObject($order->orderRoles()->where('role_id', '=', OrderRoleTypes::SUPERVISOR)->first()->appointee->id, $obligation->reference, $obligation->sequence, $obligation->hash_id);
                                $this->createsStep->execute($packingList, $stepObject);
                            }
                        }
                    }

                    foreach($row->deliverysize as $package) {
                        $packageObject = new PackageObject(PackageType::CARTON, $row->goodname, (float) $package->width, (float) $package->height, (float) $package->length, 0, (float) $package->num, ApprovalStatus::APPROVED);
                        $this->createPackageProcessor->execute($packageObject, $warehouseReceiveList);
                        $this->createPackageProcessor->execute($packageObject, $packingList);
                    }

                    if(!count($row->deliverysize)){
                        $measurement = round(((float) $row->volume / (float) $row->goodcount) ** (1/3) * 100, 2);
                        $packageObject = new PackageObject(PackageType::CARTON, $row->goodname, (float) $measurement, (float) $measurement, (float) $measurement, 0, (float) $row->goodcount, ApprovalStatus::APPROVED);
                        $this->createPackageProcessor->execute($packageObject, $warehouseReceiveList);
                        $this->createPackageProcessor->execute($packageObject, $packingList);
                    }

                    $weightCbm = (float) $row->weight / 500;
                    $overWeightCbm = $weightCbm - $packingList->packages()->sum(DB::raw('(width/100) * (height/100) * (length/100) * quantity'));

                    if($overWeightCbm > 0){
                        $measurement = round($overWeightCbm ** (1/3) * 100, 2);
                        $packageObject = new PackageObject(PackageType::OVER_WEIGHT, 'Overweight CBM', (float) $measurement, (float) $measurement, (float) $measurement, 0, 1, ApprovalStatus::APPROVED);
                        $this->createPackageProcessor->execute($packageObject, $packingList);
                    }

                    if($order instanceof Order){
                        $marking = $order->companyModule->inviters()->withPivot('invitee_reference')->first()->pivot->invitee_reference;
                        if(!in_array($marking, ['1290CSW', '8997ITB', '3992WHE', '962LOW', '1152AAT'])){
                            $this->fetchesDataFRomYDPortal->clientRequest('http://www.yd-wl.com/api/confirmsendorder.ashx', 'GET', [
                                'expressno' => $row->expressno
                            ]);
                        }
                    }

                    $time_end = microtime(true);

                    $execution_time = ($time_end - $time_start)/60;

                    echo 'successful => <b>Total Execution Time:</b> '.$execution_time.' Mins<br>';

                }

            } catch (\Exception $exception) {
                Log::debug($exception);
            }
        }



    }
}

