<?php

namespace Database\Seeders;

use App\Classes\Exceptions\AccessForbiddenException;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\Exceptions\RequestValidationException;
use App\Classes\General\Services\GeneratesInitials;
use App\Classes\Modules\Accounts\DataTransferObjects\RegistrationObject;
use App\Classes\Modules\Accounts\Services\CreatesUser;
use App\Classes\Modules\Addresses\DataTransferObjects\AddressObject;
use App\Classes\Modules\Addresses\Services\CreatesAddress;
use App\Classes\Modules\Companies\DataTransferObjects\CompanyConnectionObject;
use App\Classes\Modules\Companies\DataTransferObjects\CompanyModuleObject;
use App\Classes\Modules\Companies\DataTransferObjects\CompanyObject;
use App\Classes\Modules\Companies\DataTransferObjects\EmploymentObject;
use App\Classes\Modules\Companies\Processors\AssignEmployeeProcessor;
use App\Classes\Modules\Companies\Services\ApprovesCompanyConnection;
use App\Classes\Modules\Companies\Services\CreatesCompany;
use App\Classes\Modules\Companies\Services\CreatesCompanyConnection;
use App\Classes\Modules\Companies\Services\CreatesCompanyModule;
use App\Classes\Modules\Companies\Services\GeneratesUniqueAccountNumber;
use App\Classes\Modules\Contacts\DataTransferObjects\ContactObject;
use App\Classes\Modules\Contacts\Services\CreatesContact;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\Modules\Documents\Services\CreatesDocument;
use App\Classes\Modules\Documents\Services\CreatesFiles;
use App\Classes\Modules\Orders\Processors\CreateOrderProcessor;
use App\Classes\Modules\Orders\Services\GeneratesOrderNumber;
use App\Classes\Modules\PackingLists\DataTransferObjects\ContainerObject;
use App\Classes\Modules\PackingLists\DataTransferObjects\PackageObject;
use App\Classes\Modules\PackingLists\DataTransferObjects\PackingListObject;
use App\Classes\Modules\PackingLists\Processors\CreateContainerProcessor;
use App\Classes\Modules\PackingLists\Processors\CreatePackageProcessor;
use App\Classes\Modules\PackingLists\Processors\CreatePackingListProcessor;
use App\Classes\Modules\Schedules\DataTransferObjects\ScheduleObject;
use App\Classes\Modules\Schedules\Services\CreatesSchedule;
use App\Classes\Modules\Transports\DataTransferObjects\TransportObject;
use App\Classes\Modules\Transports\Services\CreatesTransport;
use App\Classes\ValueObjects\Constants\AddressType;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\BusinessType;
use App\Classes\ValueObjects\Constants\CompanyType;
use App\Classes\ValueObjects\Constants\ContainerTypes;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Classes\ValueObjects\Constants\PackageType;
use App\Classes\ValueObjects\Constants\PackingListType;
use App\Classes\ValueObjects\Constants\RoleTypes;
use App\Classes\ValueObjects\Constants\TransportType;
use App\Models\Address;
use App\Models\Company;
use App\Models\CompanyModule;
use App\Models\Container;
use App\Models\Document;
use App\Models\PackingList;
use App\Models\Transport;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Database\Seeder;

use Faker\Generator as Faker;

class DummyDataSeeder extends Seeder
{

    /** @var Faker */
    public $faker;

    /** @var CreatesUser */
    public $createsUser;

    /** @var GeneratesUniqueAccountNumber */
    public $generatesUniqueAccountNumber;

    /** @var CreatesCompany */
    public $createsCompany;

    /** @var CreatesCompanyModule */
    public $createsCompanyModule;

    /** @var CreatesContact */
    public $createsContact;

    /** @var CreatesAddress */
    public $createsAddress;

    /** @var CreateContainerProcessor */
    public $createContainerProcessor;

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

    /** @var CreatesSchedule */
    public $createsSchedule;

    /** @var CreatesCompanyConnection */
    public $createsCompanyConnection;

