<?php

namespace PortalPeople\virtualstock\models;

use Yii;
use yii\helpers\Json;
use \backend\models\Accounts;
use PortalPeople\virtualstock\Module;
use PortalPeople\virtualstock\components\ProductLookup;

class Cart extends \backend\models\Cartsbe
{
    public static function NewVirtualStockOrder(array $data): array
    {
        $module = Yii::$app->getModule('virtualstock');
        if (!$module instanceof \PortalPeople\virtualstock\Module || !$module->isConfigured()) {
            throw new \RuntimeException('VirtualStock module is not configured.');
        }

        return $module->createOrderImportService()->importSingleOrder($data);
    }

    public static function upsertFromVirtualStock(array $order, Module $module, array $lineProductMap = []): array
    {
        $orderNo = self::resolveOrderNo($order);

        return Yii::$app->db->transaction(function () use ($order, $orderNo, $module, $lineProductMap) {
            [$cart, $created] = self::findOrCreateForVirtualStock($orderNo, $module, $order);
            self::applyVirtualStockOrderData($cart, $order, $orderNo, $module);
            if (!$cart->save()) {
                throw new \RuntimeException('Failed to save cart: ' . Json::encode($cart->errors));
            }

            $lineResult = self::upsertVirtualStockLines($cart, self::extractOrderLines($order), $lineProductMap);
            if (!$cart->save(false)) {
                throw new \RuntimeException('Failed to refresh cart totals: ' . Json::encode($cart->errors));
            }

            $result = [
                'created' => $created,
                'linesCreated' => $lineResult['created'],
                'linesUpdated' => $lineResult['updated'],
            ];

            // Log import completion with cart order reference for visibility
            try {
                $label = $cart->porderref ?: $cart->ordernumber ?: 'unknown';
                Yii::warning($result, 'virtualstock-import');
                Yii::warning('VirtualStock import completed: ' . $label, 'virtualstock-import');
            } catch (\Throwable $t) {
                // keep import resilient if logging fails
                Yii::error('VirtualStock import logging failed: ' . $t->getMessage(), 'virtualstock-import');
            }

            return $result;
        });
    }

