<?php
/**
 * Caterpoint Product Lookup Helper
 *
 * Adds product matching metadata for Caterpoint order items and resolves
 * product overrides for the import routine.
 *
 * @category    PortalPeople
 * @package     Caterpoint
 */

namespace PortalPeople\caterpoint\components;

use backend\models\Productsbe;
use Yii;

class ProductLookup
{
    /**
     * @var array<string, Productsbe|null>
     */
    private static array $productByIdCache = [];

    /**
     * @var array<string, Productsbe|null>
     */
    private static array $productBySkuCache = [];

    /**
     * @var array<string, Productsbe|null>
     */
    private static array $productByNameCache = [];

    /**
     * @var array<string, array<int, Productsbe>>
     */
    private static array $candidateCache = [];

    /**
     * Add product lookup metadata to each item in an order list.
     *
     * @param array $orders
     * @return array
     */
    public static function enrichOrders(array $orders): array
    {
        foreach ($orders as $orderIndex => $orderData) {
            if (!isset($orderData['items']) || !is_array($orderData['items'])) {
                continue;
            }

            foreach ($orderData['items'] as $itemIndex => $item) {
                $orders[$orderIndex]['items'][$itemIndex]['product_lookup'] = self::buildLookupData($item);
            }
        }

        return $orders;
    }

    /**
     * Resolve the product to use during import.
     *
     * Priority:
     * 1. Manual selection from the bulk import UI
     * 2. Exact SKU match against `stock_code`
     * 3. Exact product name match against `product_name`
     *
     * @param array $item
     * @param string|null $selectedProductId
     * @return Productsbe|null
     */
    public static function resolveProduct(array $item, ?string $selectedProductId = null): ?Productsbe
    {
        $selectedProductId = trim((string) $selectedProductId);
        if ($selectedProductId !== '') {
            $selectedProduct = self::findById($selectedProductId);
            if ($selectedProduct) {
                return $selectedProduct;
            }

            Yii::warning("Selected product override not found: {$selectedProductId}", 'caterpoint-import');
        }

        $sku = trim((string) ($item['sku'] ?? ''));
        if ($sku !== '') {
            $skuProduct = self::findBySku($sku);
            if ($skuProduct) {
                return $skuProduct;
            }
        }

        $name = trim((string) ($item['name'] ?? ''));
        if ($name !== '') {
            $nameProduct = self::findByName($name);
            if ($nameProduct) {
                return $nameProduct;
            }
        }

        return null;
    }

    /**
     * Build lookup data for a single item.
     *
     * @param array $item
     * @return array
     */
    private static function buildLookupData(array $item): array
    {
        $sku = trim((string) ($item['sku'] ?? ''));
        $name = trim((string) ($item['name'] ?? ''));

        $matchedProduct = null;
        $matchType = 'none';

        if ($sku !== '') {
            $matchedProduct = self::findBySku($sku);
            if ($matchedProduct) {
                $matchType = 'sku';
            }
        }

        if (!$matchedProduct && $name !== '') {
            $matchedProduct = self::findByName($name);
            if ($matchedProduct) {
                $matchType = 'name';
            }
        }

        $options = [];
        foreach (self::findCandidates($sku, $name) as $candidate) {
            $options[$candidate->id] = self::formatProductLabel($candidate);
        }

        if ($matchedProduct && !isset($options[$matchedProduct->id])) {
            $options = [$matchedProduct->id => self::formatProductLabel($matchedProduct)] + $options;
        }

        // If no candidates were found, fall back to the full product list for manual search
        $allProducts = false;
        if (empty($options)) {
            $allProducts = true;
            foreach (self::getAllProducts() as $product) {
                $options[$product->id] = self::formatProductLabel($product);
            }
        }

        return [
            'selectedProductId' => $matchedProduct ? $matchedProduct->id : '',
            'matchType' => $matchType,
            'matchLabel' => self::getMatchLabel($matchType),
            'matchedProductLabel' => $matchedProduct ? self::formatProductLabel($matchedProduct) : null,
            'options' => $options,
            'allProducts' => $allProducts,
        ];
    }

