<?php

namespace PortalPeople\virtualstock\services;

use Yii;
use yii\base\BaseObject;
use yii\helpers\Json;
use PortalPeople\virtualstock\Module;
use PortalPeople\virtualstock\components\ProductLookup;
use PortalPeople\virtualstock\components\VirtualStockClient;

class OrderImportService extends BaseObject
{
    public VirtualStockClient $client;
    public VirtualStockCursorStore $cursorStore;
    public Module $module;
    public int $defaultLimit = 100;

    public function run(?string $cursor = null, int $limit = 0, bool $dryRun = false, array $productMappings = []): array
    {
        $effectiveLimit = $limit > 0 ? $limit : $this->defaultLimit;
        $effectiveCursor = $cursor !== null && $cursor !== '' ? $cursor : $this->cursorStore->get();

        $fetched = $this->client->getOrdersUpdatedSince($effectiveCursor, $effectiveLimit);
        $orders = $this->normalizeOrders($fetched['orders'] ?? []);
        // Only import orders with status 'ORDER' (case-insensitive)
        $orders = array_values(array_filter($orders, static fn(array $o) =>
            isset($o['status']) && is_string($o['status']) && strcasecmp($o['status'], 'ORDER') === 0
        ));

        $summary = [
            'cursor' => $effectiveCursor,
            'nextCursor' => $fetched['nextCursor'] ?? null,
            'fetched' => count($orders),
            'created' => 0,
            'updated' => 0,
            'linesCreated' => 0,
            'linesUpdated' => 0,
            'failed' => 0,
            'dryRun' => $dryRun,
            'errors' => [],
            // processed entries contain orderNo, porderref and created flag and cartId when available
            'processed' => [],
        ];

        foreach ($orders as $order) {
            try {
                if ($dryRun) {
                    $preview = $this->previewOne($order);
                    if ($preview['created'] === true) {
                        $summary['created']++;
                    } else {
                        $summary['updated']++;
                    }
                    continue;
                }

                $orderNo = $this->resolveOrderNo($order);
                $lineMap  = $productMappings[$orderNo] ?? [];
                $result   = $this->importOne($order, $lineMap);

                if ($result['created'] === true) {
                    $summary['created']++;
                } else {
                    $summary['updated']++;
                }

                $summary['linesCreated'] += $result['linesCreated'];
                $summary['linesUpdated'] += $result['linesUpdated'];

                // Attempt to resolve the created/updated cart id for downstream processing
                try {
                    $porderref = $order['customerReference'] ?? $order['orderRef'] ?? null;
                    $cart = $this->findCartByVirtualStockOrderNo($orderNo, $porderref);
                    $summary['processed'][] = [
                        'orderNo' => $orderNo,
                        'porderref' => $porderref,
                        'created' => (bool) ($result['created'] ?? false),
                        'cartId' => $cart ? ($cart->id ?? null) : null,
                    ];
                } catch (\Throwable $_e) {
                    // Non-fatal: continue without cartId
                    $summary['processed'][] = [
                        'orderNo' => $orderNo,
                        'porderref' => $order['customerReference'] ?? $order['orderRef'] ?? null,
                        'created' => (bool) ($result['created'] ?? false),
                        'cartId' => null,
                    ];
                }
            } catch (\Throwable $throwable) {
                $summary['failed']++;
                $orderNo = $this->describeOrder($order);
                Yii::error(sprintf(
                    'VirtualStock import failed for order %s (%s): %s',
                    $orderNo,
                    get_class($throwable),
                    $throwable->getMessage()
                ), 'virtualstock');
                Yii::error($throwable->getTraceAsString(), 'virtualstock');
                $summary['errors'][] = [
                    'message' => $throwable->getMessage(),
                    'order' => $order,
                ];
            }
        }

        if ($dryRun === false && is_string($summary['nextCursor']) && $summary['nextCursor'] !== '') {
            $this->cursorStore->set($summary['nextCursor']);
        }

        return $summary;
    }

    /**
     * Magento-style direct ingest path for one order payload.
     * Accepts either a raw VirtualStock order object or a normalized order shape.
     *
     * @param array $orderPayload
     * @param array<int, string> $lineProductMap
     * @return array{success: bool, orderNo: string, created: bool, linesCreated: int, linesUpdated: int}
     */
    public function importSingleOrder(array $orderPayload, array $lineProductMap = []): array
    {
        $isNormalized = isset($orderPayload['orderNo']) || isset($orderPayload['order_no']);
        $orders = $isNormalized ? [$orderPayload] : $this->normalizeOrders([$orderPayload]);

        if ($orders === []) {
            throw new \InvalidArgumentException('Provided payload does not contain an importable order.');
        }

        $order = $orders[0];
        $orderNo = $this->resolveOrderNo($order);
        $result = $this->importOne($order, $lineProductMap);

        return [
            'success' => true,
            'orderNo' => $orderNo,
            'created' => (bool) $result['created'],
            'linesCreated' => (int) $result['linesCreated'],
            'linesUpdated' => (int) $result['linesUpdated'],
        ];
    }

