<?php

namespace Modules\ProductCompat\Entities\Enrichment\Providers;

use App\Product;
use Illuminate\Support\Facades\Http;
use Modules\ProductCompat\Entities\PcPartNumber;
use Modules\ProductCompat\Entities\Enrichment\EnrichmentProvider;
use Modules\ProductCompat\Entities\Enrichment\EnrichmentResult;
use Modules\ProductCompat\Entities\Services\CustomFieldParser;
use Modules\ProductCompat\Entities\Services\Settings;

/**
 * Icecat Open Catalog provider (https://icecat.biz). Looks the product up by
 * Brand + ProductCode (using the product's existing part numbers as the code)
 * or by GTIN, and returns the canonical manufacturer part code, GTINs and any
 * compatible models found in the product title.
 *
 * The free open-catalog username "openIcecat-live" works out of the box; set
 * PRODUCTCOMPAT_ICECAT_USER to your own account for full-catalog coverage.
 */
class IcecatProvider implements EnrichmentProvider
{
    protected CustomFieldParser $parser;

    /** Title words that are not device models. */
    protected array $noise = [
        'battery', 'li-ion', 'lithium', 'ion', 'cell', 'cells', 'laptop', 'notebook',
        'spare', 'part', 'pc', 'adapter', 'charger', 'original', 'genuine', 'for', 'with',
        'black', 'white', 'series', 'and', 'the', 'replacement', 'compatible',
    ];

    public function __construct(?CustomFieldParser $parser = null)
    {
        $this->parser = $parser ?? new CustomFieldParser();
    }

    public function key(): string
    {
        return 'icecat';
    }

    public function isAvailable(): bool
    {
        return ! empty(Settings::get('icecat_username', config('productcompat.enrichment.icecat.username')));
    }

    public function fetch(Product $product): EnrichmentResult
    {
        $cfg     = config('productcompat.enrichment.icecat', []);
        // UI-editable settings override config/env.
        $user    = Settings::get('icecat_username', $cfg['username'] ?? 'openIcecat-live');
        $lang    = Settings::get('icecat_lang', $cfg['language'] ?? 'en');
        $base    = $cfg['base_url'] ?? 'https://live.icecat.biz/api';
        $timeout = (int) config('productcompat.enrichment.timeout', 15);
        $ua      = config('productcompat.enrichment.user_agent', 'ProductCompatBot/1.0');
        $conf    = (int) ($cfg['confidence'] ?? config('productcompat.enrichment.confidence.api') ?? 80);

        $brand = trim((string) optional($product->brand)->name);

        // Build the lookup attempts: existing part numbers as ProductCode, then GTINs.
        $attempts = [];
        if ($brand !== '') {
            $codes = PcPartNumber::where('product_id', $product->id)
                ->orderByDesc('is_primary')->limit(8)->pluck('part_number');
            foreach ($codes as $code) {
                $attempts[] = ['Brand' => $brand, 'ProductCode' => $code];
            }
        }
        foreach ($this->gtinCandidates($product) as $gtin) {
            $attempts[] = ['GTIN' => $gtin];
        }

        if (empty($attempts)) {
            return EnrichmentResult::empty(['reason' => 'no brand+part-number or GTIN to query Icecat']);
        }

        $lastMsg = null;
        foreach ($attempts as $q) {
            try {
                $resp = Http::timeout($timeout)->withHeaders(['User-Agent' => $ua])
                    ->get($base, array_merge(['UserName' => $user, 'Language' => $lang], $q));
            } catch (\Throwable $e) {
                $lastMsg = $e->getMessage();
                continue;
            }

            // Detect rate/quota limits and surface them (UI prompts to switch account).
            if ($resp->status() === 429 || $resp->status() === 403) {
                Settings::set('icecat_limit_reached_at', now()->toDateTimeString());
                Settings::set('icecat_limit_message', 'HTTP ' . $resp->status() . ' from Icecat — rate/quota limit likely reached.');
                return EnrichmentResult::fail('icecat_limit: HTTP ' . $resp->status());
            }

            $json = $resp->json() ?? [];
            if (($json['msg'] ?? null) !== 'OK' || empty($json['data']['GeneralInfo'])) {
                $msg = (string) ($json['Message'] ?? ('HTTP ' . $resp->status()));
                if (preg_match('/limit|quota|requests|exceeded|too many/i', $msg)) {
                    Settings::set('icecat_limit_reached_at', now()->toDateTimeString());
                    Settings::set('icecat_limit_message', $msg);
                    return EnrichmentResult::fail('icecat_limit: ' . $msg);
                }
                $lastMsg = $msg;
                continue;
            }

            $data = $json['data'];
            $g = $data['GeneralInfo'];
            $title = $g['Title'] ?? '';
            $gtins = array_values(array_unique((array) ($g['GTIN'] ?? [])));

            // BrandPartCode is the clean, canonical part number. We deliberately
            // do NOT mine the free-text title for part numbers (too noisy, e.g.
            // "9-cell"); device-model compatibility is mined from the title.
            $parts = [];
            if (! empty($g['BrandPartCode'])) {
                $parts[] = ['value' => $g['BrandPartCode'], 'type' => 'mpn', 'confidence' => $conf];
            }

            $devices = $this->devicesFromTitle($title, $brand);

            // GTINs -> barcode candidates (stored as needs_review).
            $barcodes = [];
            foreach ($gtins as $gtin) {
                $digits = preg_replace('/\D/', '', (string) $gtin);
                if (in_array(strlen($digits), [8, 12, 13, 14], true)) {
                    $barcodes[] = ['value' => $digits, 'type' => 'gtin', 'confidence' => $conf];
                }
            }

            return new EnrichmentResult(true, ['part_numbers' => $parts, 'device_models' => $devices, 'barcodes' => $barcodes], [
                'matched_by' => $q,
                'icecat_id'  => $g['IcecatId'] ?? null,
                'title'      => $title,
                'category'   => $g['Category']['Name']['Value'] ?? null,
                'gtins'      => array_slice($gtins, 0, 12),
            ], null, $this->richFromIcecat($data, $g, $title));
        }

        return EnrichmentResult::empty(['reason' => 'no Icecat match', 'last' => $lastMsg]);
    }