    /** @var ApprovesCompanyConnection */
    public $approvesCompanyConnection;

   /** @var AssignEmployeeProcessor */
    public $assignEmployeeProcessor;

   /** @var CreatesDocument */
    public $createsDocument;

   /** @var CreatesFiles */
    public $createsFiles;

    /** @var GeneratesOrderNumber */
    public $generatesOrderNumber;

    /** @var CreateOrderProcessor */
    public $createOrderProcessor;

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

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

    /**
     * @param Faker $faker
     * @param CreatesUser $createsUser
     * @param GeneratesUniqueAccountNumber $generatesUniqueAccountNumber
     * @param CreatesCompany $createsCompany
     * @param CreatesCompanyModule $createsCompanyModule
     * @param CreatesContact $createsContact
     * @param CreatesAddress $createsAddress
     * @param CreateContainerProcessor $createContainerProcessor
     * @param CreatesTransport $createsTransport
     * @param CreatesSchedule $createsSchedule
     * @param CreatesCompanyConnection $createsCompanyConnection
     * @param ApprovesCompanyConnection $approvesCompanyConnection
     * @param AssignEmployeeProcessor $assignEmployeeProcessor
     * @param CreatesDocument $createsDocument
     * @param CreatesFiles $createsFiles
     * @param GeneratesOrderNumber $generatesOrderNumber
     * @param CreateOrderProcessor $createOrderProcessor
     * @param CreatePackingListProcessor $createPackingListProcessor
     * @param CreatePackageProcessor $createPackageProcessor
     */
    public function __construct(Faker $faker, CreatesUser $createsUser, GeneratesUniqueAccountNumber $generatesUniqueAccountNumber, CreatesCompany $createsCompany, CreatesCompanyModule $createsCompanyModule, CreatesContact $createsContact, CreatesAddress $createsAddress, CreateContainerProcessor $createContainerProcessor, CreatesTransport $createsTransport, CreatesSchedule $createsSchedule, CreatesCompanyConnection $createsCompanyConnection, ApprovesCompanyConnection $approvesCompanyConnection, AssignEmployeeProcessor $assignEmployeeProcessor, CreatesDocument $createsDocument, CreatesFiles $createsFiles, GeneratesOrderNumber $generatesOrderNumber, CreateOrderProcessor $createOrderProcessor, CreatePackingListProcessor $createPackingListProcessor, CreatePackageProcessor $createPackageProcessor)
    {
        $this->faker = $faker;
        $this->createsUser = $createsUser;
        $this->generatesUniqueAccountNumber = $generatesUniqueAccountNumber;
        $this->createsCompany = $createsCompany;
        $this->createsCompanyModule = $createsCompanyModule;
        $this->createsContact = $createsContact;
        $this->createsAddress = $createsAddress;
        $this->createContainerProcessor = $createContainerProcessor;
        $this->createsTransport = $createsTransport;
        $this->createsSchedule = $createsSchedule;
        $this->createsCompanyConnection = $createsCompanyConnection;
        $this->approvesCompanyConnection = $approvesCompanyConnection;
        $this->assignEmployeeProcessor = $assignEmployeeProcessor;
        $this->createsDocument = $createsDocument;
        $this->createsFiles = $createsFiles;
        $this->generatesOrderNumber = $generatesOrderNumber;
        $this->createOrderProcessor = $createOrderProcessor;
        $this->createPackingListProcessor = $createPackingListProcessor;
        $this->createPackageProcessor = $createPackageProcessor;
    }