    public static function applyVirtualStockOrderData($cart, array $order, string $orderNo, Module $module): void
    {
        $cart->ordernumber = $cart->ordernumber ?: $module->makeOrderNumber($orderNo);

        // orderref is protected in this application and should not be set by imports.
        if ($cart->hasAttribute('orderref')) {
            $cart->setAttribute('orderref', null);
        }

        if ($cart->hasAttribute('ordernumber')) {
            $cart->setAttribute('ordernumber', $orderNo);
        }

        $orderRef = self::firstNonEmpty([
            $order['customerReference'] ?? null,
            $order['orderRef'] ?? null,
        ]);
        if ($orderRef !== '') {
            $cart->porderref = $orderRef;
        }

        $status = self::firstNonEmpty([
            $order['status'] ?? null,
            $order['orderStatus'] ?? null,
        ]);
        $cart->status = $module->mapStatus($status);

        $dateValue = self::firstNonEmpty([
            $order['orderDate'] ?? null,
            $order['createdAt'] ?? null,
            $order['updatedAt'] ?? null,
            $order['updated_at'] ?? null,
        ]);
        $dateTimestamp = self::toTimestamp($dateValue);
        if ($dateTimestamp !== null) {
            $cart->dateordered = $dateTimestamp;
        }

        $accountCode = self::firstNonEmpty([
            $order['customerAccount'] ?? null,
            $order['accountCode'] ?? null,
        ]);
        if ($accountCode !== '') {
            $cart->account_code = $accountCode;
        } elseif (isset($module->defaultAccountCode) && $module->defaultAccountCode !== '') {
            $cart->account_code = $cart->account_code ?: $module->defaultAccountCode;
        }

        $email = self::firstNonEmpty([
            $order['customerEmail'] ?? null,
            $order['email'] ?? null,
        ]);
        if ($email !== '') {
            $cart->email = $email;
        }

        $contact = self::firstNonEmpty([
            $order['customerName'] ?? null,
            $order['contact'] ?? null,
        ]);
        if ($contact !== '') {
            $cart->contact = $contact;
        }

        $telephone = self::firstNonEmpty([
            $order['customerPhone'] ?? null,
            $order['telephone'] ?? null,
        ]);
        if ($telephone !== '') {
            $cart->telephone = $telephone;
        }

        $postcode = self::firstNonEmpty([
            $order['deliveryPostcode'] ?? null,
            $order['postcode'] ?? null,
        ]);
        if ($postcode !== '') {
            $cart->postcode = $postcode;
        }

        $fullAddress = self::buildFullAddress($order);
        if ($fullAddress !== '') {
            $cart->fulladdress = $fullAddress;
        }

        $deliveryAddress = $order['deliveryAddress'] ?? [];
        if (!empty($deliveryAddress)) {
            $existing = is_string($cart->fulladdresses)
                ? (Json::decode($cart->fulladdresses, true) ?? [])
                : (is_array($cart->fulladdresses) ? $cart->fulladdresses : []);
            $existing['shipping'] = [
                'address1'  => $deliveryAddress['line1'] ?? '',
                'address2'  => $deliveryAddress['line2'] ?? '',
                'town'      => $deliveryAddress['city'] ?? '',
                'postcode'  => $deliveryAddress['postcode'] ?? '',
                'country'   => $deliveryAddress['country'] ?? '',
                'telephone' => $order['telephone'] ?? '',
                'full_name' => $order['customerName'] ?? '',
                'email'     => $order['customerEmail'] ?? '',
            ];
            $cart->fulladdresses = $existing;
        }

        $orderUrl = self::firstNonEmpty([
            $order['url'] ?? null,
            $order['orderUrl'] ?? null,
            $order['order_url'] ?? null,
        ]);
        Yii::warning($order);
        $meta = [
            'order_no' => $orderNo,
            'status' => $status,
            'last_sync' => date('c'),
            'orderurl' => $orderUrl,
            'updated_at_source' => self::firstNonEmpty([
                $order['updatedAt'] ?? null,
                $order['updated_at'] ?? null,
            ]),
        ];

        $shipments = self::extractShipments($order);
        if ($shipments !== []) {
            $meta['shipments_json'] = Json::encode($shipments);
        }

        // Persist the raw import payload into the cart's imported_data column
        // when available. Normaliser includes the original payload under `_raw`.
        if (isset($order['_raw'])) {
            $cart->imported_data = Json::encode($order['_raw']);
        } else {
            // Fallback to storing the normalized order for traceability
            $cart->imported_data = Json::encode($order);
        }

        // Log cart attributes for debugging before save/import
        try {
            Yii::warning($cart->attributes, 'virtualstock');
        } catch (\Throwable $t) {
            // Keep import resilient if logging fails
            Yii::error('VirtualStock attribute logging failed: ' . $t->getMessage(), 'virtualstock');
        }

        self::setVirtualStockMetadata($cart, $meta);
    }

    private static function findOrCreateForVirtualStock(string $orderNo, Module $module, array $order = []): array
    {
        $cartClass = $module->cartClass ?? self::class;
        $probe = new $cartClass();

        $jsonOrderNo = new \yii\db\Expression("json_value(`data`, '$.virtualstock.order_no')");
            $cart = $cartClass::find()->where(['=', $jsonOrderNo, $orderNo])->one();
            if ($cart !== null) {
                return $cart;
            }

        // Prefer matching by purchase order reference (`porderref`) when supplied in the
        // incoming VirtualStock payload. This avoids relying on the `ordernumber` column
        // which is often 0 for these imports.
        $orderRef = self::firstNonEmpty([
            $order['customerReference'] ?? null,
            $order['orderRef'] ?? null,
        ]);
        if ($orderRef !== '' && $probe->hasAttribute('porderref')) {
            $foundByPorder = $cartClass::find()->where(['porderref' => $orderRef])->one();
            if ($foundByPorder !== null) {
                return [$foundByPorder, false];
            }
        }
        /*
            $foundByOrderNumber = $cartClass::find()->where(['ordernumber' => $module->makeOrderNumber($orderNo)])->one();
            if ($foundByOrderNumber !== null) {
                return [$foundByOrderNumber, false];
            }
        */
        return [new $cartClass(), true];
    }

