<?php

namespace App\Services;

use App\Models\BiataBotConversation;
use App\Models\Product;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Throwable;

class BiataBotAnalysisService
{
    private const ENDPOINT = 'https://api.gapgpt.app/v1/chat/completions';

    /** @var list<string> */
    private const STOP_WORDS = [
        'آیا', 'ایا', 'دارید', 'داریم', 'داره', 'هست', 'هستش', 'موجود', 'موجوده',
        'میخوام', 'می‌خوام', 'میخواهم', 'می‌خواهم', 'لطفا', 'لطفاً', 'سلام',
        'مدل', 'محصول', 'کالا', 'چی', 'چه', 'را', 'رو', 'به', 'از', 'با', 'که',
        'یک', 'یه', 'این', 'اون', 'برای', 'تو', 'در', 'و', 'یا', 'هم', 'همه',
        'دارین', 'فروشگاه', 'سایت', 'میشه', 'میشود', 'کنید', 'بده', 'بگو',
        'قیمت', 'چنده', 'چقدر', 'ممنون', 'مرسی', 'خوبه', 'عالیه',
    ];

    /**
     * @return array{
     *   topics: list<array{title:string, message_ids: list<int>}>,
     *   products: list<array{title:string, url:?string, slug:?string, message_ids: list<int>}>,
     *   source: string
     * }
     */
    public function analyze(BiataBotConversation $conversation): array
    {
        $conversation->loadMissing(['messages' => fn ($q) => $q->orderBy('id')]);

        $cacheKey = 'biata_bot_analysis_v2_' . $conversation->id . '_' . $conversation->messages_count;

        return Cache::remember($cacheKey, 3600, function () use ($conversation) {
            $userMessages = $conversation->messages
                ->where('role', 'user')
                ->filter(fn ($m) => trim((string) $m->content) !== '')
                ->values();

            $userTexts = $userMessages->pluck('content')->all();
            $userMsgIds = $userMessages->pluck('id')->map(fn ($id) => (int) $id)->all();

            $products = $this->findMentionedProducts($conversation, $userMessages);
            $topics   = $this->extractTopics($userTexts, $userMsgIds, $products);

            return [
                'topics'   => $topics,
                'products' => $products,
                'source'   => 'live',
            ];
        });
    }

    /**
     * @param \Illuminate\Support\Collection<int, \App\Models\BiataBotMessage> $userMessages
     * @return list<array{title:string,url:?string,slug:?string,message_ids:list<int>}>
     */
    private function findMentionedProducts(BiataBotConversation $conversation, $userMessages): array
    {
        $found = [];

        // 1) Products linked in any message (bot or user)
        foreach ($conversation->messages as $msg) {
            if (preg_match_all('#/product/([^\s\)\]"\'<>]+)#u', $msg->content, $m)) {
                foreach ($m[1] as $slugRaw) {
                    $slug = urldecode(rtrim($slugRaw, ".,)"));
                    $slug = $this->toEnglishDigits($slug);
                    $product = Product::where('slug', $slug)->first();
                    if ($product) {
                        if (!isset($found[$product->id])) {
                            $found[$product->id] = [
                                'title'       => $product->title,
                                'url'         => route('product.show', $product->slug),
                                'slug'        => $product->slug,
                                'message_ids' => [],
                            ];
                        }
                        $found[$product->id]['message_ids'][] = (int) $msg->id;
                    }
                }
            }
        }

        // 2) Search catalog against each user message
        foreach ($userMessages as $msg) {
            foreach ($this->searchProducts((string) $msg->content, 8) as $row) {
                if (!isset($found[$row['id']])) {
                    $found[$row['id']] = [
                        'title'       => $row['title'],
                        'url'         => $row['url'],
                        'slug'        => $row['slug'],
                        'message_ids' => [],
                    ];
                }
                $found[$row['id']]['message_ids'][] = (int) $msg->id;
            }
        }

        return array_values(array_map(function (array $row) {
            $row['message_ids'] = array_values(array_unique($row['message_ids']));

            return $row;
        }, $found));
    }

