<?php

namespace App\Services\AiChat;

use App\Models\AiProductImageIndex;
use App\Models\AiProductIndex;
use App\Models\BusinessSetting;
use App\Models\Item;
use App\Models\KnowledgeBaseEntry;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;

class AiProductIndexService
{
    public function syncAllProducts(?int $limit = null): int
    {
        if (!Schema::hasTable('ai_product_indices') || !Schema::hasTable('items')) {
            return 0;
        }

        $synced = 0;
        $query = Item::withoutGlobalScopes()
            ->with(['category:id,name,parent_id', 'unit:id,unit', 'colors:id,name,code', 'storage', 'ecommerce_item_details.brand:id,name'])
            ->where('status', 1)
            ->where('is_approved', 1)
            ->orderBy('id');

        $query->chunkById(100, function (Collection $items) use (&$synced, $limit) {
            foreach ($items as $item) {
                if ($limit !== null && $synced >= $limit) {
                    return false;
                }
                $this->syncLoadedItem($item);
                $synced++;
            }

            return true;
        }, 'id');

        $this->bustCatalogCaches();

        return $synced;
    }

    public function syncItem(int|Item $item): bool
    {
        if (!Schema::hasTable('ai_product_indices') || !Schema::hasTable('items')) {
            return false;
        }

        $model = $item instanceof Item
            ? $item->loadMissing(['category:id,name,parent_id', 'unit:id,unit', 'colors:id,name,code', 'storage', 'ecommerce_item_details.brand:id,name'])
            : Item::withoutGlobalScopes()
                ->with(['category:id,name,parent_id', 'unit:id,unit', 'colors:id,name,code', 'storage', 'ecommerce_item_details.brand:id,name'])
                ->find($item);

        if (!$model || (int) $model->status !== 1 || (int) $model->is_approved !== 1) {
            $this->removeItem((int) ($item instanceof Item ? $item->id : $item));
            return false;
        }

        $this->syncLoadedItem($model);
        $this->bustCatalogCaches($model->id);

        return true;
    }

    public function removeItem(int $itemId): void
    {
        if (Schema::hasTable('ai_product_image_indices')) {
            AiProductImageIndex::query()->where('item_id', $itemId)->delete();
        }

        if (Schema::hasTable('ai_product_indices')) {
            AiProductIndex::query()->where('item_id', $itemId)->delete();
        }

        $this->bustCatalogCaches($itemId);
    }

    public function syncPolicyPages(): int
    {
        if (!Schema::hasTable('knowledge_base')) {
            return 0;
        }

        $pages = [
            'shipping_policy' => ['Delivery Policy', 'policy'],
            'refund' => ['Return/Refund Policy', 'policy'],
            'return_policy' => ['Return Policy', 'policy'],
            'privacy_policy' => ['Privacy Policy', 'policy'],
            'terms_and_conditions' => ['Terms & Conditions', 'policy'],
            'about_us' => ['About Us', 'support'],
            'cash_on_delivery' => ['Cash on Delivery Policy', 'payment'],
            'digital_payment' => ['Payment Policy', 'payment'],
            'free_delivery_status' => ['Free Delivery Rules', 'delivery'],
            'ref_earning_status' => ['Referral Rules', 'wallet_bonus'],
            'loyalty_point_status' => ['Loyalty Rules', 'wallet_bonus'],
            'wallet_status' => ['Wallet Rules', 'wallet_bonus'],
            'phone' => ['Contact Phone', 'support'],
            'email_address' => ['Contact Email', 'support'],
            'address' => ['Contact Address', 'support'],
        ];

        $synced = 0;
        foreach ($pages as $key => [$title, $category]) {
            $raw = BusinessSetting::query()->where('key', $key)->value('value');
            $content = $this->businessSettingText($raw);
            if ($content === '') {
                continue;
            }

            $normalizedKey = 'system-page-' . Str::slug($key);
            KnowledgeBaseEntry::query()->updateOrCreate(
                ['normalized_key' => $normalizedKey],
                [
                    'title' => $title,
                    'source_category' => $category,
                    'source_url' => url('/'),
                    'source_hash' => hash('sha256', $key . '|' . $content),
                    'synced_at' => now(),
                    'question_patterns' => str_replace('_', ' ', $key) . ',' . $title,
                    'tags' => $category . ',system,page,policy',
                    'answer' => $content,
                    'answer_bn' => $content,
                    'answer_en' => $content,
                    'priority' => 80,
                    'is_active' => true,
                ]
            );
            $synced++;
        }

        Cache::forget('ai_business_info');
        Cache::forget('ai_settings');

        return $synced;
    }

