<?php

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

namespace PortalPeople\palletline\components;

use Yii;
use backend\models\Cartsbe;
use PortalPeople\palletline\models\ConsignmentForm;
use PortalPeople\palletline\Module;
use yii\base\Component;

/**
 * ConsignmentService
 *
 * Contains business logic used by the consignment controller to keep
 * request/response handling separate from Palletline 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->palletlineClient;
        if ($module !== null) {
            $model->collectionContactName = (string) $module->collectionContactName;
            $model->collectionTelephone = (string) $module->collectionTelephone;
            $model->collectionCompanyName = (string) $module->collectionCompanyName;
            $model->collectionLine = (string) $module->collectionLine;
            $model->collectionLine2 = (string) $module->collectionLine2;
            $model->collectionTown = (string) $module->collectionTown;
            $model->collectionCounty = (string) $module->collectionCounty;
            $model->collectionPostCode = $this->normalizePostcodeWithSpace((string) $module->collectionPostcode);
            $model->collectionCountry = strtoupper((string) ($module->collectionCountry ?: 'GB'));
            $model->collectionEmail = (string) ($module->collectionEmail ?? '');
        }

        $shippingAddress = $cart->fulladdresses['shipping'] ?? null;
        if (is_object($shippingAddress)) {
            $country = strtoupper((string) ($shippingAddress->country ?? ''));
            $countryCode = strlen($country) === 2 ? $country : 'GB';
            $fullName = trim($shippingAddress->firstname . ' ' . $shippingAddress->lastname);

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

        $deliveryLines  = $cart->deliveryCartlines;
        $lineWeight     = is_array($deliveryLines)
            ? (float) array_sum(array_column($deliveryLines, 'totalweight'))
            : 0.0;
        $fallbackWeight = (float) ($cart->totalweight ?? 0);

        if ($lineWeight > 0) {
            $model->weight = (int) ceil($lineWeight / 1000); // grams -> kg
        } elseif ($fallbackWeight > 0) {
            $model->weight = (int) ceil($fallbackWeight / 1000);
        }

        $model->collectionDate    = date('Y-m-d');
        $model->deliveryDate      = date('Y-m-d', strtotime('+3 days'));
        $sizeOfPallet = $cart->deliveryCartlines[0]->product->data->shipping->size_of_pallet ?? null;
        $model->billUnitType = (float) $sizeOfPallet === 1.0 ? 'FP' : 'HP';
        $model->consignmentNumber = (string) ($cart->porderref ?? $cart->orderref . '_' . date('His') ?? '');
        $routeCheck = $client->getAvailableServices(
            $model->consignmentType,
            strtoupper((string) ($model->collectionCountry ?: 'GB')),
            strtoupper((string) ($model->collectionPostCode ?: '')),
            strtoupper((string) ($model->deliveryCountry ?: 'GB')),
            strtoupper((string) $model->deliveryPostCode)
        );
        $routeCode = $this->getStatusCode($routeCheck);
        if ($routeCode === 'OK') {
            foreach ($this->extractServiceRows($routeCheck) as $service) {
                $serviceCode = trim((string) ($service['serviceCode'] ?? ''));
                if ($serviceCode !== '') {
                    $model->serviceCode = $serviceCode;
                    $model->serviceSurcharge = trim((string) ($service['serviceGroup'] ?? ''));
                    break;
                }
            }
        }
    }

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

    /**
     * Validate selected service route and run Palletline 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->palletlineClient;
        $model->deliveryPostCode = $this->normalizePostcodeWithSpace((string) $model->deliveryPostCode);

        $routeCheck = $client->getAvailableServices(
            $model->consignmentType,
            strtoupper((string) ($model->collectionCountry ?: 'GB')),
            strtoupper((string) ($model->collectionPostCode ?: '')),
            strtoupper((string) ($model->deliveryCountry ?: 'GB')),
            strtoupper((string) $model->deliveryPostCode)
        );
        Yii::warning($routeCheck, 'palletline.routecheck1');
        $routeCode = $this->getStatusCode($routeCheck);
        if ($routeCode !== 'OK') {
            return [
                'ok' => false,
                'message' => 'Palletline route check failed: '
                    . $this->getStatusDescription($routeCheck, 'Could not load available services.'),
            ];
        }

        $serviceRows = $this->extractServiceRows($routeCheck);
        $availableServiceCodes = [];
        $availableGroupCodes = [];

        foreach ($serviceRows as $row) {
            $serviceCode = trim((string) ($row['ServiceCode'] ?? $row['Code'] ?? ''));
            $groupCode   = trim((string) ($row['ServiceGroupCode'] ?? $row['Surcharge'] ?? ''));

            if ($serviceCode !== '') {
                $availableServiceCodes[$serviceCode] = true;
            }
            if ($groupCode !== '') {
                $availableGroupCodes[$groupCode] = true;
            }
        }

        if (!isset($availableServiceCodes[$model->serviceCode])) {
            $valid = implode(', ', array_keys($availableServiceCodes));
            return [
                'ok' => false,
                'message' => 'Selected service code "' . $model->serviceCode
                    . '" is not available for this route. Available: ' . ($valid ?: 'none'),
            ];
        }

        if (!empty($model->serviceSurcharge) && !isset($availableGroupCodes[$model->serviceSurcharge])) {
            $validGroups = implode(', ', array_keys($availableGroupCodes));
            return [
                'ok' => false,
                'message' => 'Selected surcharge/group "' . $model->serviceSurcharge
                    . '" is not available for this route. Available groups: '
                    . ($validGroups ?: 'none'),
            ];
        }

        return ['ok' => true, 'message' => ''];
    }

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

    /**
     * Get a status code from either supported Palletline response shape.
     *
     * @param array $response
     * @return string
     */
    public function getStatusCode(array $response): string
    {
        if (!empty($response['Status']['Code'])) {
            return (string) $response['Status']['Code'];
        }

        if (!empty($response['Response']['Status']['Code'])) {
            return (string) $response['Response']['Status']['Code'];
        }

        if (!empty($response['collectionServices']) || !empty($response['deliveryServices'])) {
            return 'OK';
        }

        return '';
    }

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

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

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

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

    /**
     * Extract available service rows from different Palletline response shapes.
     *
     * @param array $response
     * @return array
     */
    private function extractServiceRows(array $response): array
    {
        if (!empty($response['deliveryServices']) && is_array($response['deliveryServices'])) {
            return $response['deliveryServices'];
        }

        if (!empty($response['data']['deliveryServices']) && is_array($response['data']['deliveryServices'])) {
            return $response['data']['deliveryServices'];
        }

        if (!empty($response['collectionServices']) && is_array($response['collectionServices'])) {
            return $response['collectionServices'];
        }

        return [];
    }

    /**
     * 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);
    }
}
