<?php

/**
 * Consignment Form Model – Palletline Module
 *
 * @category    PortalPeople
 * @package     Palletline
 * @author      Portal People Development Team
 * @copyright   Copyright (c) 2026 Portal People (https://portalpeople.co.uk)
 */

namespace PortalPeople\palletline\models;

use yii\base\Model;

/**
 * ConsignmentForm
 *
 * Validates and builds the Palletline API manifest JSON payload.
 *
 * API endpoint:  POST https://api.palletline.com/createconsignment
 * Confirm=yes  → consignment sent directly to depot for processing.
 * Confirm=no   → consignment posted to customer account for manual review.
 *
 * Consignment types:  D=Delivery, C=Collection, 3=Third Party
 * Service codes:      A=Premium Next Day, B=Economy, and others (see AvailableServices API).
 * Bill unit types:    FP=Full (1200kg), LP=Light (750kg), HP=Half (500kg),
 *                     QP=Quarter (250kg), MQP=Mini Quarter (150kg),
 *                     EPL=Euro Pallet (500kg), XLP=XL Pallet (1200kg)
 */
class ConsignmentForm extends Model
{
    // -------------------------------------------------------------------------
    // Consignment core
    // -------------------------------------------------------------------------

    /** @var string Consignment type: D=Delivery, C=Collection, 3=ThirdParty. */
    public $consignmentType = 'D';

    /** @var string Customer-supplied ID (for batch tracking). Maps to ImportID. */
    public $importId;

    /** @var string Customer consignment number (required – unique per consignment). */
    public $consignmentNumber;

    /** @var string Additional customer reference. */
    public $reference;

    /** @var int Number of pallet lifts (total number of pallets). */
    public $lifts = 1;

    /** @var int Total weight in kg (positive integer). */
    public $weight;

    /** @var bool Handball service required (contents broken down for delivery). */
    public $handball = false;

    /** @var bool Tail lift service required (max 750kg). */
    public $tailLift = false;

    /** @var string|int RSA insurance value (£ per 1000kg). Leave blank for account defaults. */
    public $insurance;

    // -------------------------------------------------------------------------
    // Book-in (required if bookInRequest = true)
    // -------------------------------------------------------------------------

    /** @var bool Book-in service required before delivery. */
    public $bookInRequest = false;

    /** @var string Book-in instructions (required if bookInRequest). */
    public $bookInInstructions;

    /** @var string Book-in reference (required if bookInRequest). */
    public $bookInReference;

    /** @var string Book-in contact name (required if bookInRequest). */
    public $bookInContactName;

    /** @var string Book-in contact phone (required if bookInRequest). */
    public $bookInContactPhone;

    /** @var string Book-in email address. */
    public $bookInEmailAddress;

    // -------------------------------------------------------------------------
    // Delivery notes & dates
    // -------------------------------------------------------------------------

    /** @var string Special delivery instructions (max 255 chars). */
    public $manifestNote;

    /** @var string Collection date YYYY-MM-DD (only for collection service). */
    public $collectionDate;

    /** @var string Requested delivery date YYYY-MM-DD (available on economy service). */
    public $deliveryDate;

    /** @var string Requested delivery time HH:MM:SS (only for timed service). */
    public $deliveryTime;

    // -------------------------------------------------------------------------
    // Service
    // -------------------------------------------------------------------------

    /** @var string Service code (e.g. A=Next Day Premium, B=Economy). */
    public $serviceCode = 'B';

    /** @var string Service surcharge code. */
    public $serviceSurcharge = 'B';

    // -------------------------------------------------------------------------
    // Delivery address
    // -------------------------------------------------------------------------

    public $deliveryContactName;
    public $deliveryTelephone;
    public $deliveryCompanyName;
    public $deliveryLine;
    public $deliveryLine2;
    public $deliveryTown;
    public $deliveryCounty;
    public $deliveryPostCode;
    public $deliveryCountry = 'GB';
    public $collectionEmail;

    public $collectionContactName;
    public $collectionTelephone;
    public $collectionCompanyName;
    public $collectionLine;
    public $collectionLine2;
    public $collectionTown;
    public $collectionCounty;
    public $collectionPostCode;
    public $collectionCountry = 'GB';