    /**
     * Fetch and normalise pending orders from the API ready for a preview UI.
     * Does NOT write to the database. For each order, includes an `_isNew` flag
     * and (if updating) the existing cart's `ordernumber`.
     *
     * @return array{cursor:string|null, nextCursor:string|null, orders:array<int,array>}
     */
    public function fetchForPreview(?string $cursor = null, int $limit = 0): array
    {
        $effectiveLimit  = $limit > 0 ? $limit : $this->defaultLimit;
        $effectiveCursor = $cursor !== null && $cursor !== '' ? $cursor : $this->cursorStore->get();

        $fetched = $this->client->getOrdersUpdatedSince($effectiveCursor, $effectiveLimit);
        $orders  = $this->normalizeOrders($fetched['orders'] ?? []);
        // Only preview orders with status 'ORDER'
        $orders = array_values(array_filter($orders, static fn(array $o) =>
            isset($o['status']) && is_string($o['status']) && strcasecmp($o['status'], 'ORDER') === 0
        ));

        $preview = [];
        foreach ($orders as $order) {
            $order['_errors'] = [];
            try {
                $orderNo  = $this->resolveOrderNo($order);
                $porderref = $order['customerReference'] ?? $order['orderRef'] ?? null;
                $existing = $this->findCartByVirtualStockOrderNo($orderNo, $porderref);
                $order['_isNew']            = $existing === null;
                $order['_existingOrderNo']  = $existing ? $existing->ordernumber : null;

                    // Dry-run validation: build the cart model and validate without saving.
                try {
                    $this->assertRuntimeClasses();
                    [$cart] = $this->findOrCreateCart($orderNo, $porderref);
                    $this->mapOrderToCart($cart, $order, $orderNo);
                    if (!$cart->validate()) {
                        Yii::warning(sprintf(
                            'VirtualStock preview validation failed for order %s: %s',
                            $orderNo,
                            Json::encode($cart->errors)
                        ), 'virtualstock');

                        foreach ($cart->errors as $attr => $msgs) {
                            foreach ((array) $msgs as $msg) {
                                $order['_errors'][] = $attr . ': ' . $msg;
                            }
                        }
                    }
                } catch (\Throwable $ve) {
                    Yii::error(sprintf(
                        'VirtualStock preview validation exception for order %s (%s): %s',
                        $orderNo,
                        get_class($ve),
                        $ve->getMessage()
                    ), 'virtualstock');
                    Yii::error($ve->getTraceAsString(), 'virtualstock');
                    $order['_errors'][] = 'Validation error: ' . $ve->getMessage();
                }
            } catch (\Throwable $e) {
                $order['_isNew']           = true;
                $order['_existingOrderNo'] = null;
                Yii::error(sprintf(
                    'VirtualStock preview failed for order %s (%s): %s',
                    $this->describeOrder($order),
                    get_class($e),
                    $e->getMessage()
                ), 'virtualstock');
                Yii::error($e->getTraceAsString(), 'virtualstock');
                $order['_errors'][]        = $e->getMessage();
            }
            // Enrich order lines with product-lookup metadata for the UI.
            if (class_exists('\\backend\\models\\Productsbe')) {
                try {
                    $order['orderLines'] = ProductLookup::enrichOrderLines($order['orderLines'] ?? []);
                } catch (\Throwable $le) {
                    Yii::warning('VirtualStock ProductLookup enrich failed: ' . $le->getMessage(), 'virtualstock');
                }
            }

            $preview[] = $order;
        }

        return [
            'cursor'     => $effectiveCursor,
            'nextCursor' => $fetched['nextCursor'] ?? null,
            'orders'     => $preview,
        ];
    }

    private function previewOne(array $order): array
    {
        $orderNo = $this->resolveOrderNo($order);
        $porderref = $order['customerReference'] ?? $order['orderRef'] ?? null;
        $existing = $this->findCartByVirtualStockOrderNo($orderNo, $porderref);

        return [
            'created' => $existing === null,
        ];
    }

    private function importOne(array $order, array $lineProductMap = []): array
    {
        $this->assertRuntimeClasses();
        return \PortalPeople\virtualstock\models\Cart::upsertFromVirtualStock($order, $this->module, $lineProductMap);
    }

    private function mapOrderToCart($cart, array $order, string $orderNo): void
    {
        \PortalPeople\virtualstock\models\Cart::applyVirtualStockOrderData($cart, $order, $orderNo, $this->module);
    }

    private function findOrCreateCart(string $orderNo, ?string $porderref = null): array
    {
        $cart = $this->findCartByVirtualStockOrderNo($orderNo, $porderref);
        if ($cart !== null) {
            return [$cart, false];
        }

        $cartClass = $this->module->cartClass ?? '\\PortalPeople\\virtualstock\\models\\Cart';
        $cart = new $cartClass();

        return [$cart, true];
    }

