<?php
namespace App\Services;

use App\Models\AiClassification;
use App\Models\AiKeywordRule;
use App\Models\KbArticle;
use App\Models\Ticket;
use App\Models\TicketCategory;
use App\Models\TicketPriority;

class AiClassifierService
{
    // ── Clasificar un ticket ──────────────────────────────────────────────────
    public function classify(Ticket $ticket): AiClassification
    {
        $text     = $this->normalizeText($ticket->title . ' ' . $ticket->description);
        $keywords = $this->extractKeywords($text);

        [$categoryId, $catConf] = $this->matchEntity('category', $text, $keywords);
        [$priorityId, $priConf] = $this->matchEntity('priority', $text, $keywords);
        $kbSuggestions          = $this->suggestKb($text, $keywords);

        return AiClassification::updateOrCreate(
            ['ticket_id' => $ticket->id],
            [
                'suggested_category_id' => $categoryId,
                'suggested_priority_id' => $priorityId,
                'category_confidence'   => $catConf,
                'priority_confidence'   => $priConf,
                'kb_suggestions'        => $kbSuggestions,
                'keywords_detected'     => array_slice($keywords, 0, 15),
                'applied'               => false,
            ]
        );
    }

    // ── Sugerir artículos KB para un texto libre (usado en creación de ticket) ─
    public function suggestKbForText(string $text, int $limit = 5): array
    {
        $normalized = $this->normalizeText($text);
        $keywords   = $this->extractKeywords($normalized);
        return $this->suggestKb($normalized, $keywords, $limit);
    }

    // ── Aceptar sugerencia (feedback positivo) ────────────────────────────────
    public function applySuggestion(AiClassification $classification): void
    {
        if ($classification->applied) return;

        // Reforzar los pesos de las reglas que acertaron
        $keywords = $classification->keywords_detected ?? [];
        if ($classification->suggested_category_id) {
            AiKeywordRule::active()->category()
                ->where('entity_id', $classification->suggested_category_id)
                ->whereIn('keyword', $keywords)
                ->each(fn($r) => $r->increment('hits') && $r->update(['weight' => min(5.0, $r->weight + 0.1)]));
        }
        if ($classification->suggested_priority_id) {
            AiKeywordRule::active()->priority()
                ->where('entity_id', $classification->suggested_priority_id)
                ->whereIn('keyword', $keywords)
                ->each(fn($r) => $r->increment('hits') && $r->update(['weight' => min(5.0, $r->weight + 0.1)]));
        }

        $classification->update(['applied' => true]);
    }