    /**
     * @return list<array{id:int,title:string,url:string,slug:string,score:int}>
     */
    private function searchProducts(string $message, int $limit = 8): array
    {
        $normalized = $this->normalizeSearchText($message);
        $tokens     = $this->extractTokens($normalized);

        if ($normalized === '' && $tokens === []) {
            return [];
        }

        $query = Product::query()->where('is_active', true);

        $query->where(function ($outer) use ($normalized, $tokens) {
            if ($normalized !== '') {
                $outer->where(function ($q) use ($normalized) {
                    $q->where('title', 'like', "%{$normalized}%")
                      ->orWhere('title_en', 'like', "%{$normalized}%")
                      ->orWhere('tags', 'like', "%{$normalized}%");
                });
            }
            foreach ($tokens as $token) {
                $outer->orWhere(function ($q) use ($token) {
                    $q->where('title', 'like', "%{$token}%")
                      ->orWhere('title_en', 'like', "%{$token}%")
                      ->orWhere('tags', 'like', "%{$token}%");
                });
            }
        });

        $products = $query->limit(60)->get(['id', 'title', 'title_en', 'tags', 'slug']);

        return $products->map(function ($p) use ($normalized, $tokens) {
            $haystack = mb_strtolower(trim(
                ($p->title ?? '') . ' ' . ($p->title_en ?? '') . ' ' . ($p->tags ?? '')
            ));
            $score = 0;
            if ($normalized !== '' && mb_strpos($haystack, mb_strtolower($normalized)) !== false) {
                $score += 100;
            }
            foreach ($tokens as $token) {
                if (mb_strpos($haystack, mb_strtolower($token)) !== false) {
                    $score += 10 + min(5, mb_strlen($token));
                }
            }
            // Need at least one meaningful token match (avoid ultra-broad hits)
            if ($score < 15) {
                return null;
            }

            return [
                'id'    => $p->id,
                'title' => $p->title,
                'url'   => route('product.show', $p->slug),
                'slug'  => $p->slug,
                'score' => $score,
            ];
        })
            ->filter()
            ->sortByDesc('score')
            ->take($limit)
            ->values()
            ->all();
    }

    /**
     * @param list<string> $userTexts
     * @param list<int> $userMsgIds
     * @param list<array{title:string,url:?string,slug:?string,message_ids:list<int>}> $products
     * @return list<array{title:string, message_ids: list<int>}>
     */
    private function extractTopics(array $userTexts, array $userMsgIds, array $products): array
    {
        if ($userTexts === []) {
            return [];
        }

        $aiTopics = $this->extractTopicsWithAi($userTexts, $userMsgIds);
        if ($aiTopics !== []) {
            return $aiTopics;
        }

        return $this->extractTopicsHeuristic($userTexts, $userMsgIds, $products);
    }

    /**
     * @param list<string> $userTexts
     * @param list<int> $userMsgIds
     * @return list<array{title:string, message_ids: list<int>}>
     */
    private function extractTopicsWithAi(array $userTexts, array $userMsgIds): array
    {
        $apiKey = GapGptService::apiKey();
        $model  = GapGptService::biataBotModel();
        if (!$apiKey) {
            return [];
        }

        $joined = '';
        foreach ($userTexts as $i => $text) {
            $joined .= ($i + 1) . '. ' . mb_substr(trim($text), 0, 500) . "\n";
        }
        $joined = mb_substr($joined, 0, 3500);

        $system = <<<'TXT'
تو یک تحلیل‌گر گفتگوی فروشگاهی هستی.
فقط JSON خالص برگردان، بدون توضیح اضافه.
فرمت:
{"topics":[{"title":"موضوع ۱","messages":[1,3]},{"title":"موضوع ۲","messages":[2]}]}
قوانین:
- حداکثر ۶ موضوع اصلی که کاربر مطرح کرده
- هر موضوع یک تیتر کوتاه فارسی (۳ تا ۸ کلمه)
- فیلد messages شماره پیام‌های کاربر مرتبط است (همان شماره‌های لیست ورودی، از ۱)
- موضوعات کلی مثل «سلام» یا «تشکر» نیاور
- اگر موضوع خاصی نبود، topics را آرایه خالی بگذار
TXT;

        try {
            $response = Http::withHeaders([
                'Authorization' => 'Bearer ' . $apiKey,
                'Content-Type'  => 'application/json',
            ])
                ->timeout(25)
                ->connectTimeout(10)
                ->post(self::ENDPOINT, [
                    'model'       => $model,
                    'messages'    => [
                        ['role' => 'system', 'content' => $system],
                        ['role' => 'user', 'content' => "پیام‌های کاربر:\n" . $joined],
                    ],
                    'temperature' => 0.2,
                    'max_tokens'  => 500,
                ]);

            if (!$response->successful()) {
                return [];
            }

            $content = (string) data_get($response->json(), 'choices.0.message.content', '');
            $content = trim($content);
            if ($content === '') {
                return [];
            }

            if (preg_match('/\{.*\}/s', $content, $m)) {
                $content = $m[0];
            }

            $json = json_decode($content, true);
            if (!is_array($json) || !isset($json['topics']) || !is_array($json['topics'])) {
                return [];
            }

            $topics = [];
            foreach ($json['topics'] as $topic) {
                // Backward-compatible: plain string topics
                if (is_string($topic)) {
                    $title = trim($topic);
                    $indices = [];
                } else {
                    $title = trim((string) ($topic['title'] ?? ''));
                    $indices = is_array($topic['messages'] ?? null) ? $topic['messages'] : [];
                }

                if ($title === '' || mb_strlen($title) < 3) {
                    continue;
                }

                $messageIds = [];
                foreach ($indices as $idx) {
                    $i = (int) $idx - 1;
                    if (isset($userMsgIds[$i])) {
                        $messageIds[] = (int) $userMsgIds[$i];
                    }
                }

                // Fallback: match topic keywords against user texts
                if ($messageIds === []) {
                    $messageIds = $this->guessMessageIdsForTopic($title, $userTexts, $userMsgIds);
                }

                $topics[] = [
                    'title'       => mb_substr($title, 0, 80),
                    'message_ids' => array_values(array_unique($messageIds)),
                ];
                if (count($topics) >= 6) {
                    break;
                }
            }

            return $topics;
        } catch (Throwable) {
            return [];
        }
    }