    private static function upsertVirtualStockLines($cart, array $lines, array $lineProductMap): array
    {
        $cartLinesClass = '\\backend\\models\\Cartlines';

        $created = 0;
        $updated = 0;
        $seenSequences = [];

        foreach ($lines as $index => $line) {
            if (!is_array($line)) {
                continue;
            }

            $sequence = (int) ($line['lineNo'] ?? $line['sequence'] ?? ($index + 1));
            if ($sequence <= 0) {
                $sequence = $index + 1;
            }

            $seenSequences[] = $sequence;

            $lineModel = $cartLinesClass::find()
                ->where(['basketid' => $cart->id, 'sequence' => $sequence])
                ->one();

            if ($lineModel === null) {
                $lineModel = new $cartLinesClass([
                    'basketid' => $cart->id,
                    'sequence' => $sequence,
                ]);
                $created++;
            } else {
                $updated++;
            }

            self::mapVirtualStockLine($lineModel, $line, $sequence, $lineProductMap[$sequence] ?? null);

            if (!$lineModel->save()) {
                throw new \RuntimeException('Failed to save cart line: ' . Json::encode($lineModel->errors));
            }
        }

        self::markMissingLinesDeleted($cart->id, $seenSequences);

        return [
            'created' => $created,
            'updated' => $updated,
        ];
    }

    private static function mapVirtualStockLine($lineModel, array $line, int $sequence, ?string $productIdOverride = null): void
    {
        $lineModel->sequence = $sequence;
        $lineModel->qty = max(0, (int) ($line['quantity'] ?? $line['qty'] ?? 0));
        $lineModel->price = (float) ($line['unitPrice'] ?? $line['price'] ?? 0);
        $lineModel->status = 'Active';

        $productName = self::firstNonEmpty([
            $line['productDescription'] ?? null,
            $line['name'] ?? null,
            $line['product_name'] ?? null,
        ]);

        if ($productName !== '') {
            $lineModel->product_name = $productName;
        }

        $productDescription = self::firstNonEmpty([
            $line['description'] ?? null,
            $line['productDescription'] ?? null,
            $line['product_description'] ?? null,
        ]);

        if ($productDescription !== '' && $lineModel->hasAttribute('product_description')) {
            $lineModel->setAttribute('product_description', $productDescription);
        }

        $supplierSku = trim(self::firstNonEmpty([
            $line['supplierProductNo'] ?? null,
            $line['sku'] ?? null,
            $line['stock_code'] ?? null,
        ]));

        if ($supplierSku !== '' && $lineModel->hasAttribute('stock_code')) {
            $lineModel->setAttribute('stock_code', $supplierSku);
        }

        $product = ProductLookup::resolveProduct($line, $productIdOverride);
        if ($product !== null) {
            $lineModel->productid = $product->id;
        }


        $lineModel->requested_date = $line['promisedDate'] ?? null;

        $lineModel->dispatch = 0;
        $lineModel->data = Json::encode($line['data'] ?? $line);
        /*$lineModel->data->virtualstock = [
            'line_reference' => $line['line_reference'] ?? null,
            'part_number' => $line['part_number'] ?? null,
            'supplier_sku_reference' => $line['supplier_sku_reference'] ?? null,
            'name' => $line['name'] ?? null,
            'description' => $line['description'] ?? null,
        ];
        */
    }