    // ── Rechazar sugerencia (feedback negativo) ───────────────────────────────
    public function rejectSuggestion(AiClassification $classification, ?int $correctCategoryId = null, ?int $correctPriorityId = null): void
    {
        $keywords = $classification->keywords_detected ?? [];

        // Reducir peso de reglas incorrectas
        if ($classification->suggested_category_id && $classification->suggested_category_id !== $correctCategoryId) {
            AiKeywordRule::active()->category()
                ->where('entity_id', $classification->suggested_category_id)
                ->whereIn('keyword', $keywords)
                ->each(fn($r) => $r->update(['weight' => max(0.1, $r->weight - 0.15)]));
        }

        // Aprender la corrección: agregar regla si no existe
        if ($correctCategoryId && $correctCategoryId !== $classification->suggested_category_id) {
            foreach (array_slice($keywords, 0, 5) as $kw) {
                AiKeywordRule::firstOrCreate(
                    ['keyword' => $kw, 'entity_type' => 'category', 'entity_id' => $correctCategoryId],
                    ['weight' => 1.0, 'active' => true]
                );
            }
        }

        $classification->update(['applied' => false]);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Métodos privados
    // ─────────────────────────────────────────────────────────────────────────

    private function normalizeText(string $text): string
    {
        $text = mb_strtolower($text);
        $text = preg_replace('/[^\p{L}\p{N}\s]/u', ' ', $text);
        $text = preg_replace('/\s+/', ' ', trim($text));
        return $text;
    }

    private function extractKeywords(string $text): array
    {
        // Stopwords en español
        $stopwords = ['de','la','el','en','un','una','que','es','se','no','con','por','para','su','al','del','los','las','le','lo','a','y','o','pero','si','como','mas','ya','hay','esto','esta','este','son','ser','fue','era','han','has','sus','mi','me','te','tu','nos','les','muy','bien','mal','todo','todos','puede','cuando','donde','quien','tengo','tiene','esta','estoy','estamos'];
        
        $words = explode(' ', $text);
        $keywords = [];
        
        foreach ($words as $word) {
            $word = trim($word);
            if (strlen($word) >= 4 && !in_array($word, $stopwords)) {
                $keywords[] = $word;
            }
        }
        
        // N-gramas de 2 palabras más significativas
        $words_clean = array_values(array_filter($words, fn($w) => strlen($w) >= 4 && !in_array($w, $stopwords)));
        for ($i = 0; $i < count($words_clean) - 1; $i++) {
            $bigram = $words_clean[$i] . ' ' . $words_clean[$i+1];
            $keywords[] = $bigram;
        }
        
        return array_unique($keywords);
    }

    private function matchEntity(string $type, string $text, array $keywords): array
    {
        $rules = AiKeywordRule::active()
            ->where('entity_type', $type)
            ->get()
            ->groupBy('entity_id');

        $scores = [];

        foreach ($rules as $entityId => $entityRules) {
            $score = 0.0;
            foreach ($entityRules as $rule) {
                $kw = $rule->keyword;
                // Match exacto en keywords extraídas
                if (in_array($kw, $keywords)) {
                    $score += $rule->weight * 2.0;
                }
                // Match parcial en texto completo
                elseif (str_contains($text, $kw)) {
                    $score += $rule->weight * 1.0;
                }
            }
            if ($score > 0) {
                $scores[$entityId] = $score;
            }
        }

        if (empty($scores)) return [null, 0.0];

        arsort($scores);
        $topId    = array_key_first($scores);
        $topScore = $scores[$topId];
        $total    = array_sum($scores);

        // Confianza = score del ganador / total de scores * factor normalización
        $confidence = min(100, round(($topScore / max(1, $total)) * 100 * 1.5));

        return [$topId, $confidence];
    }

    private function suggestKb(string $text, array $keywords, int $limit = 5): array
    {
        // Usar el método search del modelo KbArticle (LIKE-based)
        if (empty($keywords)) return [];

        // Tomar las 3 keywords más largas (más específicas)
        usort($keywords, fn($a,$b) => strlen($b) - strlen($a));
        $topKeywords = array_slice($keywords, 0, 5);

        $articles = KbArticle::published()
            ->where(function($q) use ($topKeywords) {
                foreach ($topKeywords as $kw) {
                    $q->orWhere('title',   'like', '%'.$kw.'%')
                      ->orWhere('excerpt', 'like', '%'.$kw.'%')
                      ->orWhere('tags',    'like', '%'.$kw.'%');
                }
            })
            ->limit($limit * 2)
            ->get(['id','title','slug','excerpt','tags','views','helpful_yes','helpful_no']);

        if ($articles->isEmpty()) return [];

        // Scoring de relevancia
        $scored = $articles->map(function($article) use ($text, $keywords) {
            $score = 0;
            $articleText = mb_strtolower($article->title . ' ' . $article->excerpt . ' ' . $article->tags);

            foreach ($keywords as $kw) {
                if (str_contains(mb_strtolower($article->title), $kw))   $score += 3;
                if (str_contains($articleText, $kw))                      $score += 1;
            }

            // Boost por popularidad
            $score += min(2, $article->views / 10);
            $total  = $article->helpful_yes + $article->helpful_no;
            if ($total > 0) $score += ($article->helpful_yes / $total) * 2;

            return ['article' => $article, 'score' => round($score, 2)];
        })->sortByDesc('score')->take($limit);

        return $scored->map(fn($item) => [
            'id'      => $item['article']->id,
            'title'   => $item['article']->title,
            'slug'    => $item['article']->slug,
            'excerpt' => $item['article']->excerpt,
            'score'   => $item['score'],
            'url'     => route('kb.show', $item['article']->slug),
        ])->values()->toArray();
    }
}