    public function findSimilarByUpload(UploadedFile|string $file, int $limit = 3): Collection
    {
        if (!Schema::hasTable('ai_product_image_indices')) {
            return collect();
        }

        if (AiProductImageIndex::query()->count() === 0) {
            $this->syncAllProducts();
        }

        $path = $file instanceof UploadedFile ? $file->getRealPath() : $file;
        $signature = $path ? $this->signatureFromFile($path) : null;
        if (!$signature) {
            return collect();
        }

        return AiProductImageIndex::query()
            ->with(['item' => function ($query) {
                $query->withoutGlobalScopes()
                    ->with(['category:id,name,parent_id', 'unit:id,unit', 'colors:id,name,code', 'storage', 'ecommerce_item_details.brand:id,name'])
                    ->where('status', 1)
                    ->where('is_approved', 1);
            }])
            ->whereNotNull('perceptual_hash')
            ->get()
            ->filter(fn (AiProductImageIndex $index) => $index->item !== null)
            ->map(function (AiProductImageIndex $index) use ($signature) {
                $hashScore = $this->hashSimilarity($signature['perceptual_hash'], (string) $index->perceptual_hash);
                $vectorScore = $this->vectorSimilarity($signature['embedding'] ?? [], $index->embedding ?? []);
                $score = (int) round(max($hashScore, ($hashScore * 0.65) + ($vectorScore * 0.35)));

                $index->confidence_score = $score;
                return $index;
            })
            ->filter(fn (AiProductImageIndex $index) => (int) $index->confidence_score >= 35)
            ->sortByDesc('confidence_score')
            ->take($limit)
            ->values();
    }

    protected function syncLoadedItem(Item $item): void
    {
        $payload = $this->buildProductPayload($item);
        $searchText = implode(' ', array_filter([
            $payload['name'] ?? null,
            $payload['sku'] ?? null,
            $payload['brand'] ?? null,
            $payload['category'] ?? null,
            implode(' ', $payload['colors'] ?? []),
            implode(' ', $payload['variants'] ?? []),
            $payload['unit'] ?? null,
            $payload['short_description'] ?? null,
            $payload['full_description'] ?? null,
        ]));

        $indexPayload = [
            'sku' => $payload['sku'] ?? null,
            'brand' => $payload['brand'] ?? null,
            'category' => $payload['category'] ?? null,
            'search_text' => $searchText,
            'normalized_text' => $this->normalizeText($searchText),
            'payload' => $payload,
            'content_hash' => hash('sha256', json_encode($payload, JSON_UNESCAPED_UNICODE)),
            'indexed_at' => now(),
        ];

        if (Schema::hasColumn('ai_product_indices', 'embedding')) {
            $indexPayload['embedding'] = $this->textEmbedding($searchText);
        }

        AiProductIndex::query()->updateOrCreate(['item_id' => $item->id], $indexPayload);

        $this->syncItemImages($item, $payload['images'] ?? []);
    }

    protected function buildProductPayload(Item $item): array
    {
        $description = trim(strip_tags((string) ($item->description ?? '')));
        $variants = $this->variantTexts($item);
        $sku = $this->firstFilled([
            $item->getAttribute('item_code'),
            $item->getAttribute('code'),
            $item->getAttribute('sku'),
            $this->firstVariantSku($item),
        ]);

        return [
            'id' => $item->id,
            'name' => $item->name,
            'sku' => $sku,
            'brand' => $item->ecommerce_item_details?->brand?->name,
            'category' => $item->category?->name,
            'variants' => $variants,
            'colors' => $item->relationLoaded('colors') ? $item->colors->pluck('name')->filter()->values()->all() : [],
            'unit' => $item->unit?->unit,
            'regular_price' => (float) ($item->price ?? 0),
            'discount' => (float) ($item->discount ?? 0),
            'discount_type' => $item->discount_type,
            'stock' => (int) ($item->stock ?? 0),
            'delivery_charge' => (float) ($item->item_delivery_charge ?? 0),
            'packaging_charge' => (float) ($item->item_packaging_charge ?? 0),
            'free_delivery' => (bool) ($item->free_delivery_badge_status ?? false),
            'short_description' => Str::limit($description, 180),
            'full_description' => $description,
            'images' => $this->imageEntries($item),
            'page_link' => rtrim((string) config('app.front_end_url'), '/') . '/product/' . $item->id,
        ];
    }