    private static function markMissingLinesDeleted(string $cartId, array $seenSequences): void
    {
        $cartLinesClass = '\\backend\\models\\Cartlines';

        $query = $cartLinesClass::find()
            ->where(['basketid' => $cartId])
            ->andWhere(['<', 'sequence', self::SEQUENCE_PRODUCT_MAX])
            ->andWhere(['!=', 'status', 'Deleted']);

        if ($seenSequences !== []) {
            $query->andWhere(['not in', 'sequence', $seenSequences]);
        }

        $staleLines = $query->all();

        foreach ($staleLines as $lineModel) {
            $lineModel->status = 'Deleted';
            $lineModel->qty = 0;
            if (!$lineModel->save(false, ['status', 'qty'])) {
                throw new \RuntimeException('Failed to mark stale cart line deleted: ' . Json::encode($lineModel->errors));
            }
        }
    }

    private static function setVirtualStockMetadata($cart, array $metadata): void
    {
        $existing = self::decodeExistingData($cart);

        $virtualStockData = [];
        if (isset($existing['virtualstock']) && is_array($existing['virtualstock'])) {
            $virtualStockData = $existing['virtualstock'];
        }

        $existing['virtualstock'] = array_merge($virtualStockData, $metadata);
        $cart->data = Json::encode($existing);
    }

    /**
     * Append last API response from VirtualStock into the cart's data.virtualstock.last_api_response
     * and persist the cart.
     *
     * @param object|array $response
     * @return void
     */
    public static function appendVirtualStockApiResponse($cart, $response): void
    {
        try {
            $existing = self::decodeExistingData($cart);
            if (!isset($existing['virtualstock']) || !is_array($existing['virtualstock'])) {
                $existing['virtualstock'] = [];
            }
            $existing['virtualstock']['last_api_response'] = $response;
            $cart->data = Json::encode($existing);
            $cart->save(false);
        } catch (\Throwable $t) {
            Yii::warning('Failed to append VirtualStock API response to cart: ' . $t->getMessage(), 'virtualstock');
        }
    }

    private static function decodeExistingData($cart): array
    {
        $rawData = $cart->isNewRecord ? null : $cart->getOldAttribute('data');

        if (!is_string($rawData) || $rawData === '') {
            if (is_string($cart->data)) {
                $rawData = $cart->data;
            }
        }

        if (!is_string($rawData) || $rawData === '') {
            return [];
        }

        $decoded = Json::decode($rawData, true);

        return is_array($decoded) ? $decoded : [];
    }

    private static function resolveOrderNo(array $order): string
    {
        $orderNo = self::firstValidOrderIdentifier([
            $order['orderNo'] ?? null,
            $order['order_no'] ?? null,
            $order['orderId'] ?? null,
            $order['order_id'] ?? null,
            $order['order_reference'] ?? null,
            $order['reference'] ?? null,
            $order['id'] ?? null,
            self::extractIdFromUrl((string) ($order['url'] ?? '')),
        ]);

        if ($orderNo === '') {
            throw new \InvalidArgumentException('Order payload missing order identifier.');
        }

        return $orderNo;
    }

    private static function extractOrderLines(array $order): array
    {
        $lines = $order['orderLines'] ?? $order['lines'] ?? [];

        if (!is_array($lines)) {
            return [];
        }

        if (array_is_list($lines)) {
            return array_values(array_filter($lines, static fn($line) => is_array($line)));
        }

        if (isset($lines['lineNo']) || isset($lines['quantity']) || isset($lines['supplierProductNo'])) {
            return [$lines];
        }

        $out = [];
        foreach ($lines as $line) {
            if (is_array($line)) {
                $out[] = $line;
            }
        }

        return $out;
    }

    private static function extractShipments(array $order): array
    {
        $shipments = $order['shipments'] ?? [];
        if (!is_array($shipments)) {
            return [];
        }

        if (array_is_list($shipments)) {
            return array_values(array_filter($shipments, static fn($shipment) => is_array($shipment)));
        }

        if (isset($shipments['trackingNo']) || isset($shipments['carrier'])) {
            return [$shipments];
        }

        $out = [];
        foreach ($shipments as $shipment) {
            if (is_array($shipment)) {
                $out[] = $shipment;
            }
        }

        return $out;
    }