    // -------------------------------------------------------------------------
    // Bill unit
    // -------------------------------------------------------------------------

    /** @var string Pallet type: FP, LP, HP, QP, MQP, EPL, XLP. */
    public $billUnitType = 'LP';

    /** @var int Number of billing units. */
    public $billUnitAmount = 1;

    // -------------------------------------------------------------------------
    // Notifications (optional)
    // -------------------------------------------------------------------------

    /**
     * @var string[] Notification groups:
     *   1=Scanned out hub, 2=Scanned in hub, 3=Out for delivery,
     *   4=Delivered, 5=ETA details, 6=Image available,
     *   7=Scheduled AM, 8=Scheduled PM.
     */
    public $notificationSysGroups = [];

    /** @var string SMS number for ETA notifications. */
    public $notificationSms;

    /** @var string Notification email (up to 2 total can be provided). */
    public $notificationEmail;

    /** @var string Second notification email. */
    public $notificationEmail2;

    // -------------------------------------------------------------------------
    // Products (optional)
    // -------------------------------------------------------------------------

    /**
     * @var array Products array – each entry has keys: description, quantity, code.
     *   e.g. [['description' => 'Part A', 'quantity' => 2, 'code' => 'PA001'], ...]
     */
    public $products = [];

    // -------------------------------------------------------------------------

    /** @inheritdoc */
    public function rules(): array
    {
        return [
            // Core consignment
            [['consignmentType'], 'in', 'range' => ['D', 'C', '3']],
            [['consignmentNumber', 'weight'], 'required'],
            [['lifts'], 'integer', 'min' => 1],
            [['billUnitAmount'], 'integer', 'min' => 1],
            [['weight'], 'integer', 'min' => 1],
            [['handball', 'tailLift', 'bookInRequest'], 'boolean'],
            [['importId', 'consignmentNumber', 'reference'], 'string', 'max' => 100],
            [['manifestNote'], 'string', 'max' => 255],

            // Service
            [['serviceCode', 'serviceSurcharge'], 'required'],
            [['serviceCode', 'serviceSurcharge'], 'string', 'max' => 10],

            // Delivery address
            [[
                'deliveryContactName',
                'deliveryTelephone',
                'deliveryCompanyName',
                'deliveryLine',
                'deliveryTown',
                'deliveryPostCode',
            ], 'required'],
            [['deliveryContactName', 'deliveryCompanyName'], 'string', 'max' => 50],
            [['deliveryLine', 'deliveryLine2', 'deliveryTown', 'deliveryCounty'], 'string', 'max' => 35],
            [['deliveryTelephone'], 'string', 'max' => 20],
            [['deliveryPostCode'], 'string', 'max' => 10],
            [['deliveryCountry'], 'string', 'length' => 2],

            // Bill unit
            [['billUnitType'], 'in', 'range' => ['FP', 'LP', 'HP', 'QP', 'MQP', 'EPL', 'XLP']],

            // Notifications
            [['notificationEmail', 'notificationEmail2'], 'email'],
            [['notificationSms'], 'string', 'max' => 20],
            [['notificationSysGroups'], 'required', 'when' => function ($model) {
                return !empty($model->notificationSms)
                    || !empty($model->notificationEmail)
                    || !empty($model->notificationEmail2);
            }, 'whenClient' => "function(attribute, value) {\n"
                . "  return $('#consignmentform-notificationsms').val().trim() !== ''\n"
                . "    || $('#consignmentform-notificationemail').val().trim() !== ''\n"
                . "    || $('#consignmentform-notificationemail2').val().trim() !== '';\n"
                . "}"],

            // Dates
            [['collectionDate', 'deliveryDate'], 'date', 'format' => 'php:Y-m-d'],
            [
                ['deliveryTime'],
                'match',
                'pattern' => '/^\d{2}:\d{2}:\d{2}$/',
                'message' => 'Time must be in HH:MM:SS format.',
            ],

            // Book-in conditionals
            [
                [
                    'bookInInstructions',
                    'bookInReference',
                    'bookInContactName',
                    'bookInContactPhone',
                    'bookInEmailAddress',
                ],
                'required',
                'when' => function ($model) {
                    return (bool) $model->bookInRequest;
                },
                'whenClient' => "function(attribute, value) {\n"
                    . "  return $('#consignmentform-bookinrequest').is(':checked');\n"
                    . '}',
            ],

            // Optional text fields
            [['importId', 'reference', 'collectionDate', 'deliveryDate', 'deliveryTime', 'manifestNote',
              'bookInInstructions', 'bookInReference', 'bookInContactName', 'bookInContactPhone',
              'bookInEmailAddress', 'notificationSms', 'notificationEmail', 'notificationEmail2',
              'deliveryLine2', 'deliveryCounty', 'insurance'], 'default', 'value' => ''],
        ];
    }