    /**
     * @param list<string> $userTexts
     * @param list<int> $userMsgIds
     * @return list<int>
     */
    private function guessMessageIdsForTopic(string $title, array $userTexts, array $userMsgIds): array
    {
        $tokens = $this->extractTokens($this->normalizeSearchText($title));
        if ($tokens === []) {
            return isset($userMsgIds[0]) ? [(int) $userMsgIds[0]] : [];
        }

        $bestId = null;
        $bestScore = 0;
        foreach ($userTexts as $i => $text) {
            $hay = mb_strtolower($text);
            $score = 0;
            foreach ($tokens as $token) {
                if (mb_strpos($hay, mb_strtolower($token)) !== false) {
                    $score += 1;
                }
            }
            if ($score > $bestScore && isset($userMsgIds[$i])) {
                $bestScore = $score;
                $bestId = (int) $userMsgIds[$i];
            }
        }

        return $bestId ? [$bestId] : (isset($userMsgIds[0]) ? [(int) $userMsgIds[0]] : []);
    }

    /**
     * @param list<string> $userTexts
     * @param list<int> $userMsgIds
     * @param list<array{title:string,url:?string,slug:?string,message_ids:list<int>}> $products
     * @return list<array{title:string, message_ids: list<int>}>
     */
    private function extractTopicsHeuristic(array $userTexts, array $userMsgIds, array $products): array
    {
        $topics = [];

        foreach ($products as $p) {
            $topics[] = [
                'title'       => 'سوال دربارهٔ ' . $p['title'],
                'message_ids' => $p['message_ids'] ?? [],
            ];
        }

        foreach ($userTexts as $i => $text) {
            $norm = $this->normalizeSearchText($text);
            if ($norm === '' || mb_strlen($norm) < 6) {
                continue;
            }
            $covered = false;
            foreach ($products as $p) {
                $titleBits = $this->extractTokens($this->normalizeSearchText($p['title']));
                $hit = 0;
                foreach ($titleBits as $bit) {
                    if (mb_strpos(mb_strtolower($norm), mb_strtolower($bit)) !== false) {
                        $hit++;
                    }
                }
                if ($hit >= 2) {
                    $covered = true;
                    break;
                }
            }
            if ($covered) {
                continue;
            }
            $topics[] = [
                'title'       => mb_substr($norm, 0, 60),
                'message_ids' => isset($userMsgIds[$i]) ? [(int) $userMsgIds[$i]] : [],
            ];
        }

        return array_slice($topics, 0, 6);
    }

    private function normalizeSearchText(string $message): string
    {
        $text = $this->toEnglishDigits($message);
        $text = preg_replace('/[؟?!.،,;؛:()\[\]{}"«»]/u', ' ', $text) ?? $text;
        $text = preg_replace('/\s+/u', ' ', trim($text)) ?? trim($text);

        $words = preg_split('/\s+/u', $text, -1, PREG_SPLIT_NO_EMPTY) ?: [];
        $kept  = [];
        foreach ($words as $w) {
            $lower = mb_strtolower($w);
            if (in_array($lower, self::STOP_WORDS, true) || mb_strlen($w) < 2) {
                continue;
            }
            $kept[] = $w;
        }

        return implode(' ', $kept);
    }

    /**
     * @return list<string>
     */
    private function extractTokens(string $normalized): array
    {
        if ($normalized === '') {
            return [];
        }
        $parts = preg_split('/\s+/u', $normalized, -1, PREG_SPLIT_NO_EMPTY) ?: [];
        $tokens = [];
        foreach ($parts as $part) {
            if (mb_strlen($part) >= 2) {
                $tokens[] = $part;
            }
        }

        return array_values(array_unique($tokens));
    }

    private function toEnglishDigits(string $value): string
    {
        return strtr($value, [
            '۰' => '0', '۱' => '1', '۲' => '2', '۳' => '3', '۴' => '4',
            '۵' => '5', '۶' => '6', '۷' => '7', '۸' => '8', '۹' => '9',
            '٠' => '0', '١' => '1', '٢' => '2', '٣' => '3', '٤' => '4',
            '٥' => '5', '٦' => '6', '٧' => '7', '٨' => '8', '٩' => '9',
        ]);
    }
}