    private static function buildFullAddress(array $order): string
    {
        $parts = [];

        if (isset($order['deliveryAddress']) && is_array($order['deliveryAddress'])) {
            $deliveryAddress = $order['deliveryAddress'];
            $parts[] = self::firstNonEmpty([
                $deliveryAddress['line1'] ?? null,
                $deliveryAddress['address1'] ?? null,
            ]);
            $parts[] = self::firstNonEmpty([
                $deliveryAddress['line2'] ?? null,
                $deliveryAddress['address2'] ?? null,
            ]);
            $parts[] = self::firstNonEmpty([
                $deliveryAddress['city'] ?? null,
                $deliveryAddress['town'] ?? null,
            ]);
            $parts[] = $deliveryAddress['postcode'] ?? null;
            $parts[] = $deliveryAddress['country'] ?? null;
        } else {
            $parts[] = self::firstNonEmpty([
                $order['deliveryAddress1'] ?? null,
                $order['address1'] ?? null,
            ]);
            $parts[] = self::firstNonEmpty([
                $order['deliveryAddress2'] ?? null,
                $order['address2'] ?? null,
            ]);
            $parts[] = self::firstNonEmpty([
                $order['deliveryCity'] ?? null,
                $order['city'] ?? null,
            ]);
            $parts[] = self::firstNonEmpty([
                $order['deliveryPostcode'] ?? null,
                $order['postcode'] ?? null,
            ]);
            $parts[] = self::firstNonEmpty([
                $order['deliveryCountry'] ?? null,
                $order['country'] ?? null,
            ]);
        }

        $parts = array_filter(array_map(static fn($part) => trim((string) $part), $parts), static fn($part) => $part !== '');

        return implode(', ', $parts);
    }

    private static function toTimestamp(string $dateValue): ?int
    {
        if ($dateValue === '') {
            return null;
        }

        $timestamp = strtotime($dateValue);

        return $timestamp === false ? null : $timestamp;
    }

    private static function firstNonEmpty(array $values): string
    {
        foreach ($values as $value) {
            if (!is_scalar($value)) {
                continue;
            }

            $stringValue = trim((string) $value);
            if ($stringValue !== '') {
                return $stringValue;
            }
        }

        return '';
    }

    private static function firstValidOrderIdentifier(array $values): string
    {
        foreach ($values as $value) {
            if (!is_scalar($value)) {
                continue;
            }

            $candidate = trim((string) $value);
            if ($candidate === '') {
                continue;
            }

            $lower = strtolower($candidate);
            if (in_array($lower, ['0', 'null', 'undefined', 'order', 'orders', 'unknown'], true)) {
                continue;
            }

            return $candidate;
        }

        return '';
    }

    private static function extractIdFromUrl(string $url): string
    {
        if ($url === '') {
            return '';
        }

        $path = parse_url($url, PHP_URL_PATH);
        if (!is_string($path) || $path === '') {
            return '';
        }

        $segments = array_values(array_filter(explode('/', trim($path, '/')), static fn($segment) => $segment !== ''));
        if ($segments === []) {
            return '';
        }

        $last = trim((string) end($segments));
        if ($last === '' || strcasecmp($last, 'orders') === 0 || str_contains($last, '?')) {
            return '';
        }

        if (count($segments) >= 2 && strcasecmp($segments[count($segments) - 2], 'orders') === 0) {
            return $last;
        }

        return '';
    }

    
    public $csvFile;
    public function rules() : array
    {
        $rules = parent::rules();
        $rules[] = [['account_code'], 'default', 'value' => 'SCR001'];
        $rules[] = [['paymentmethod'], 'default', 'value' => 'VirtualStock'];
        return $rules;
    }