    private function findCartByVirtualStockOrderNo(string $orderNo, ?string $porderref = null)
    {
        $cartClass = $this->module->cartClass ?? '\\PortalPeople\\virtualstock\\models\\Cart';
        $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;
            }
        

        // If the incoming payload provides a purchase order reference, prefer
        // matching existing carts by `porderref` to avoid collisions caused by
        // using `ordernumber` which may be unset/0 for these imports.
        if ($porderref !== null && $porderref !== '' && $probe->hasAttribute('porderref')) {
            $found = $cartClass::find()->where(['porderref' => $porderref])->one();
            if ($found !== null) {
                return $found;
            }
        }

        Yii::warning($cart, 'virtualstock');
        return $cart;
    }

    private function resolveOrderNo(array $order): string
    {
        $orderNo = $this->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,
        ]);

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

        return $orderNo;
    }


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

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

        return '';
    }

    private function describeOrder(array $order): string
    {
        $orderNo = $this->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,
        ]);

        return $orderNo !== '' ? $orderNo : 'unknown';
    }

    private function normalizeOrders(array $orders): array
    {
        $normalized = [];
        foreach ($orders as $order) {
            if (!is_array($order)) {
                continue;
            }

            $shipping = isset($order['shipping_address']) && is_array($order['shipping_address']) ? $order['shipping_address'] : [];
            $retailer = isset($order['retailer_data']) && is_array($order['retailer_data']) ? $order['retailer_data'] : [];
            $orderIdentifier = $this->firstValidOrderIdentifier([
                $order['order_reference'] ?? null,
                $order['id'] ?? null,
                $order['order_id'] ?? null,
                $order['reference'] ?? null,
                $this->extractIdFromUrl((string) ($order['url'] ?? '')),
            ]);

            $normalized[] = [
                // Identity
                'orderNo'           => $orderIdentifier,
                // Source URL (if provided) — preserved so callers can store it as metadata
                'url'               => $order['url'] ?? null,
                // References
                'customerReference' => $order['end_user_purchase_order_reference']
                                        ?? $order['additional_order_reference']
                                        ?? null,
                'orderRef'          => $order['purchase_order_reference'] ?? null,
                // Status & date
                'status'            => $order['status'] ?? null,
                'orderDate'         => $order['order_date'] ?? null,
                // Customer contact (from shipping address)
                'customerName'      => $shipping['full_name'] ?? null,
                'customerEmail'     => $shipping['email'] ?? ($retailer['email'] ?? null),
                'telephone'         => $shipping['phone'] ?? null,
                // Delivery address
                'deliveryPostcode'  => $shipping['postal_code'] ?? null,
                'deliveryAddress'   => [
                    'line1'    => $shipping['line_1'] ?? null,
                    'line2'    => $shipping['line_2'] ?? null,
                    'city'     => $shipping['city'] ?? null,
                    'postcode' => $shipping['postal_code'] ?? null,
                    'country'  => $shipping['country'] ?? null,
                ],
                // Currency
                'currency'          => $order['currency_code'] ?? null,
                // Lines
                'orderLines'        => $this->normalizeOrderLines(is_array($order['items'] ?? null) ? $order['items'] : []),
                // Raw payload preserved for metadata storage
                '_raw'              => $order,
            ];
        }

        return $normalized;
    }

    private function normalizeOrderLines(array $items): array
    {
        $lines = [];
        foreach ($items as $index => $item) {
            if (!is_array($item)) {
                continue;
            }
            $lines[] = [
                'lineNo'             => $index + 1,
                'erpid'              => $item['line_reference'] ?? null,
                'supplierProductNo'  => $item['part_number'] ?? $item['supplier_sku_reference'] ?? null,
                'sku'                => $item['part_number'] ?? null,
                'name'               => $item['name'] ?? null,
                'productDescription' => $item['name'] ?? null,
                'description'        => $item['description'] ?? null,
                'quantity'           => $item['quantity'] ?? 0,
                'unitPrice'          => $item['unit_cost_price'] ?? 0,
                'status'             => $item['status'] ?? null,
                'promisedDate'       => $item['promised_date'] ?? null,
                'data'               => $item,
            ];
        }
        return $lines;
    }

    private 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 '';
    }

    private 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 function assertRuntimeClasses(): void
    {
        $requiredClasses = [
            '\\backend\\models\\Cartsbe',
            '\\backend\\models\\Cartlines',
            '\\backend\\models\\Productsbe',
        ];

        foreach ($requiredClasses as $requiredClass) {
            if (!class_exists($requiredClass)) {
                throw new \RuntimeException('Required runtime class not found: ' . $requiredClass);
            }
        }
    }
}
