"""Service d'intégration OpenAI pour la génération narrative."""

import json
import time
from typing import Optional, Dict, List
from openai import OpenAI

from config import Config
from services.ai_logging_service import AILoggingService


class AIService:
    """Service pour toutes les interactions avec OpenAI."""
    
    @staticmethod
    def _get_client() -> OpenAI:
        """Obtient une instance du client OpenAI.
        
        Returns:
            Client OpenAI configuré.
        """
        return OpenAI(api_key=Config.OPENAI_API_KEY)
    
    @staticmethod
    def analyze_pitch(pitch: str, title: Optional[str] = None, story_id: Optional[str] = None) -> Dict:
        """Analyse un pitch utilisateur et extrait les informations pour créer l'histoire.
        
        Args:
            pitch: Description de l'histoire souhaitée par l'utilisateur.
            title: Titre optionnel fourni par l'utilisateur.
            story_id: ID de l'histoire (pour le logging).
        
        Returns:
            Dictionnaire contenant les paramètres extraits de l'histoire.
        """
        client = AIService._get_client()
        
        prompt = f"""Tu es un expert en création d'histoires interactives. Analyse le pitch suivant et extrait les informations structurées.

PITCH DE L'UTILISATEUR :
{pitch}

{'TITRE SUGGÉRÉ : ' + title if title else 'Le titre doit être généré automatiquement.'}

TÂCHE : Analyse ce pitch et retourne un JSON avec les informations suivantes :
- "title": Titre de l'histoire EN FRANÇAIS (utilise le titre suggéré s'il existe, sinon crée-en un accrocheur)
- "genre": Genre littéraire principal EN FRANÇAIS (fantasy, science-fiction, policier, horreur, aventure, etc.)
- "epoch": Époque EN FRANÇAIS (médiéval, contemporain, futuriste, renaissance, etc.)
- "reality_level": Niveau de réalisme de 0 à 100 (0=pure fantasy magique, 50=équilibré, 100=ultra réaliste)
- "narrative_perspective": "second" pour 2ème personne (Tu) ou "third" pour 3ème personne (Il/Elle)
- "protagonist_name": Nom du protagoniste si mentionné (null sinon)
- "protagonist_gender": "he" ou "she" si en 3ème personne (null sinon)
- "ambiance": Description de l'ambiance générale EN FRANÇAIS (épique, sombre, mystérieuse, etc.)
- "key_elements": Liste de 3-5 éléments clés mentionnés dans le pitch EN FRANÇAIS

RÈGLES IMPORTANTES :
- TOUS LES TEXTES DOIVENT ÊTRE EN FRANÇAIS (genre, époque, ambiance, éléments clés)
- Si le pitch dit "je" ou "tu", utilise "second"
- Si le pitch parle d'un personnage à la 3ème personne, utilise "third"
- En cas de doute sur la perspective, utilise "second" (plus immersif)
- Le reality_level doit refléter le niveau de fantastique vs réalisme
- Soit précis et fidèle au pitch de l'utilisateur
- N'utilise PAS de mots anglais comme "drama", "contemporary", "medieval" - utilise "drame", "contemporain", "médiéval"

Réponds UNIQUEMENT avec le JSON, sans texte additionnel."""

        start_time = time.time()
        
        try:
            response = client.chat.completions.create(
                model="gpt-4",
                messages=[
                    {"role": "system", "content": "Tu es un expert en analyse de récits et création d'histoires interactives. Tu réponds toujours en JSON valide."},
                    {"role": "user", "content": prompt}
                ],
                temperature=0.7,
                max_tokens=500
            )
            
            response_time_ms = int((time.time() - start_time) * 1000)
            
            content = response.choices[0].message.content.strip()
            # Nettoyer si le JSON est entouré de ```json```
            if content.startswith("```"):
                content = content.split("```")[1]
                if content.startswith("json"):
                    content = content[4:]
                content = content.strip()
            
            result = json.loads(content)
            
            # Valider et nettoyer les résultats
            if not result.get('title'):
                result['title'] = "Histoire sans titre"
            if not result.get('genre'):
                result['genre'] = "aventure"
            if not result.get('epoch'):
                result['epoch'] = "contemporain"
            if not isinstance(result.get('reality_level'), int):
                result['reality_level'] = 70
            else:
                result['reality_level'] = max(0, min(100, result['reality_level']))
            if result.get('narrative_perspective') not in ['second', 'third']:
                result['narrative_perspective'] = 'second'
            
            # Logger l'appel
            AILoggingService.log_ai_call(
                call_type='pitch_analysis',
                prompt=prompt,
                response=content,
                story_id=story_id,
                model=response.model,
                temperature=0.7,
                max_tokens=500,
                prompt_tokens=response.usage.prompt_tokens if response.usage else None,
                completion_tokens=response.usage.completion_tokens if response.usage else None,
                total_tokens=response.usage.total_tokens if response.usage else None,
                response_time_ms=response_time_ms,
                success=True
            )
            
            return result
            
        except Exception as e:
            response_time_ms = int((time.time() - start_time) * 1000)
            
            # Logger l'erreur
            AILoggingService.log_ai_call(
                call_type='pitch_analysis',
                prompt=prompt,
                story_id=story_id,
                model='gpt-4',
                temperature=0.7,
                max_tokens=500,
                response_time_ms=response_time_ms,
                success=False,
                error_message=str(e)
            )
            
            raise Exception(f"Erreur lors de l'analyse du pitch : {str(e)}")
    
    @staticmethod
    def generate_narrative_skeleton(
        genre: str,
        epoch: str,
        reality_level: int,
        context_answers: Optional[Dict] = None,
        story_id: Optional[str] = None,
        pitch: Optional[str] = None
    ) -> Dict:
        """Génère un squelette narratif pour une histoire.
        
        Args:
            genre: Genre littéraire.
            epoch: Époque de l'histoire.
            reality_level: Niveau de réalité (0-100).
            context_answers: Réponses aux questions de contextualisation.
            story_id: ID de l'histoire (pour le logging).
            pitch: Description initiale de l'histoire par l'utilisateur.
        
        Returns:
            Dictionnaire contenant le squelette narratif structuré.
        """
        pitch_str = ""
        if pitch:
            pitch_str = f"""
DESCRIPTION DE L'HISTOIRE PAR L'UTILISATEUR :
{pitch}
"""
        
        context_str = ""
        if context_answers:
            context_str = "\n".join([
                f"- {k}: {v}" for k, v in context_answers.items()
            ])
        
        prompt = f"""Tu es un auteur de fiction expérimenté. Crée un squelette narratif pour une histoire interactive EN FRANÇAIS.

{pitch_str}

Paramètres :
- Genre : {genre}
- Époque : {epoch}
- Niveau de réalité : {reality_level}/100 (0=fantasy pure, 100=ultra réaliste)
{f"- Contexte additionnel :{chr(10)}{context_str}" if context_str else ""}

Structure à générer (format JSON strict) :
{{
  "act_1": {{
    "description": "Situation initiale et déclencheur (EN FRANÇAIS)",
    "key_points": ["point 1", "point 2"],
    "trigger": "événement déclencheur"
  }},
  "act_2": {{
    "description": "Développement et obstacles (EN FRANÇAIS)",
    "key_points": ["point 1", "point 2", "point 3"],
    "twists": ["retournement potentiel 1", "retournement potentiel 2"]
  }},
  "act_3": {{
    "description": "Climax et résolution (EN FRANÇAIS)",
    "key_points": ["point 1", "point 2"],
    "climax": "description du climax"
  }},
  "themes": ["thème 1", "thème 2"],
  "tone": "description du ton général (EN FRANÇAIS)"
}}

RÈGLES IMPORTANTES :
- TOUT le contenu doit être EN FRANÇAIS
- Si une description de l'histoire est fournie ci-dessus, RESPECTE-LA SCRUPULEUSEMENT
- Le squelette doit correspondre exactement à l'univers, au contexte et aux éléments décrits par l'utilisateur
- Reste fidèle au genre, à l'époque et au niveau de réalité indiqués
- Le squelette doit être flexible et s'adapter aux choix du joueur
- Retourne UNIQUEMENT le JSON, sans texte additionnel"""

        start_time = time.time()

        try:
            client = AIService._get_client()
            response = client.chat.completions.create(
                model=Config.OPENAI_MODEL_CHAT,
                messages=[
                    {"role": "system", "content": "Tu es un créateur de squelettes narratifs. Tu réponds toujours en JSON valide."},
                    {"role": "user", "content": prompt}
                ],
                temperature=0.8,
                max_tokens=1000
            )
            
            response_time_ms = int((time.time() - start_time) * 1000)
            
            content = response.choices[0].message.content.strip()
            
            # Extraire le JSON du contenu (au cas où il y a du texte autour)
            if "```json" in content:
                content = content.split("```json")[1].split("```")[0].strip()
            elif "```" in content:
                content = content.split("```")[1].split("```")[0].strip()
            
            skeleton = json.loads(content)
            
            # Logger l'appel
            AILoggingService.log_ai_call(
                call_type='narrative_skeleton',
                prompt=prompt,
                response=content,
                story_id=story_id,
                model=response.model,
                temperature=0.8,
                max_tokens=1000,
                prompt_tokens=response.usage.prompt_tokens if response.usage else None,
                completion_tokens=response.usage.completion_tokens if response.usage else None,
                total_tokens=response.usage.total_tokens if response.usage else None,
                response_time_ms=response_time_ms,
                success=True
            )
            
            return skeleton
            
        except json.JSONDecodeError as e:
            response_time_ms = int((time.time() - start_time) * 1000)
            
            # Logger l'erreur
            AILoggingService.log_ai_call(
                call_type='narrative_skeleton',
                prompt=prompt,
                story_id=story_id,
                model=Config.OPENAI_MODEL_CHAT,
                temperature=0.8,
                max_tokens=1000,
                response_time_ms=response_time_ms,
                success=False,
                error_message=f"JSONDecodeError: {str(e)}"
            )
            
            # Fallback : retourner un squelette générique
            return {
                "act_1": {
                    "description": f"Début de l'aventure dans un univers de {genre}",
                    "key_points": ["Introduction du protagoniste", "Découverte du monde"],
                    "trigger": "Un événement imprévu change tout"
                },
                "act_2": {
                    "description": "Les défis s'accumulent",
                    "key_points": ["Premier obstacle", "Rencontre clé", "Révélation importante"],
                    "twists": ["Une trahison inattendue", "Une découverte majeure"]
                },
                "act_3": {
                    "description": "La confrontation finale",
                    "key_points": ["Préparation", "Climax"],
                    "climax": "L'affrontement décisif"
                },
                "themes": [genre, "aventure", "découverte"],
                "tone": "Immersif et captivant"
            }
        
        except Exception as e:
            response_time_ms = int((time.time() - start_time) * 1000)
            
            # Logger l'erreur
            AILoggingService.log_ai_call(
                call_type='narrative_skeleton',
                prompt=prompt,
                story_id=story_id,
                model=Config.OPENAI_MODEL_CHAT,
                temperature=0.8,
                max_tokens=1000,
                response_time_ms=response_time_ms,
                success=False,
                error_message=str(e)
            )
            
            raise Exception(f"Erreur lors de la génération du squelette : {str(e)}")
    
    @staticmethod
    def generate_cover_image(
        title: str,
        genre: str,
        epoch: str,
        skeleton: Dict,
        story_id: Optional[str] = None,
        pitch: Optional[str] = None
    ) -> str:
        """Génère une image de couverture avec DALL-E 3.
        
        Args:
            title: Titre de l'histoire.
            genre: Genre littéraire.
            epoch: Époque.
            skeleton: Squelette narratif.
            story_id: ID de l'histoire (pour le logging).
            pitch: Description initiale de l'histoire (optionnel).
        
        Returns:
            URL de l'image générée.
        """
        tone = skeleton.get("tone", "mystérieux")
        themes = ", ".join(skeleton.get("themes", [genre]))
        
        pitch_context = ""
        if pitch:
            pitch_context = f"""

Contexte de l'histoire :
{pitch[:300]}"""  # Limiter pour ne pas dépasser les tokens
        
        prompt = f"""Create a highly detailed illustration for a story titled "{title}".

Genre: {genre}
Era/Setting: {epoch}
Mood/Atmosphere: {tone}
Themes: {themes}{pitch_context}

Visual Style Requirements:
- Epic fantasy illustration or digital painting style
- Cinematic composition with dramatic lighting
- Rich, atmospheric environment
- Professional concept art quality
- Focus on scenery, characters, or symbolic elements
- Evocative and mysterious atmosphere

CRITICAL: NO TEXT, NO LETTERS, NO WORDS, NO SYMBOLS, NO TYPOGRAPHY of any kind in the image. Pure visual illustration only.

Create a captivating scene that embodies the story's essence through visual elements alone."""
        
        start_time = time.time()
        
        try:
            client = AIService._get_client()
            response = client.images.generate(
                model=Config.OPENAI_MODEL_IMAGE,
                prompt=prompt,
                size="1024x1024",
                quality="standard",
                n=1
            )
            
            response_time_ms = int((time.time() - start_time) * 1000)
            image_url = response.data[0].url
            
            # Logger l'appel
            AILoggingService.log_ai_call(
                call_type='cover_image',
                prompt=prompt,
                response=f"Image générée: {image_url}",
                story_id=story_id,
                model=Config.OPENAI_MODEL_IMAGE,
                response_time_ms=response_time_ms,
                success=True
            )
            
            return image_url
            
        except Exception as e:
            response_time_ms = int((time.time() - start_time) * 1000)
            
            # Logger l'erreur
            AILoggingService.log_ai_call(
                call_type='cover_image',
                prompt=prompt,
                story_id=story_id,
                model=Config.OPENAI_MODEL_IMAGE,
                response_time_ms=response_time_ms,
                success=False,
                error_message=str(e)
            )
            
            raise Exception(f"Erreur lors de la génération de la couverture : {str(e)}")
    
    @staticmethod
    def generate_narrative_passage(
        story: "Story",
        lorebook: "LoreBook",
        recent_turns: List["StoryTurn"],
        player_action: Optional[str] = None,
        is_technical: bool = False,
        is_first_passage: bool = False
    ) -> str:
        """Génère un passage narratif.
        
        Args:
            story: L'histoire.
            lorebook: Le LoreBook actuel.
            recent_turns: Derniers tours (pour contexte).
            player_action: Action du joueur (optionnelle pour le premier passage).
            is_technical: Si True, c'est un message OFF.
            is_first_passage: Si True, c'est le tout premier passage.
        
        Returns:
            Le texte narratif généré.
        """
        # Construire l'instruction de perspective
        if story.narrative_perspective == 'second':
            perspective_instruction = """**Narration en 2ème personne ("Tu")**
- Utilise "Tu" pour désigner le protagoniste
- Exemples : "Tu te tiens...", "Tu décides de...", "Ta main se crispe..."
"""
        else:  # third person
            pronoun = "Elle" if story.protagonist_gender == 'she' else "Il"
            if story.protagonist_name:
                perspective_instruction = f"""**Narration en 3ème personne avec nom du protagoniste**
- Protagoniste : {story.protagonist_name} ({pronoun})
- Utilise le nom "{story.protagonist_name}" et le pronom "{pronoun}"
- Exemples : "{story.protagonist_name} se tient...", "{pronoun} décide de...", "sa main se crispe..."
"""
            else:
                perspective_instruction = f"""**Narration en 3ème personne sans nom**
- Protagoniste : {pronoun}
- Utilise uniquement le pronom "{pronoun}" (pas de nom propre)
- Exemples : "{pronoun} se tient...", "{pronoun} décide de...", "sa main se crispe..."
"""
        
        # Construire le contexte d'historique
        history_context = ""
        if recent_turns:
            history_lines = []
            for turn in recent_turns[-3:]:  # 3 derniers tours max
                if not turn.is_technical:
                    history_lines.append(f"Passage : {turn.narrative_text[:200]}...")
                    if turn.player_action:
                        history_lines.append(f"Action : {turn.player_action}")
            history_context = "\n".join(history_lines)
        
        # Construire le LoreBook
        lorebook_str = json.dumps({
            "personnages": lorebook.characters or [],
            "lieux": lorebook.locations or [],
            "objets": lorebook.items or [],
            "événements_clés": lorebook.key_events or [],
            "état_protagoniste": lorebook.protagonist_state or {}
        }, ensure_ascii=False, indent=2)
        
        # Instruction technique si message OFF
        technical_instruction = ""
        tech_note = ""
        if is_technical and player_action:
            technical_instruction = f"""
## Instruction technique de l'auteur
L'auteur souhaite orienter l'histoire ainsi :
{player_action}

Intègre cette directive de manière naturelle dans la narration suivante.
"""
            tech_note = "Intègre subtilement la directive technique de l'auteur"
        
        # Déterminer l'acte actuel
        turn_count = story.turn_count
        if turn_count < 5:
            current_act = "1 (introduction)"
        elif turn_count < 15:
            current_act = "2 (développement)"
        else:
            current_act = "3 (climax)"
        
        # Premier passage ou passage normal
        if is_first_passage:
            action_context = "C'est le tout premier passage de l'histoire. Commence l'aventure de manière captivante."
        else:
            action_context = f"Dernière action du joueur : {player_action}" if player_action and not is_technical else "Continue la narration."
        
        prompt = f"""Tu es un narrateur immersif pour une histoire interactive EN FRANÇAIS.

## Contexte de l'histoire
Genre : {story.genre}
Époque : {story.epoch}
Niveau de réalité : {story.reality_level}/100

## Perspective narrative
{perspective_instruction}

## Squelette narratif
{json.dumps(story.narrative_skeleton, ensure_ascii=False, indent=2)}

## LoreBook actuel
{lorebook_str}

## Historique récent
{history_context if history_context else "Pas d'historique (début de l'histoire)"}

## Action actuelle
{action_context}
{technical_instruction}

## Instructions
1. Continue l'histoire de manière cohérente EN FRANÇAIS
2. Prends en compte l'action du joueur
{f'3. {tech_note}' if tech_note else '3. Avance la narration de manière fluide'}
4. Respecte le squelette narratif (acte {current_act})
5. **IMPORTANT : Respecte strictement la perspective narrative définie ci-dessus**
6. Garde un style immersif et littéraire
7. Longueur : 150-300 mots
8. Termine sur une situation appelant à l'action
9. Reste approprié (pas de contenu violent/sexuel explicite)
10. TOUT doit être EN FRANÇAIS

Génère maintenant le passage narratif."""

        start_time = time.time()

        try:
            client = AIService._get_client()
            response = client.chat.completions.create(
                model=Config.OPENAI_MODEL_CHAT,
                messages=[
                    {"role": "system", "content": "Tu es un narrateur expert en fiction interactive. Tu crées des passages immersifs et captivants EN FRANÇAIS."},
                    {"role": "user", "content": prompt}
                ],
                temperature=Config.TEMPERATURE,
                max_tokens=Config.MAX_TOKENS_NARRATIVE
            )
            
            response_time_ms = int((time.time() - start_time) * 1000)
            narrative_text = response.choices[0].message.content.strip()
            
            # Logger l'appel
            AILoggingService.log_ai_call(
                call_type='narrative_passage',
                prompt=prompt,
                response=narrative_text,
                story_id=story.id,
                model=response.model,
                temperature=Config.TEMPERATURE,
                max_tokens=Config.MAX_TOKENS_NARRATIVE,
                prompt_tokens=response.usage.prompt_tokens if response.usage else None,
                completion_tokens=response.usage.completion_tokens if response.usage else None,
                total_tokens=response.usage.total_tokens if response.usage else None,
                response_time_ms=response_time_ms,
                success=True
            )
            
            return narrative_text
            
        except Exception as e:
            response_time_ms = int((time.time() - start_time) * 1000)
            
            # Logger l'erreur
            AILoggingService.log_ai_call(
                call_type='narrative_passage',
                prompt=prompt,
                story_id=story.id,
                model=Config.OPENAI_MODEL_CHAT,
                temperature=Config.TEMPERATURE,
                max_tokens=Config.MAX_TOKENS_NARRATIVE,
                response_time_ms=response_time_ms,
                success=False,
                error_message=str(e)
            )
            
            raise Exception(f"Erreur lors de la génération du passage : {str(e)}")
    
    @staticmethod
    def generate_choices(
        story: "Story",
        narrative_passage: str,
        protagonist_traits: List[str]
    ) -> List[str]:
        """Génère 4 choix d'action cohérents.
        
        Args:
            story: L'histoire.
            narrative_passage: Le passage narratif actuel.
            protagonist_traits: Traits du protagoniste.
        
        Returns:
            Liste de 4 choix.
        """
        # Construire l'info de perspective pour les choix
        if story.narrative_perspective == 'second':
            perspective_info = 'Les choix doivent être formulés simplement sans "Tu" (ex: "Entrer prudemment dans le château")'
        else:
            if story.protagonist_name:
                name = story.protagonist_name
                perspective_info = f'Les choix peuvent utiliser le nom "{name}" ou être neutres (ex: "Entrer prudemment" ou "{name} entre prudemment")'
            else:
                pronoun = "Elle" if story.protagonist_gender == 'she' else "Il"
                perspective_info = f'Les choix peuvent utiliser "{pronoun}" ou être neutres (ex: "Entrer prudemment" ou "{pronoun} entre prudemment")'
        
        traits_str = ", ".join(protagonist_traits) if protagonist_traits else "non définis"
        
        prompt = f"""Contexte : le joueur vient de lire ce passage EN FRANÇAIS :
{narrative_passage}

Perspective narrative : {perspective_info}
Traits du protagoniste : {traits_str}

Génère 4 choix d'action cohérents et variés EN FRANÇAIS :
- Choix pragmatique
- Choix audacieux
- Choix réfléchi
- Choix créatif

Format JSON strict :
{{
  "choices": [
    "Action 1 EN FRANÇAIS",
    "Action 2 EN FRANÇAIS",
    "Action 3 EN FRANÇAIS",
    "Action 4 EN FRANÇAIS"
  ]
}}

Chaque choix doit être une phrase complète et engageante EN FRANÇAIS. Retourne UNIQUEMENT le JSON."""

        start_time = time.time()

        try:
            client = AIService._get_client()
            response = client.chat.completions.create(
                model=Config.OPENAI_MODEL_CHAT,
                messages=[
                    {"role": "system", "content": "Tu génères des choix d'action pour une histoire interactive EN FRANÇAIS. Tu réponds toujours en JSON valide."},
                    {"role": "user", "content": prompt}
                ],
                temperature=0.7,
                max_tokens=200
            )
            
            response_time_ms = int((time.time() - start_time) * 1000)
            
            content = response.choices[0].message.content.strip()
            
            # Extraire le JSON
            if "```json" in content:
                content = content.split("```json")[1].split("```")[0].strip()
            elif "```" in content:
                content = content.split("```")[1].split("```")[0].strip()
            
            data = json.loads(content)
            choices = data.get("choices", [
                "Continuer prudemment",
                "Agir rapidement",
                "Observer attentivement",
                "Chercher une alternative"
            ])
            
            # Logger l'appel
            AILoggingService.log_ai_call(
                call_type='choices_generation',
                prompt=prompt,
                response=content,
                story_id=story.id,
                model=response.model,
                temperature=0.7,
                max_tokens=200,
                prompt_tokens=response.usage.prompt_tokens if response.usage else None,
                completion_tokens=response.usage.completion_tokens if response.usage else None,
                total_tokens=response.usage.total_tokens if response.usage else None,
                response_time_ms=response_time_ms,
                success=True
            )
            
            return choices
            
        except Exception as e:
            response_time_ms = int((time.time() - start_time) * 1000)
            
            # Logger l'erreur
            AILoggingService.log_ai_call(
                call_type='choices_generation',
                prompt=prompt,
                story_id=story.id,
                model=Config.OPENAI_MODEL_CHAT,
                temperature=0.7,
                max_tokens=200,
                response_time_ms=response_time_ms,
                success=False,
                error_message=str(e)
            )
            
            # Retourner des choix par défaut
            return [
                "Continuer prudemment",
                "Agir rapidement",
                "Observer attentivement",
                "Chercher une alternative"
            ]