    protected function syncItemImages(Item $item, array $images): void
    {
        if (!Schema::hasTable('ai_product_image_indices')) {
            return;
        }

        $seen = [];
        foreach ($images as $image) {
            $key = $image['key'] ?? null;
            if (!$key) {
                continue;
            }
            $seen[] = $key;

            $signature = !empty($image['local_path']) ? $this->signatureFromFile($image['local_path']) : null;
            if (!$signature) {
                continue;
            }

            AiProductImageIndex::query()->updateOrCreate(
                ['item_id' => $item->id, 'image_key' => $key],
                [
                    'image_url' => $image['url'] ?? null,
                    'local_path' => $image['local_path'] ?? null,
                    'perceptual_hash' => $signature['perceptual_hash'],
                    'embedding' => $signature['embedding'],
                    'width' => $signature['width'] ?? null,
                    'height' => $signature['height'] ?? null,
                    'content_hash' => $signature['content_hash'] ?? null,
                    'indexed_at' => now(),
                ]
            );
        }

        if (!empty($seen)) {
            AiProductImageIndex::query()
                ->where('item_id', $item->id)
                ->whereNotIn('image_key', $seen)
                ->delete();
        }
    }

    protected function imageEntries(Item $item): array
    {
        $entries = [];
        if (filled($item->image)) {
            $entries[] = [
                'key' => 'main:' . $item->image,
                'url' => $item->image_full_url,
                'local_path' => $this->localProductImagePath((string) $item->image),
            ];
        }

        $rawImages = is_array($item->images)
            ? $item->images
            : (is_string($item->images) ? json_decode($item->images, true) : []);

        foreach (($rawImages ?: []) as $index => $rawImage) {
            $file = is_array($rawImage)
                ? ($rawImage['img'] ?? $rawImage['image_name'] ?? $rawImage['name'] ?? null)
                : (string) $rawImage;
            if (!filled($file)) {
                continue;
            }
            $entries[] = [
                'key' => 'gallery:' . $file,
                'url' => $item->images_full_url[$index] ?? null,
                'local_path' => $this->localProductImagePath((string) $file),
            ];
        }

        return collect($entries)->unique('key')->values()->all();
    }

    public function signatureFromFile(string $path): ?array
    {
        if (!is_file($path) || !function_exists('imagecreatefromstring')) {
            return null;
        }

        $bytes = @file_get_contents($path);
        if ($bytes === false || $bytes === '') {
            return null;
        }

        $source = @imagecreatefromstring($bytes);
        if (!$source) {
            return null;
        }

        try {
            $width = imagesx($source);
            $height = imagesy($source);
            $hash = $this->averageHash($source);
            $embedding = $this->colorEmbedding($source);

            return [
                'perceptual_hash' => $hash,
                'embedding' => $embedding,
                'width' => $width,
                'height' => $height,
                'content_hash' => hash('sha256', $bytes),
            ];
        } finally {
            imagedestroy($source);
        }
    }

    protected function averageHash($source): string
    {
        $thumb = imagecreatetruecolor(8, 8);
        imagecopyresampled($thumb, $source, 0, 0, 0, 0, 8, 8, imagesx($source), imagesy($source));

        $values = [];
        for ($y = 0; $y < 8; $y++) {
            for ($x = 0; $x < 8; $x++) {
                $rgb = imagecolorat($thumb, $x, $y);
                $r = ($rgb >> 16) & 0xFF;
                $g = ($rgb >> 8) & 0xFF;
                $b = $rgb & 0xFF;
                $values[] = (int) round(($r + $g + $b) / 3);
            }
        }

        imagedestroy($thumb);

        $average = array_sum($values) / max(1, count($values));
        $bits = '';
        foreach ($values as $value) {
            $bits .= $value >= $average ? '1' : '0';
        }

        $hex = '';
        foreach (str_split($bits, 4) as $chunk) {
            $hex .= dechex(bindec($chunk));
        }

        return str_pad($hex, 16, '0', STR_PAD_LEFT);
    }

    protected function colorEmbedding($source): array
    {
        $thumb = imagecreatetruecolor(4, 4);
        imagecopyresampled($thumb, $source, 0, 0, 0, 0, 4, 4, imagesx($source), imagesy($source));

        $values = [];
        for ($y = 0; $y < 4; $y++) {
            for ($x = 0; $x < 4; $x++) {
                $rgb = imagecolorat($thumb, $x, $y);
                $values[] = round((($rgb >> 16) & 0xFF) / 255, 4);
                $values[] = round((($rgb >> 8) & 0xFF) / 255, 4);
                $values[] = round(($rgb & 0xFF) / 255, 4);
            }
        }

        imagedestroy($thumb);

        return $values;
    }

    protected function hashSimilarity(string $a, string $b): float
    {
        if ($a === '' || $b === '' || strlen($a) !== strlen($b)) {
            return 0.0;
        }

        $distance = 0;
        for ($i = 0; $i < strlen($a); $i++) {
            $distance += substr_count(decbin(hexdec($a[$i]) ^ hexdec($b[$i])), '1');
        }

        return max(0, (64 - $distance) / 64 * 100);
    }