    public function CreateDeliveryLines(){
        $shippingcosts = \PortalPeople\shippingcosts\services\ShippingCostCalculatorService::calculateCartCosts($this->id, 'detailed');
        $shippingline = $this->shippingline;
        foreach($shippingcosts as $haulier => $data){
            $cartline = Cartlines::find()
                ->where([
                    'basketid' => $this->id, 
                    'haulierint' => $haulier, 
                    'productid' => '331D4D9B-7F11-9473-1279-CAFDA629360A'
                ])
                ->one();
            $shippingPrice = 0;
            foreach($this->cartlines as $line){
                $lineData = $line->data  && !is_array($line->data) ? json_decode($line->data, true) : [];
                if (isset($lineData['virtualstock']['shipping_price'])){
                    $shippingPrice += $lineData['virtualstock']['shipping_price'];
                }
            }
            if (!$cartline){
                $cartline = new Cartlines(['id' => Cartlines::Tempid(), 'basketid' => $this->id]);
                $cartline->productid = '331D4D9B-7F11-9473-1279-CAFDA629360A';
                $cartline->requested_date = getWorkingDaysAhead(10, 'Y-m-d');
                $cartline->qty = 1;
                $cartline->erpqty = 1;
                $cartline->price_exvat = $shippingPrice;
                !empty($cartline->price_exvat) && empty($cartline->price) 
                    ? $cartline->price = round((float) $cartline->price_exvat * 1.2, 2) 
                    : false;
                $cartline->deliveryline = 0;
                $cartline->paid = true;
            } 
            $cartline->haulierint = $haulier;
            $cartline->cost = $data['cost'];
            $cartline->costtotal = $data['cost'];
            $cartline->product_description = $shippingline?->product_description;
            
            $cartline->hidden_info = $data['haulier'] .' Delivery ' . Inflector::pluralize($data['total_qty'] .' Parcel', $data['total_qty']);
            $cartline->save();
        } 
    }
    public function afterSave($insert, $changedAttributes)
    {
        $this->CreateDeliveryLines();
        $this->refresh();
        $total = array_sum(array_column($this->cartlines, 'linetotal'));
        $this->payment->updateAttributes(['status' => 'Paid', 'camount' => $total, 'amount' => $total]);
        if (($insert && $this->status == 'Paid') ||  (in_array('status', array_keys($changedAttributes)) && $this->status == 'Paid' )){
            Yii::warning('Triggering cart-order-sendtonetsuite event for Cart ID: ' . $this->id, 'Cart afterSave');
       //     Yii::$app->trigger('cart-order-sendtonetsuite', ($event = new \common\events\UserDataEvent(['model' => $this])));
        }
        return parent::afterSave($insert, $changedAttributes);
    }

    public function beforeSave($insert)
    {
        $account = Accounts::find()->where(['account_code' => 'SCR001'])->one() ?? throw new \RuntimeException("ScrewFix Account not found");
        $scraddress = $account->addressl ?? null;
        $addresses = $this->fulladdresses; //&& !is_array($this->fulladdresses) ?? json_decode($this->fulladdresses, true)  ?? $this->fulladdresses;
        
        //Yii::warning($addresses, 'Cart beforeSave addresses 2');
        $addresses['billing'] = $scraddress ?? [];
        $this->fulladdresses = $addresses;
        parent::beforeSave($insert);
        $shipment = new \backend\models\Shipment();
        $shipment->shipment_type = 0;
        $shipment->cart_id = $this->id;
        $shipment->delivery = 'Eco' ?? null;
        $shipment->shipment_date = getWorkingDaysAhead(3, 'Y-m-d');
        $shipment->save();

        $payment = new \backend\models\Paymentresponsesbe();
        $payment->basketid  = $this->id;
        $payment->source  = 'VirtualStock';
        $payment->camount = $this->total;
        $payment->amount = $this->total;
        $payment->status = 'Paid';
        $payment->save();

        return true;
    }
}

/**
 * Get the date after a specified number of working days.
 *
 * @param int $days Number of working days to add.
 * @param string $format Date format to return.
 * @return string Formatted date string.
 */
function getWorkingDaysAhead($days, $format)
{
    $currentDate = new \DateTime();
    $workingDays = 0;
    while ($workingDays < $days) {
        $currentDate->add(new \DateInterval('P1D'));
        if ($currentDate->format('N') < 6) {
            $workingDays++;
        }
    }
    return $currentDate->format($format);
}
