"""Prompt assembly and management"""

import logging
from typing import Dict, Any
from .context import context_manager

logger = logging.getLogger(__name__)

# Estimation grossière : ~1.3 tokens par mot
TOKENS_PER_WORD = 1.3


def _estimate_tokens(text: str) -> int:
    return int(len(text.split()) * TOKENS_PER_WORD)


class PromptBuilder:
    """Builds prompts for LLM requests"""

    def __init__(self):
        self.base_rules = (
            "Réponds en français, concis (2-3 phrases). "
            "Évite politique, santé, argent, données personnelles."
        )

    def build_prompt(
        self, user_input: str, system_prompt: str, model: str, max_tokens: int
    ) -> str:
        """Build complete prompt with system, rules, context and user input"""

        # Contexte conversationnel
        context_text = context_manager.get_context_for_model(model)

        # Sections de base
        sections = [
            f"[SYSTEM] {system_prompt}".strip(),
            f"[RULES] {self.base_rules}".strip(),
        ]

        if context_text:
            sections.append(f"[CONTEXT] {context_text}".strip())

        sections.append(f"[USER] {user_input}".strip())

        full_prompt = "\n".join(sections)

        # Budget d'entrée (on réserve ~30% à la sortie)
        max_input_tokens = int(max_tokens * 0.7)

        estimated_tokens = _estimate_tokens(full_prompt)
        if estimated_tokens > max_input_tokens:
            logger.warning(
                f"Prompt too long ({float(estimated_tokens)} tokens), truncating"
            )
            full_prompt = self._truncate_prompt(full_prompt, max_input_tokens)

        return full_prompt

    def _truncate_prompt(self, prompt: str, max_tokens: int) -> str:
        """Tronque le prompt pour respecter le budget de tokens d'entrée.

        Stratégie :
        1) Garder [SYSTEM] + [RULES] en priorité.
        2) Ajouter autant de [CONTEXT] que possible.
        3) Tronquer la section [USER] pour rentrer dans le budget.
        """
        lines = [ln for ln in prompt.split("\n") if ln.strip()]
        system_lines = [ln for ln in lines if ln.startswith("[SYSTEM]")]
        rules_lines = [ln for ln in lines if ln.startswith("[RULES]")]
        context_lines = [ln for ln in lines if ln.startswith("[CONTEXT]")]
        user_lines = [ln for ln in lines if ln.startswith("[USER]")]

        system = system_lines[0] if system_lines else ""
        rules = rules_lines[0] if rules_lines else ""
        user = user_lines[-1] if user_lines else "[USER]"

        # Base : SYSTEM + RULES
        result_sections = [s for s in (system, rules) if s]
        current_tokens = _estimate_tokens("\n".join(result_sections))

        # Ajouter le contexte tant que ça passe
        kept_context = []
        for ctx in context_lines:
            ctx_tokens = _estimate_tokens(ctx)
            if current_tokens + ctx_tokens <= max_tokens:
                kept_context.append(ctx)
                current_tokens += ctx_tokens
            else:
                break

        result_sections.extend(kept_context)

        # Calcul du budget restant pour [USER]
        # On prend uniquement le contenu (sans l'étiquette) pour le tronquer finement
        user_prefix = "[USER]"
        user_text = (
            user[len(user_prefix) :].strip() if user.startswith(user_prefix) else user
        )
        remaining_tokens = max_tokens - current_tokens

        if remaining_tokens <= 0:
            # Pas de place : garder un minimum symbolique
            truncated_user = f"{user_prefix} (abrégé)"
        else:
            # Nombre de mots autorisés, estimation inverse
            allowed_words = max(5, int(remaining_tokens / TOKENS_PER_WORD))
            words = user_text.split()
            if len(words) > allowed_words:
                words = words[:allowed_words]
            truncated_user = f"{user_prefix} {' '.join(words).strip()}"

        result_sections.append(truncated_user)

        final_prompt = "\n".join([s for s in result_sections if s.strip()])

        # Sécurité : si on dépasse encore, on coupe dur la fin de [USER]
        if _estimate_tokens(final_prompt) > max_tokens:
            # Re-tronque le [USER] encore plus si nécessaire
            base = "\n".join([s for s in result_sections[:-1] if s.strip()])
            remaining = max(0, max_tokens - _estimate_tokens(base))
            user_words = (truncated_user[len(user_prefix) :].strip()).split()
            hard_allowed = max(3, int(remaining / TOKENS_PER_WORD))
            user_words = user_words[:hard_allowed]
            final_prompt = base + "\n" + f"{user_prefix} {' '.join(user_words).strip()}"

        return final_prompt

    def extract_user_intent(self, user_input: str) -> Dict[str, Any]:
        """Détecte l'intention et catégorise les sujets sensibles.

        Catégories normalisées:
        - 'argent'     (prix, coût/coûte, combien, €, $, salaire, etc.)
        - 'politique'  (politique, élection(s), voter, vote, gouvernement, macron, président, ...)
        - 'santé'      (santé, mal, douleur, dos, thoraciques, médecin, symptômes, ...)
        - 'personnel'  (privé, adresse, téléphone, email, identité, ...)
        """
        import unicodedata

        def normalize(s: str) -> str:
            # minuscule + suppression des accents (compat entrées “élections”, “coute”, etc.)
            s = s.lower()
            s = unicodedata.normalize("NFKD", s)
            return "".join(ch for ch in s if not unicodedata.combining(ch))

        intent: Dict[str, Any] = {
            "type": "general",
            "confidence": 1.0,
            "sensitive": False,
        }

        text = user_input
        low = text.lower()
        norm = normalize(text)

        # Mots-clés élargis (on met versions accentuées ET normalisées)
        argent_kw = {
            "argent",
            "euro",
            "€",
            "eur",
            "dollar",
            "$",
            "prix",
            "coût",
            "coûte",
            "cout",
            "coute",
            "tarif",
            "budget",
            "combien",
            "combien ça coûte",
            "combien ca coute",
            "salaire",
            "revenu",
            "honoraires",
        }
        politique_kw = {
            "politique",
            "élection",
            "élections",
            "election",
            "elections",
            "voter",
            "vote",
            "gouvernement",
            "parti",
            "macron",
            "président",
            "president",
            "ministre",
            "assemblée",
            "senat",
            "sénat",
        }
        sante_kw = {
            "santé",
            "sante",
            "mal",
            "douleur",
            "douleurs",
            "dos",
            "mal au dos",
            "thoracique",
            "thoraciques",
            "symptôme",
            "symptomes",
            "symptôme",
            "symptômes",
            "médecin",
            "medecin",
            "médicament",
            "medicament",
            "traitement",
        }
        personnel_kw = {
            "personnel",
            "privé",
            "prive",
            "confidentiel",
            "adresse",
            "téléphone",
            "telephone",
            "email",
            "mail",
            "identité",
            "identite",
            "carte",
            "numéro",
            "numero",
        }

        def matches_any(keywords: set[str]) -> bool:
            # on teste dans la version d'origine (minuscule) ET normalisée
            for k in keywords:
                k_norm = normalize(k)
                if k in low or k_norm in norm:
                    return True
            return False

        # Détection par catégorie (ordre important)
        if matches_any(argent_kw):
            intent["sensitive"] = True
            intent["sensitive_topic"] = "argent"
        elif matches_any(politique_kw):
            intent["sensitive"] = True
            intent["sensitive_topic"] = "politique"
        elif matches_any(sante_kw):
            intent["sensitive"] = True
            intent["sensitive_topic"] = "santé"
        elif matches_any(personnel_kw):
            intent["sensitive"] = True
            intent["sensitive_topic"] = "personnel"

        # Types d'intention (annote, sans changer la sensibilité)
        if any(w in low for w in ["blague", "humour", "drôle", "drole", "rire"]):
            intent["type"] = "humor"
        elif any(
            w in low
            for w in ["présente", "presente", "qui es-tu", "qui es tu", "bonjour"]
        ):
            intent["type"] = "greeting"
        elif any(w in low for w in ["devinette", "énigme", "enigme", "puzzle"]):
            intent["type"] = "riddle"
        elif "?" in text:
            intent["type"] = "question"

        return intent

    def validate_prompt_safety(self, prompt: str) -> bool:
        """Validate prompt doesn't contain unsafe content"""
        unsafe_patterns = [
            "ignore previous instructions",
            "système de sécurité",
            "systeme de securite",
            "contourner les règles",
            "contourner les regles",
            "mode développeur",
            "mode developpeur",
        ]
        prompt_lower = prompt.lower()
        return not any(pattern in prompt_lower for pattern in unsafe_patterns)


# Global prompt builder instance
prompt_builder = PromptBuilder()