    /** @inheritdoc */
    public function attributeLabels(): array
    {
        return [
            'consignmentType'      => 'Consignment Type',
            'importId'             => 'Import ID (optional)',
            'consignmentNumber'    => 'Consignment Number',
            'reference'            => 'Customer Reference',
            'lifts'                => 'Number of Lifts (Pallets)',
            'weight'               => 'Total Weight (kg)',
            'handball'             => 'Handball Service Required',
            'tailLift'             => 'Tail Lift Service Required',
            'insurance'            => 'Insurance (£ per 1000kg)',
            'bookInRequest'        => 'Book-In Service Required',
            'bookInInstructions'   => 'Book-In Instructions',
            'bookInReference'      => 'Book-In Reference',
            'bookInContactName'    => 'Book-In Contact Name',
            'bookInContactPhone'   => 'Book-In Contact Phone',
            'bookInEmailAddress'   => 'Book-In Email Address',
            'manifestNote'         => 'Delivery Instructions (max 255 chars)',
            'collectionDate'       => 'Collection Date (YYYY-MM-DD)',
            'deliveryDate'         => 'Delivery Date (YYYY-MM-DD)',
            'deliveryTime'         => 'Delivery Time (HH:MM:SS)',
            'serviceCode'          => 'Service Code',
            'serviceSurcharge'     => 'Surcharge Code',
            'deliveryContactName'  => 'Contact Name',
            'deliveryTelephone'    => 'Telephone',
            'deliveryCompanyName'  => 'Company Name',
            'deliveryLine'         => 'Address Line 1',
            'deliveryLine2'        => 'Address Line 2',
            'deliveryTown'         => 'Town',
            'deliveryCounty'       => 'County',
            'deliveryPostCode'     => 'Post Code',
            'deliveryCountry'      => 'Country (ISO Code)',
            'billUnitType'         => 'Pallet Type',
            'billUnitAmount'       => 'Number of Billing Units',
            'notificationSysGroups' => 'Notification Types',
                        'notificationSysGroups' => 'Notification Types',
            'notificationSms'      => 'SMS Number (ETA only)',
            'notificationEmail'    => 'Email Address 1',
            'notificationEmail2'   => 'Email Address 2',
            'products'             => 'Products',
        ];
    }

    // -------------------------------------------------------------------------

