<?php
/**
 * Consignment Service – GForce Module
 *
 * @category    PortalPeople
 * @package     GForce
 * @author      Portal People Development Team
 * @copyright   Copyright (c) 2026 Portal People (https://portalpeople.co.uk)
 */

namespace PortalPeople\gforce\components;

use Yii;
use backend\models\{ActionLog, Cartsbe};
use PortalPeople\gforce\models\ConsignmentForm;
use PortalPeople\gforce\Module;
use yii\base\Component;

/**
 * ConsignmentService
 *
 * Contains business logic used by the consignment controller to keep
 * request/response handling separate from GForce workflow logic.
 */
class ConsignmentService extends Component
{
    /**
     * Pre-populate consignment form values from a cart shipping address.
     *
     * @param ConsignmentForm $model
     * @param Cartsbe         $cart
     * @return void
     */
    public function prepopulateModelFromCart(ConsignmentForm $model, ?Module $module, Cartsbe $cart): void
    {
        $client = Yii::$app->gforceClient;
        ActionLog::create(['class' => self::class, 'action' => 'prepopulate', 'relatedid' => $cart->id, 'type' => 'Info', 'message' => "GForce: Prepopulating consignment form from cart #{$cart->orderref}"]);
        $shippingAddress = $cart->fulladdresses['shipping'] ?? null;
        if ($shippingAddress) {
            $get = function ($obj, $key, $default = '') {
                if (is_array($obj)) {
                    return $obj[$key] ?? $default;
                }
                if (is_object($obj)) {
                    return $obj->{$key} ?? $default;
                }

                return $default;
            };

            $country     = strtoupper((string) $get($shippingAddress, 'country', ''));
            $countryCode = strlen($country) === 2 ? $country : 'GBR';

            $model->deliveryContactName = (string) (
                $cart->contact ?? trim($get($shippingAddress, 'firstname', '') . ' ' . $get($shippingAddress, 'lastname', ''))
            );
            $model->deliveryTelephone   = (string) $get($shippingAddress, 'telephone', '');
            $model->deliveryCompanyName = (string) (
                $get($shippingAddress, 'company_name', $get($shippingAddress, 'companyname', $model->deliveryContactName))
            );
            $model->deliveryLine        = (string) $get($shippingAddress, 'address1', '');
            $model->deliveryLine2       = (string) $get($shippingAddress, 'address2', '');
            $model->deliveryTown        = (string) $get($shippingAddress, 'town', '');
            $model->deliveryCounty      = (string) $get($shippingAddress, 'county', '');
            $model->deliveryPostCode    = $this->normalizePostcodeWithSpace((string) $get($shippingAddress, 'postcode', ''));
            $model->deliveryCountry     = $countryCode;
            $model->notificationEmail   = (string) ($get($shippingAddress, 'email', '') ?: ($cart->email ?? ''));
        }

        $deliveryLines = $cart->deliveryCartlines ?? [];
        $lineWeight = 0.0;
        $products = [];
        $notes = '';
        $fp = 0;
        $hp = 0;
        $weight = 0;
        $pallets = [];
        if (is_array($deliveryLines) || $deliveryLines instanceof \Traversable) {
            foreach ($deliveryLines as $dl) {
                $products[] = ['qty' => intval($dl->qty) ?? null, 'name' => $dl->product_name ?? null];
                $product = \backend\models\Productsbe::findOne($dl->productid);
                if ($product) {
                    $prodData = $product->data ?? null;
                    $shippingData = is_object($prodData) ? ($prodData->shipping ?? null) : (is_array($prodData) ? ($prodData['shipping'] ?? null) : null);
                    if ($shippingData) {
                        $sizeOfPallet = is_object($shippingData) ? ($shippingData->size_of_pallet ?? null) : ($shippingData['size_of_pallet'] ?? null);
                        if ($sizeOfPallet !== null) {
                            if ((float) $sizeOfPallet === 1.0) {
                                $pallets['FULL'] = ($pallets['FULL'] ?? 0) + (int) $dl->qty;
                            } else {    
                                $pallets['HALF'] = ($pallets['HALF'] ?? 0) + (float) $dl->qty;
                            }
                        }
                    }

                    $weight += (float) ($dl->totalweight ?? 0);
                }
            }
        }
        foreach ($products as $prod) {
            if (is_array($prod) && isset($prod['name'], $prod['qty'])) {
                $notes .= "{$prod['qty']} x {$prod['name']}; ";
            } elseif (is_object($prod) && isset($prod->name, $prod->qty)) {
                $notes .= "{$prod->qty} x {$prod->name}; ";
            }
        }
        $model->deliveryNoteL3 = substr($notes, 0, 48);
        $model->deliveryNoteL4 = substr($notes, 48, 48);
        $fallbackWeight = (float) ($cart->totalweight ?? $cart->weight ?? 0);
        $model->weight = (int) $weight;
        $model->reference = (string) ($cart->porderref ?? '');
        $model->collectionDate    = date('Y-m-d');
        $model->deliveryDate      = $cart->delivery_date;
        $model->collectionCountry = strtoupper((string) (($module && $module->defaultCollectionCountry)
            ? $module->defaultCollectionCountry
            : 'GBR'));

        // Determine bill unit type defensively from first delivery line product shipping data
        $billUnitType = 'FP';
        $firstLine = null;
        if (is_array($deliveryLines) && count($deliveryLines) > 0) {
            $firstLine = $deliveryLines[0];
        } elseif ($deliveryLines instanceof \Traversable) {
            foreach ($deliveryLines as $dl) { $firstLine = $dl; break; }
        }

        $sizeOfPallet = null;
        if ($firstLine && is_object($firstLine) && isset($firstLine->product) && is_object($firstLine->product)) {
            $prodData = $firstLine->product->data ?? null;
            $shippingData = is_object($prodData) ? ($prodData->shipping ?? null) : (is_array($prodData) ? ($prodData['shipping'] ?? null) : null);
            if ($shippingData) {
                $sizeOfPallet = is_object($shippingData) ? ($shippingData->size_of_pallet ?? null) : ($shippingData['size_of_pallet'] ?? null);
            }
        }

        if ($sizeOfPallet !== null) {
            $billUnitType = ((float) $sizeOfPallet === 1.0) ? 'FP' : 'HP';
        } else {
            ActionLog::create(['class' => self::class, 'action' => 'prepopulate', 'relatedid' => $cart->id, 'type' => 'error', 'message' => "GForce: Could not determine size_of_pallet from cart #{$cart->orderref}"]);
        }

        $model->billUnitType = $billUnitType;
        $model->products = $pallets;
        $model->consignmentNumber = (string) (
            $cart->porderref ?? (!empty($cart->orderref) ? $cart->orderref . '_' . date('His') : 'GEN_' . date('YmdHis'))
        );
        // Vigo v1 docs do not expose the route/service lookup endpoint used by
        // the legacy module, so we skip auto-selection here.
    }