    /**
     * Run the database seeds.
     *
     * @return void
     * @throws MalformedRequestException
     * @throws AccessForbiddenException
     * @throws RequestValidationException
     */
    public function run()
    {
        // Local Development Default Password Hash
        $password = '123456abcabc';

        // At the moment we only have 3 different user roles:
        // RoleTypes::SUPER_ADMIN : Full access, at the moment is not attached to a company but should be in the future.
        // RoleTypes::ADMIN : Full access except for some sensitive features that require higher level of approval, at the moment is not attached to a company but should be in the future.
        // RoleTypes::USER : This is the customer, can only access their own orders only, must be attached to a company.

        // User Status
        // ApprovalStatus::PENDING_VERIFICATION : This should be the default status before the user verifies their email status, but currently this is not being implemented.
        // ApprovalStatus::APPROVED : This is the status of users with verified emails.
        // ApprovalStatus::SUSPENDED : This is the status if the users is blocked from the system, but currently this is not being implemented.


        // =============================================== //
        //              Create CIEF Entities               //
        // =============================================== //

        // create super admin
        $userObject = new RegistrationObject($this->faker->name, 'super_admin@izyim.com', $password, $password,RoleTypes::SUPER_ADMIN, ApprovalStatus::APPROVED);
        $this->createsUser->execute($userObject);

        // create admin
        $userObject = new RegistrationObject($this->faker->name, 'admin@izyim.com', $password, $password,RoleTypes::ADMIN, ApprovalStatus::APPROVED);
        $this->createsUser->execute($userObject);

        // create CIEF
        $company_object = new CompanyObject('CIEF Worldwide Sdn Bhd', 'CIEF',CompanyType::COMPANY_BUSINESS,ApprovalStatus::APPROVED);
        /** @var Company $company */
        $company = $this->createsCompany->execute($company_object);

        $companyModuleObject = new CompanyModuleObject('CIEF Worldwide Sdn Bhd', 'CIEF', '', '', BusinessType::FREIGHT_FORWARDER, ApprovalStatus::APPROVED);
        /** @var CompanyModule $CIEF */
        $CIEF = $this->createsCompanyModule->execute($company, $companyModuleObject);

        // =============================================== //
        //              Create Supplier Entities           //
        // =============================================== //
        // supplier entities consist of 2 type of company module [BusinessType::FREIGHT_FORWARDER, BusinessType::FREIGHT_FORWARDER, BusinessType::WAREHOUSE]
        // in this use case we are creating 3 supplier, with each supplier having 6 company modules, 1 BusinessType::FREIGHT_FORWARDER and 5 BusinessType::WAREHOUSE. 1 warehouse for each location.

        for ($i = 1; $i <= 3; $i++) {
            $supplierName = $this->faker->company;
            $supplierReference = $this->faker->bothify('??-????');

            $company_object = new CompanyObject($supplierName, $supplierReference,CompanyType::COMPANY_BUSINESS,ApprovalStatus::APPROVED);
            /** @var Company $company */
            $company = $this->createsCompany->execute($company_object);

            $companyModuleObject = new CompanyModuleObject($supplierName, $supplierReference, '', '', BusinessType::FREIGHT_FORWARDER, ApprovalStatus::APPROVED);
            $this->createsCompanyModule->execute($company, $companyModuleObject);

            // create supplier warehouses
            foreach(['Guangzhou', 'Yiwu', 'Klang', 'Sabah', 'Sarawak'] as $name){

                $warehouseReference = '';
                $isChina = false;

                switch($name) {
                    case 'Guangzhou': $warehouseReference = 'GZ-V0'.$i; $isChina = true; break;
                    case 'Yiwu': $warehouseReference = 'YY-V0'.$i; $isChina = true; break;
                    case 'Klang': $warehouseReference = 'KL-V0'.$i; break;
                    case 'Sabah': $warehouseReference = 'SB-V0'.$i; break;
                    case 'Sarawak':$warehouseReference = 'SRW-V0'.$i; break;
                }

                $companyModuleObject = new CompanyModuleObject($name, $warehouseReference, '', '', BusinessType::WAREHOUSE, ApprovalStatus::APPROVED);
                /** @var CompanyModule $companyModule */
                $companyModule = $this->createsCompanyModule->execute($company, $companyModuleObject);

                $address = new AddressObject( $this->faker->streetAddress, $this->faker->streetAddress, $isChina ? 2 : 1, $isChina ? 35 : 15, $isChina ? 633 : 412, $isChina ? 510450 : 41400, AddressType::DELIVERY, ApprovalStatus::APPROVED);
                $this->createsAddress->execute($companyModule, $address);

                $contact = new ContactObject($this->faker->name, $this->faker->phoneNumber, '', '');
                $this->createsContact->execute($companyModule, $contact);
            }
        }

        // =============================================== //
        //                 Create Containers               //
        // =============================================== //
        // Containers belongs to a warehouse, a container will include many packing lists that will be shipped together
        // when a container is loaded it will be attached to transport arrangement and a schedule to indicate when the container will depart (ETD) and when it will arrive (ETA)
        // the shipping schedule can be interrupted which will result in a delay for the (ETA)

        for($containerLoop=1; $containerLoop <= rand(15, 60); $containerLoop++) {
            $loadingDate = Carbon::today()->addDays(10)->subDays(rand(1, 30));
            $containerObject = new ContainerObject($this->faker->bothify('??-######'), '', '', ContainerTypes::FORTY_FEET_DRY_CONTAINER, $loadingDate, ApprovalStatus::PENDING_VERIFICATION);

            // randomly selects an origin warehouse
            $warehouse = CompanyModule::where('reference', $this->faker->randomElement(['GZ-V01', 'GZ-V02', 'GZ-V03', 'YY-V01', 'YY-V03', 'YY-V03']))->first();

            /** @var Container $container */
            $container = $this->createContainerProcessor->execute($containerObject, $warehouse);

            $departureDate = $loadingDate->copy()->addDays(rand(0, 3));
            $arrivalDate = $departureDate->copy()->addDays(7);

            // Container Shipping Schedule
            $transportObject = new TransportObject(TransportType::SEA, null, null,  $departureDate, null, ApprovalStatus::APPROVED);

            /** @var Transport $transport */
            $transport = $this->createsTransport->execute($transportObject, $container);
            $this->createsSchedule->execute($transport, new ScheduleObject($departureDate, $arrivalDate, ApprovalStatus::APPROVED));

            // randomly reschedule some containers
            if($this->faker->numberBetween(0, 1)){
                $delayedArrivalDate = $departureDate->copy()->addDay();

                // reschedule shipment
                for($scheduleLoop=1; $scheduleLoop <= rand(1, 5); $scheduleLoop++) {
                    $delayedArrivalDate = $delayedArrivalDate->copy()->addDays(rand(0, 5));
                    $transport->schedules()->update(['status' => ApprovalStatus::EXPIRED]);
                    $this->createsSchedule->execute($transport, new ScheduleObject($departureDate, $delayedArrivalDate, ApprovalStatus::APPROVED));
                }
            }


        }


        // =============================================== //
        //                 Create Customer                 //
        // =============================================== //
        // 1. create user
        // 2. create company

        // 3. create Company Module
        // 4. Declare Company Relationship with CIEF

        // 5. Attach Employee
        // 6. create contact
        // 7. create Address

        // 8. identification verification

        // =============================================== //
        //                 Order Workflow                  //
        // =============================================== //

        // 9. Create Shipping Label
        // 10. Receive Goods at warehouse (our record match supplier record + detect any problems)
        // 11. Load into container for shipping (our record match supplier record + detect any problems)
        // 12. generate invoice (add billing address + double check price)
        // 13. additional charges
        // 12. Un-stuffing Container (our record match supplier record + detect any problems)
        // 13. last mile delivery (our record match supplier record + on hold)

        // generate random number of users
        for($userLoop=1; $userLoop <= rand(20, 50); $userLoop++) {

            // === //
            //  1  // ========== //
            //    Create user    //
            // ================= //
            $customerName = $this->faker->name;
            $customerEmail = $this->faker->email;
            $userObject = new RegistrationObject($customerName, $customerEmail, $password, $password, RoleTypes::USER, ApprovalStatus::APPROVED);
            /** @var User $user */
            $user = $this->createsUser->execute($userObject);

            // === //
            //  2  // ========== //
            //  Create Company   //
            // ================= //

            // CompanyTypes
            // CompanyType::COMPANY_BUSINESS : For SME Business Entities and requires SSM for identity verification.
            // CompanyType::PERSONAL_BUSINESS : For Personal Entities and requires IC for identity verification, and the company name will follow the customer name in this case.

            // Company Status
            // ApprovalStatus::APPROVED : This is the default status of registered company.
            // ApprovalStatus::SUSPENDED : This is the status if the company is blocked from releasing packages from warehouse due to pending verification.

            $isCompany = $this->faker->numberBetween(0, 1);
            $companyName = $isCompany ? $this->faker->company : $customerName;
            $companyAccountNumber = $this->generatesUniqueAccountNumber->execute();
            $company_object = new CompanyObject($companyName,
                $companyAccountNumber,
                $isCompany ? CompanyType::COMPANY_BUSINESS : CompanyType::PERSONAL_BUSINESS,
                ApprovalStatus::APPROVED);

            /** @var Company $company */
            $company = $this->createsCompany->execute($company_object);

            // === //
            //  3  // =================//
            //  Create Company Module  //
            // ========================//
            // Company Module are sub entities of the company, and is used to declare what kind of business role this sub entity is in "Like Departments"
            // one company can have multiple company module if they play multiple roles in the process:
            // BusinessType::IMPORTER :
            // BusinessType::FREIGHT_FORWARDER :
            // BusinessType::WAREHOUSE :
            $companyModuleObject = new CompanyModuleObject($companyName, $companyAccountNumber, '', '', BusinessType::IMPORTER, ApprovalStatus::APPROVED);
            /** @var CompanyModule $companyModule */
            $companyModule = $this->createsCompanyModule->execute($company, $companyModuleObject);

            // === //
            //  4  // ================================== //
            //  Declare Company Relationship with CIEF   //
            // ========================================= //
            // In order for 2 entities to work together they must both agree to the relationship "Like facebook friend requests".
            // But at the moment since CIEF is the only Entity that has a direct relationship with importers we just declare this relationship by default.
            $connectionObject = new CompanyConnectionObject($companyModule, $CIEF, mt_rand(1000, 9999) . (new GeneratesInitials())->name($companyName)->length(3)->generate(), 'CIEF');
            $connection = $this->createsCompanyConnection->execute($connectionObject);
            $this->approvesCompanyConnection->execute($connection);

            // === //
            //  5  // ===========//
            //  Attach Employee  //
            // ==================//
            // employees are attached to company modules not companies, because an employee maybe working for one or many "Departments".
            $Object = new EmploymentObject($companyModule, $user);
            $this->assignEmployeeProcessor->execute($Object);

            // === //
            //  6  // =========== //
            //  Create Contact   //
            // ================= //
            // Contacts uses eloquent polymorphic relationship to declare its owner. and for this use case it will be attached to the company not the company module.
            $contactObject = new ContactObject($customerName, $this->faker->phoneNumber, $customerEmail, null);
            $this->createsContact->execute($company, $contactObject);

            // === //
            //  7  // ========== //
            //  Create Address   //
            // ================= //
            // Addresses uses eloquent polymorphic relationship to declare its owner. and for this use case it will be attached to the company module.
            // an address has at least 1 contact for the PIC.
            // there are 2 type of address we use:
            // AddressType::DELIVERY : for the orders delivery address
            // AddressType::BILLING : for the invoice billing address

            foreach ([AddressType::DELIVERY, AddressType::BILLING] as $type) {
                // create delivery address
                $addressObject = new AddressObject($this->faker->streetAddress, '', 1, $this->faker->numberBetween(1, 15), $this->faker->numberBetween(1, 442), $this->faker->postcode, $type, ApprovalStatus::APPROVED);
                /** @var Address $address */
                $address = $this->createsAddress->execute($companyModule, $addressObject);

                // create delivery address contact for person in charge
                $contact = new ContactObject($this->faker->name, $this->faker->phoneNumber, '', '');
                $this->createsContact->execute($address, $contact);

            }


            // === //
            //  8  // ======================= //
            //  identification verification   //
            // ============================== //
            // please refer to company types section for more insight
            $object = new DocumentObject($isCompany ? DocumentType::SSM_REGISTRATION : DocumentType::IDENTITY_CARD, ['data:image/png;base64,[encoded data, 4548 characters]'],
                $isCompany ? $this->faker->bothify('SSM-#######') : $this->faker->bothify('############'), ApprovalStatus::APPROVED, 'identifications');
            /** @var Document $document */
            $document = $this->createsDocument->execute($company, $object);
            $this->createsFiles->execute($document, $object);

            // generate random number of shipping labels
            for($orderLoop=1; $orderLoop <= rand(1, 30); $orderLoop++) {

                // === //
                //  9  // ================= //
                //  Create Shipping Label   //
                // ======================== //
                // The shipping label is stored in the warehouse as an order entity.
                // the shipping label comes with the warehouse address which will receive the shipment, and
                // the shipping label comes with a qr code that helps the supplier warehouse to identify
                // the customer that owns the order and the delivery address.
                // A shipping label can be re-used, and each batch that arrives at the supplier warehouse
                // is referred to as a packing list. more on this later.

                $orderNumber = $this->generatesOrderNumber->execute();
                $deliveryAddress = $company->addresses()->where('type', AddressType::DELIVERY);

                // randomly selects an origin warehouse
                $originWarehouse = CompanyModule::where('reference', $this->faker->randomElement(['GZ-V01', 'GZ-V02', 'GZ-V03', 'YY-V01', 'YY-V03', 'YY-V03']))->first();

                // creates shipping label
                $order = $this->createOrderProcessor->execute($company, $originWarehouse, $address, $orderNumber);

                // Packing List Types
                // A packing list is a group of packages, there are many type of packing lists, but currently we are only using 4 types of packing list:
                // PackingListType::WAREHOUSE_RECEIVE_LIST : group of packages that has arrived to the warehouse with the original measurements provided by the warehouse.
                // PackingListType::WAREHOUSE_RECEIVE_LIST_REPLICA : group of packages that has arrived to the warehouse with the modified measurements. e.g. add 1 cm to width, length, and height.
                // PackingListType::SHIPPING_PACKING_LIST : group of packages that has packed into the container with the original measurements provided by the warehouse.
                // PackingListType::SHIPPING_PACKING_LIST_REPLICA : group of packages that has packed into the container with the modified measurements. e.g. add 1 cm to width, length, and height.

                if($orderLoop !== 1) {
                    // generate random number of packing lists
                    for($packingListLoop=1; $packingListLoop <= rand(2, 4); $packingListLoop++) {

                        // ==== //
                        //  10  // ===================== //
                        //  Receive Goods at warehouse   //
                        //    WAREHOUSE_RECEIVE_LIST     //
                        // ============================= //
                        // When a batch of goods arrives to the warehouse a WAREHOUSE_RECEIVE_LIST packing list is created.
                        // A transport arrangement is attached to WAREHOUSE_RECEIVE_LIST packing list to represent the trip
                        // from supplier to the warehouse and is used to record the arrival date.


                        // create random packages
                        $packages = [];
                        for ($packagesLoop = 1; $packagesLoop <= rand(2, 5); $packagesLoop++) {
                            $packages[] = new PackageObject(PackageType::CARTON, $this->faker->word, (float)$this->faker->numberBetween(1, 100), (float)$this->faker->numberBetween(1, 100), (float)$this->faker->numberBetween(1, 100), 0, (float)$this->faker->numberBetween(1, 10), ApprovalStatus::APPROVED);
                        }

                        $packingListReference = $this->faker->bothify('???#########');

                        $warehouseReceiveObject = new PackingListObject($packingListReference, $originWarehouse->id, PackingListType::WAREHOUSE_RECEIVE_LIST, ApprovalStatus::APPROVED);

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

                        // attach packages to packing list
                        foreach ($packages as $package) {
                            // Attach packages to both WAREHOUSE_RECEIVE_LIST & WAREHOUSE_RECEIVE_LIST_REPLICA
                            $this->createPackageProcessor->execute($package, $warehouseReceiveList);
                        }

                        // random arrival date in the last 30 days
                        $receiveDate = Carbon::today()->subDays(rand(10, 45));

                        // create transport arrangement for the trip from manufacturer to warehouse (to record drop off/ receive date)
                        $transportObject = new TransportObject(TransportType::LAND, null, $this->faker->bothify('??#########'), $receiveDate, $receiveDate, ApprovalStatus::APPROVED);
                        $this->createsTransport->execute($transportObject, $warehouseReceiveList);

                        // ==== //
                        //  11  // =========================== //
                        //  Load into container for shipping   //
                        //        SHIPPING_PACKING_LIST        //
                        // =================================== //
                        // When a batch of goods is loaded in a container to prepare for shipping a SHIPPING_PACKING_LIST packing list is created.
                        // A SHIPPING_PACKING_LIST is a copy of the WAREHOUSE_RECEIVE_LIST in most cases.

                        $shippingPackingListObject = new PackingListObject($packingListReference, $originWarehouse->id, PackingListType::SHIPPING_PACKING_LIST, ApprovalStatus::APPROVED);
                        // Creates SHIPPING_PACKING_LIST & SHIPPING_PACKING_LIST_REPLICA
                        /** @var PackingList $warehouseReceiveList */
                        $shippingPackingList = $this->createPackingListProcessor->execute($shippingPackingListObject, $order);

                        // attach packages to packing list
                        foreach ($packages as $package) {
                            // Attach packages to both SHIPPING_PACKING_LIST & SHIPPING_PACKING_LIST_REPLICA
                            $this->createPackageProcessor->execute($package, $shippingPackingList);
                        }

                        // select random container
                        $container = $this->faker->randomElement(Container::whereDate('loading_date', '>=', $receiveDate)->get());

                        // attach packing list to container
                        $container->packingLists()->attach($shippingPackingList);

                        if ($container->status !== ApprovalStatus::COMPLETED) {
                            // ==== //
                            //  12  // ================================= //
                            //           Un-stuffing Container           //
                            //  container arrived to malaysia warehouse  //
                            // ========================================= //
                            // when a container arrives to malaysia's warehouse it is un-stuffed, the un-stuffing date will update
                            // the container's transport drop off date and to container status is update to ApprovalStatus::COMPLETE

                            $containerTransport = $container->transports()->first();
                            $schedule = $containerTransport->schedules()->where('status', ApprovalStatus::APPROVED)->first();

                            // container Has arrived
                            if ($schedule->eta->isPast()) {
                                // update transport drop date
                                $containerTransport->drop_date = $schedule->eta;
                                $containerTransport->save();

                                // update container status to complete
                                $container->status = ApprovalStatus::COMPLETED;
                                $container->save();

                                // ==== //
                                //  13  // ================ //
                                //    Last Mile Delivery    //
                                // ======================== //
                                // after the container is un-stuffed, the malaysian warehouse will arrange for delivery.
                                // to schedule the delivery we will need to attach a transport arrangement with a schedule to the SHIPPING_PACKING_LIST

                                $isDelivered = rand(0, 1);
                                if ($isDelivered) {
                                    $deliveryDate = $schedule->eta->copy()->addDays(rand(1, 5));
                                    $transportObject = new TransportObject(TransportType::LAND, null, null, $deliveryDate, $deliveryDate, ApprovalStatus::APPROVED);
                                    /** @var Transport $transport */
                                    $transport = $this->createsTransport->execute($transportObject, $shippingPackingList);
                                    $this->createsSchedule->execute($transport, new ScheduleObject($deliveryDate, $deliveryDate, ApprovalStatus::APPROVED));
                                }
                            }
                        }
                    }
                }


            }


            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //
            //                Missing Functions                //
            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //
            // 1. on hold shipments
            // 2. invoices & payments
            // 3. overweight cbm
        }

    }

}