    /**
     * Numeric GTIN-like identifiers (8/12/13/14 digits) from the product SKU.
     */
    protected function gtinCandidates(Product $product): array
    {
        $out = [];
        foreach ([$product->sku] as $v) {
            $digits = preg_replace('/\D/', '', (string) $v);
            if (in_array(strlen($digits), [8, 12, 13, 14], true)) {
                $out[] = $digits;
            }
        }

        return array_values(array_unique($out));
    }

    /**
     * Pull compatible-model tokens out of the Icecat title (e.g. "…TM3210/2400/
     * AS3200/5500"). Best-effort -> moderate confidence / needs_review.
     */
    protected function devicesFromTitle(string $title, ?string $brand): array
    {
        $conf = 55;
        $out = [];
        $seen = [];
        $tokens = preg_split('/[\/\s(),]+/', $title) ?: [];

        foreach ($tokens as $t) {
            $t = trim($t, " \t.-");
            if ($t === '') {
                continue;
            }
            $low = strtolower($t);
            if (in_array($low, $this->noise, true)) {
                continue;
            }
            if ($brand && strcasecmp($t, $brand) === 0) {
                continue;
            }
            if (! preg_match('/\d/', $t)) {
                continue;                         // models contain a digit
            }
            if (! preg_match('/^[A-Za-z0-9][A-Za-z0-9\-]{1,19}$/', $t)) {
                continue;
            }
            if (preg_match('/^\d{1,2}$/', $t)) {
                continue;                         // bare small numbers
            }
            if (preg_match('/^\d+-?cell$/i', $t)) {
                continue;                         // cell counts (9-cell, 6cell)
            }
            if (preg_match('/(mah|wh|ghz|mhz|gb|mb|tb|v|hz|cm|mm|nm)$/i', $t)) {
                continue;                         // measurements / capacities
            }
            $key = strtoupper($t);
            if (isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;
            $out[] = ['brand' => $brand ?: null, 'series' => null, 'model' => $t, 'confidence' => $conf];
            if (count($out) >= 30) {
                break;
            }
        }

        return $out;
    }

    /**
     * Harvest the rich payload Icecat returns but the classic pipeline discards:
     * a description (first non-empty of the three summary fields), the brand,
     * up to 6 http(s) image URLs (HighPic + gallery), and source texts for the
     * AI pass. Best-effort — any missing field is simply omitted.
     */
    protected function richFromIcecat(array $data, array $g, string $title): array
    {
        $rich = [];

        // Description: first non-empty of LongDesc / LongSummaryDescription /
        // ShortSummaryDescription. Icecat nests these under GeneralInfo.
        foreach (['LongDesc', 'LongSummaryDescription', 'ShortSummaryDescription'] as $key) {
            $desc = trim((string) ($g[$key] ?? ''));
            if ($desc !== '') {
                $rich['description'] = $desc;
                break;
            }
        }

        $brand = trim((string) ($g['Brand'] ?? ''));
        if ($brand !== '') {
            $rich['brand'] = $brand;
        }

        // Images: primary HighPic plus any gallery Pic URLs (http(s), cap 6).
        $urls = [];
        $img = $data['Image'] ?? [];
        if (! empty($img['HighPic']) && $this->isHttpUrl($img['HighPic'])) {
            $urls[] = $img['HighPic'];
        }
        foreach ((array) ($data['Gallery'] ?? []) as $shot) {
            $pic = $shot['Pic'] ?? null;
            if ($pic && $this->isHttpUrl($pic)) {
                $urls[] = $pic;
            }
        }
        $urls = array_values(array_slice(array_unique($urls), 0, 6));
        if ($urls) {
            $rich['image_urls'] = $urls;
        }

        // Source texts for the AI extraction pass (verbatim-source rule needs these).
        $texts = [];
        if ($title !== '') {
            $texts[] = $title;
        }
        if (! empty($rich['description'])) {
            $texts[] = $rich['description'];
        }
        if ($texts) {
            $rich['source_texts'] = $texts;
        }

        return $rich;
    }

    /** Cheap http(s) URL check used when harvesting image URLs. */
    protected function isHttpUrl($value): bool
    {
        return is_string($value) && (bool) preg_match('#^https?://#i', $value);
    }
}