    /**
     * Build unique reference used for queue/send feedback.
     *
     * @return string
     */
    public function buildUniqueRef(): string
    {
        return 'NGPW-' . date('YmdHis') . '-' . rand(1000, 9999);
    }

    /**
     * Validate selected service route and run GForce preflight.
     *
     * @param ConsignmentForm $model
     * @param Module|null     $module
     * @param array           $manifest
     * @return array ['ok' => bool, 'message' => string]
     */
    public function validateRouteAndPreflight(ConsignmentForm $model, ?Module $module, array $manifest): array
    {
        $client = Yii::$app->gforceClient;
        $model->deliveryPostCode = $this->normalizePostcodeWithSpace((string) $model->deliveryPostCode);

        $validation = $client->createConsignment($manifest, false);

        if ($validation['Success'] !== true) {
            $validationDescription = $this->getStatusDescription($validation, 'Validation failed.');
            if (!empty($validation['ValidationErrors'])) {
                $validationDescription .= ' Validation: '
                    . json_encode($validation['ValidationErrors'], JSON_UNESCAPED_SLASHES);
            }

            return [
                'ok' => false,
                'message' => 'GForce validation failed: ' . $validationDescription,
            ];
        }

        return $validation;
    }

    /**
     * Send consignment to GForce as a live import.
     *
     * @param array $manifest
     * @return array
     */
    public function createConsignment(array $manifest): array
    {
        $client = Yii::$app->gforceClient;
        return $client->createConsignment($manifest, true);
    }

    /**
     * Get a status code from either supported GForce response shape.
     *
     * @param array $response
     * @return string
     */
    public function getStatusCode(array $response): string
    {
        return (string) (
            $response['Success'] ? 'OK' : 'Bad'
        );
    }

    /**
     * Get a status description from either supported GForce response shape.
     *
     * @param array  $response
     * @param string $default
     * @return string
     */
    public function getStatusDescription(array $response, string $default = 'unknown error'): string
    {
        return (string) (
            $response['Status']['Description']
            ?? $response['Response']['Status']['Description']
            ?? ($response['Status'][0]['Description'] ?? $default)
        );
    }

    /**
     * Persist successful consignment metadata to cart data payload.
     *
     * @param Cartsbe $cart
     * @param string  $trackingCode
     * @return void
     */
    public function persistCartConsignmentResult(Cartsbe $cart, string $trackingCode): void
    {
        $cartData = is_object($cart->data) ? $cart->data : (object) [];
        if (!isset($cartData->gforce) || !is_object($cartData->gforce)) {
            $cartData->gforce = (object) [];
        }

        $cartData->gforce->response_id   = $trackingCode;
        $cartData->gforce->tracking_number = $trackingCode;
        $cartData->gforce->status        = 'Consignment Created';
        $cartData->gforce->despatch_date = date('Y-m-d H:i:s');

        $cart->data = $cartData;
        $cart->save();
    }

    /**
     * Ensure a postcode contains a single space before the inward code.
     * Example: BN11AA -> BN1 1AA
     *
     * @param string $postcode
     * @return string
     */
    private function normalizePostcodeWithSpace(string $postcode): string
    {
        $compact = strtoupper(preg_replace('/\s+/', '', trim($postcode)) ?? '');
        if ($compact === '' || strlen($compact) <= 3) {
            return $compact;
        }

        return substr($compact, 0, -3) . ' ' . substr($compact, -3);
    }
}