    /**
     * Build the full Manifest array for the Palletline API.
     *
     * @param bool $confirm  If true, Confirm=yes (auto-confirm to depot).
     * @return array
     */
    public function getManifest(bool $confirm = false): array
    {
        $palletCount = max(1, (int) $this->lifts, (int) $this->billUnitAmount);
        $totalWeight = max(1, (int) $this->weight);
        $palletWeight = max(1, (int) ceil($totalWeight / $palletCount));

        $payload = [
            'externalSystemId' => (string) ($this->importId ?: $this->consignmentNumber),
            'jobType' => 'P',
            'transaction' => $this->consignmentType,
            'consignmentNumber' => (string) $this->consignmentNumber,
            'customerReference' => (string) ($this->reference ?? ''),
            'totalLifts' => $palletCount,
            'customerOwnPaperwork' => false,
            'removeWaste' => false,
            'securePalletService' => false,
            'collectionInformation' => [
                'serviceGroup' => (string) ($this->serviceSurcharge ?: ''),
                'serviceCode' => (string) ($this->serviceCode ?: ''),
                'dueDateTimeTo' => $this->buildDueDateTime($this->collectionDate, null),
                'handball' => (bool) $this->handball,
                'taillift' => false,
                'bookInRequired' => false,
                'address' => $this->buildAddress(
                    $this->collectionContactName,
                    $this->collectionCompanyName,
                    $this->collectionLine,
                    $this->collectionLine2,
                    $this->collectionTown,
                    $this->collectionCounty,
                    $this->collectionPostCode,
                    $this->collectionCountry,
                    $this->collectionTelephone,
                    $this->collectionEmail,
                    null
                ),
            ],
            'deliveryInformation' => [
                'serviceGroup' => (string) ($this->serviceSurcharge ?: ''),
                'serviceCode' => (string) ($this->serviceCode ?: ''),
                'dueDateTimeTo' => $this->buildDueDateTime($this->deliveryDate, $this->deliveryTime),
                'handball' => (bool) $this->handball,
                'taillift' => (bool) $this->tailLift,
                'bookInRequired' => (bool) $this->bookInRequest,
                'bookInContact' => (string) ($this->bookInContactName ?: ''),
                'bookInReference' => (string) ($this->bookInReference ?: ''),
                'address' => $this->buildAddress(
                    $this->deliveryContactName,
                    $this->deliveryCompanyName,
                    $this->deliveryLine,
                    $this->deliveryLine2,
                    $this->deliveryTown,
                    $this->deliveryCounty,
                    $this->deliveryPostCode,
                    $this->deliveryCountry,
                    $this->deliveryTelephone,
                    (string) ($this->notificationEmail ?: ''),
                    $this->manifestNote
                ),
            ],
            'pallets' => [],
        ];

        if ($this->insurance !== null && $this->insurance !== '') {
            $payload['insurance'] = (string) $this->insurance;
        }

        if (!empty($this->notificationEmail)) {
            $payload['emailNotificationEmail'] = (string) $this->notificationEmail;
        }

        if (!empty($this->notificationSms)) {
            $payload['smsNotificationNumber'] = (string) $this->notificationSms;
        }

        for ($index = 1; $index <= $palletCount; $index++) {
            $payload['pallets'][] = [
                'palletNumber' => $index,
                'size' => $this->billUnitType,
                'weight' => $palletWeight,
                'spaces' => $this->mapBillUnitToSpaces($this->billUnitType),
            ];
        }

        return $payload;
    }

    private function buildAddress(
        ?string $contactName,
        ?string $company,
        ?string $line1,
        ?string $line2,
        ?string $town,
        ?string $county,
        ?string $postcode,
        ?string $country,
        ?string $phone,
        ?string $email,
        ?string $instructions
    ): array {
        $address = [
            'contactName' => (string) ($contactName ?? ''),
            'company' => (string) ($company ?? ''),
            'address1' => (string) ($line1 ?? ''),
            'town' => (string) ($town ?? ''),
            'county' => (string) ($county ?? ''),
            'country' => strtoupper((string) ($country ?: 'GB')),
            'postcode' => (string) ($postcode ?? ''),
        ];

        if (!empty($line2)) {
            $address['address2'] = (string) $line2;
        }
        if (!empty($phone)) {
            $address['phone'] = (string) $phone;
        }
        if (!empty($email)) {
            $address['email'] = (string) $email;
        }
        if (!empty($instructions)) {
            $address['instructions'] = (string) $instructions;
        }

        return $address;
    }

    private function buildDueDateTime(?string $date, ?string $time): ?string
    {
        if (empty($date)) {
            return null;
        }

        return date('c', strtotime($date . ' ' . ($time ?: '17:00:00')));
    }

    private function mapBillUnitToSpaces(string $billUnitType): float
    {
        return match ($billUnitType) {
            'HP', 'EPL' => 0.5,
            'QP' => 0.25,
            'MQP' => 0.125,
            default => 1.0,
        };
    }
}