    /**
     * Return all non-deleted products ordered by name, used as a fallback when no candidates are found.
     *
     * @param int $limit Maximum rows to return (default 2000).
     * @return array<int, Productsbe>
     */
    private static function getAllProducts(int $limit = 2000): array
    {
        return self::baseQuery()
            ->orderBy(['product_name' => SORT_ASC, 'stock_code' => SORT_ASC])
            ->limit($limit)
            ->all();
    }

    /**
     * Find available candidate products for a Caterpoint item.
     *
     * @param string $sku
     * @param string $name
     * @param int $limit
     * @return array<int, Productsbe>
     */
    private static function findCandidates(string $sku, string $name, int $limit = 15): array
    {
        $cacheKey = md5($sku . '|' . $name . '|' . $limit);
        if (array_key_exists($cacheKey, self::$candidateCache)) {
            return self::$candidateCache[$cacheKey];
        }

        $candidates = [];
        $seen = [];

        $addCandidate = static function (?Productsbe $product) use (&$candidates, &$seen): void {
            if (!$product || isset($seen[$product->id])) {
                return;
            }

            $seen[$product->id] = true;
            $candidates[] = $product;
        };

        if ($sku !== '') {
            $addCandidate(self::findBySku($sku));
        }

        if ($name !== '') {
            $addCandidate(self::findByName($name));

            $nameMatches = self::baseQuery()
                ->andWhere(['like', 'product_name', $name])
                ->orderBy(['product_name' => SORT_ASC, 'stock_code' => SORT_ASC])
                ->limit($limit)
                ->all();

            foreach ($nameMatches as $match) {
                $addCandidate($match);
            }
        }

        self::$candidateCache[$cacheKey] = $candidates;

        return $candidates;
    }

    /**
     * Find a product by internal ID.
     *
     * @param string $productId
     * @return Productsbe|null
     */
    private static function findById(string $productId): ?Productsbe
    {
        if (array_key_exists($productId, self::$productByIdCache)) {
            return self::$productByIdCache[$productId];
        }

        self::$productByIdCache[$productId] = self::baseQuery()
            ->andWhere(['id' => $productId])
            ->one();

        return self::$productByIdCache[$productId];
    }

    /**
     * Find a product by exact stock code.
     *
     * @param string $sku
     * @return Productsbe|null
     */
    private static function findBySku(string $sku): ?Productsbe
    {
        $cacheKey = strtolower($sku);
        if (array_key_exists($cacheKey, self::$productBySkuCache)) {
            return self::$productBySkuCache[$cacheKey];
        }

        self::$productBySkuCache[$cacheKey] = self::baseQuery()
            ->andWhere(['stock_code' => $sku])
            ->one();

        return self::$productBySkuCache[$cacheKey];
    }

    /**
     * Find a product by exact product name.
     *
     * @param string $name
     * @return Productsbe|null
     */
    private static function findByName(string $name): ?Productsbe
    {
        $cacheKey = strtolower($name);
        if (array_key_exists($cacheKey, self::$productByNameCache)) {
            return self::$productByNameCache[$cacheKey];
        }

        self::$productByNameCache[$cacheKey] = self::baseQuery()
            ->andWhere(['product_name' => $name])
            ->one();

        return self::$productByNameCache[$cacheKey];
    }

    /**
     * Base product query for valid selectable products.
     *
     * @return \yii\db\ActiveQuery
     */
    private static function baseQuery()
    {
        return Productsbe::find()
            ->andWhere([
                'or',
                ['status' => null],
                ['not', ['status' => 'Deleted']],
            ]);
    }

    /**
     * Format a product option label for dropdowns.
     *
     * @param Productsbe $product
     * @return string
     */
    private static function formatProductLabel(Productsbe $product): string
    {
        $stockCode = trim((string) $product->stock_code);
        $productName = trim((string) $product->product_name);

        if ($stockCode !== '' && $productName !== '') {
            return $stockCode . ' - ' . $productName;
        }

        return $stockCode !== '' ? $stockCode : $productName;
    }

    /**
     * Convert match type to a display label.
     *
     * @param string $matchType
     * @return string
     */
    private static function getMatchLabel(string $matchType): string
    {
        if ($matchType === 'sku') {
            return 'SKU match';
        }

        if ($matchType === 'name') {
            return 'Name match';
        }

        return 'No match';
    }
}