    protected function vectorSimilarity(array $a, array $b): float
    {
        if (empty($a) || empty($b) || count($a) !== count($b)) {
            return 0.0;
        }

        $dot = $normA = $normB = 0.0;
        foreach ($a as $i => $value) {
            $x = (float) $value;
            $y = (float) ($b[$i] ?? 0);
            $dot += $x * $y;
            $normA += $x * $x;
            $normB += $y * $y;
        }

        if ($normA <= 0 || $normB <= 0) {
            return 0.0;
        }

        return max(0, min(100, ($dot / (sqrt($normA) * sqrt($normB))) * 100));
    }

    public function textEmbeddingForText(string $text): array
    {
        return $this->textEmbedding($text);
    }

    public function textVectorSimilarity(array $a, array $b): float
    {
        return $this->vectorSimilarity($a, $b);
    }

    protected function textEmbedding(string $text): array
    {
        $tokens = array_values(array_filter(explode(' ', $this->normalizeText($text)), static fn ($token) => mb_strlen($token) >= 2));
        $vector = array_fill(0, 64, 0.0);

        foreach ($tokens as $token) {
            $index = (int) (sprintf('%u', crc32($token)) % 64);
            $vector[$index] += 1.0;

            if (preg_match('/\d/u', $token)) {
                $modelIndex = (int) (sprintf('%u', crc32(preg_replace('/\s+/u', '', $token))) % 64);
                $vector[$modelIndex] += 1.5;
            }
        }

        $norm = sqrt(array_sum(array_map(static fn ($value) => $value * $value, $vector)));
        if ($norm <= 0) {
            return $vector;
        }

        return array_map(static fn ($value) => round($value / $norm, 6), $vector);
    }

    protected function localProductImagePath(string $file): ?string
    {
        $path = storage_path('app/public/product/' . ltrim($file, '/'));
        return is_file($path) ? $path : null;
    }

    protected function variantTexts(Item $item): array
    {
        $texts = [];
        foreach (['variations', 'food_variations', 'choice_options'] as $field) {
            foreach ($this->decodeJsonArray($item->getAttribute($field)) as $entry) {
                $texts[] = trim(implode(' ', array_filter([
                    data_get($entry, 'type'),
                    data_get($entry, 'name'),
                    data_get($entry, 'title'),
                    data_get($entry, 'sku'),
                    is_array(data_get($entry, 'options')) ? implode(' ', data_get($entry, 'options')) : null,
                ])));
            }
        }

        return collect($texts)->filter()->unique()->values()->all();
    }

    protected function firstVariantSku(Item $item): ?string
    {
        foreach (['variations', 'food_variations'] as $field) {
            foreach ($this->decodeJsonArray($item->getAttribute($field)) as $entry) {
                $sku = data_get($entry, 'sku');
                if (filled($sku)) {
                    return (string) $sku;
                }
            }
        }

        return null;
    }

    protected function decodeJsonArray(mixed $value): array
    {
        if (is_array($value)) {
            return $value;
        }
        if (is_string($value) && $value !== '') {
            $decoded = json_decode($value, true);
            return is_array($decoded) ? $decoded : [];
        }

        return [];
    }

    protected function businessSettingText(mixed $value): string
    {
        if ($value === null) {
            return '';
        }

        $decoded = json_decode((string) $value, true);
        if (json_last_error() === JSON_ERROR_NONE) {
            if (is_array($decoded)) {
                $flattened = collect($decoded)->flatten()->filter(fn ($item) => is_scalar($item))->implode(' ');
                return trim(strip_tags($flattened));
            }

            if (is_scalar($decoded)) {
                return trim(strip_tags((string) $decoded));
            }
        }

        return trim(strip_tags((string) $value));
    }

    protected function normalizeText(?string $text): string
    {
        return Str::of((string) $text)
            ->lower()
            ->replaceMatches('/[^\p{L}\p{M}\p{N}\s]+/u', ' ')
            ->replaceMatches('/\s+/u', ' ')
            ->trim()
            ->value();
    }

    protected function firstFilled(array $values): ?string
    {
        foreach ($values as $value) {
            if (filled($value)) {
                return (string) $value;
            }
        }

        return null;
    }

    protected function bustCatalogCaches(?int $itemId = null): void
    {
        Cache::forget('ai_catalog_brands');
        Cache::forget('ai_catalog_categories');
        Cache::put('ai_product_cache_version', (string) time(), now()->addDays(7));
        if ($itemId) {
            Cache::forget('ai_product_card_' . $itemId);
        }
    }
}
