"""
Chat session management module for OpenQube CLI.

This module provides the ChatSession class for managing conversation history
and coordinating message transmission with AI providers.

Requirements: 6.2, 6.4, 6.5
"""

from typing import List, Optional
from datetime import datetime
from src.provider_base import AIProvider, Message, ChatContext, Response, Model
from src.tools import ToolRegistry, ToolCall
import re


class ChatSession:
    """
    Manages a chat session with an AI provider.
    
    This class maintains conversation history, formats context for API calls,
    and coordinates message transmission with the selected provider.
    """
    
    def __init__(self, provider: AIProvider, model: str, context_window: Optional[int] = None, tool_registry: Optional[ToolRegistry] = None, skills_manager=None):
        """
        Initialize a new chat session.
        
        Args:
            provider: The AIProvider instance to use for communication
            model: The model ID to use for this session
            context_window: Optional context window size in tokens
            tool_registry: Optional tool registry for tool calling
            skills_manager: Optional skills manager for agent skills
        """
        self.provider = provider
        self.model = model
        self.context_window = context_window
        self.tool_registry = tool_registry
        self.skills_manager = skills_manager
        self._messages: List[Message] = []
        self._activated_skills = set()  # Track which skills have been loaded
        
        # Add system message with skills metadata if available
        if self.skills_manager:
            skills = self.skills_manager.list_skills()
            if skills:
                skills_content = self._build_skills_prompt(skills)
                self.add_message("user", skills_content)
                self.add_message("assistant", "I understand. I will activate skills as needed when working on tasks.")
    
    def _build_skills_prompt(self, skills) -> str:
        """Build a system prompt with available skills (metadata only for progressive disclosure)."""
        prompt = "You have access to the following skills. When you need detailed instructions for a skill, mention it in your response:\n\n"
        
        for skill in skills:
            prompt += f"- **{skill.name}**: {skill.description}\n"
            if skill.allowed_tools:
                prompt += f"  Tools: {', '.join(skill.allowed_tools)}\n"
        
        prompt += "\n\nWhen you decide to use a skill, mention it naturally in your response (e.g., 'I'll use the pdf skill to help with this'). The full skill instructions will be provided automatically."
        
        return prompt
    
    def _check_and_activate_skills(self, message: str) -> Optional[str]:
        """Check if message mentions any skills and activate them if needed."""
        if not self.skills_manager:
            return None
        
        skills = self.skills_manager.list_skills()
        activated_content = []
        message_lower = message.lower()
        
        for skill in skills:
            # Check if skill should be activated
            should_activate = False
            
            # Check if skill name is mentioned
            if skill.name in message_lower and skill.name not in self._activated_skills:
                should_activate = True
            
            # Check if description keywords match (for better activation)
            # For example, "pdf" skill should activate on "create pdf", "generate pdf", etc.
            if not should_activate and skill.name not in self._activated_skills:
                # Extract key terms from description
                desc_lower = skill.description.lower()
                # Check if message contains skill-related keywords
                if skill.name == "pdf" and ("pdf" in message_lower or "document" in message_lower):
                    should_activate = True
                elif skill.name == "code-helper" and ("code" in message_lower or "project" in message_lower):
                    should_activate = True
                elif skill.name == "frontend-designer" and ("frontend" in message_lower or "website" in message_lower or "ui" in message_lower):
                    should_activate = True
            
            if should_activate:
                # Load full skill content
                full_content = f"\n\n--- Activating {skill.name} skill ---\n\n"
                full_content += skill.get_full_content()
                
                # Load scripts if available
                scripts = self.skills_manager.list_skill_scripts(skill.name)
                if scripts:
                    full_content += f"\n\nAvailable scripts:\n"
                    for script in scripts:
                        full_content += f"- {script}\n"
                
                activated_content.append(full_content)
                self._activated_skills.add(skill.name)
        
        return "\n".join(activated_content) if activated_content else None
    
    def add_message(self, role: str, content: str) -> None:
        """
        Add a message to the conversation history.
        
        Args:
            role: The role of the message sender ("user", "assistant", or "tool")
            content: The message content
            
        Raises:
            ValueError: If role is not valid
        """
        if role not in ["user", "assistant", "tool"]:
            raise ValueError(f"Invalid role: {role}. Must be 'user', 'assistant', or 'tool'")
        
        message = Message(role=role, content=content, timestamp=datetime.now())
        self._messages.append(message)
    
    def get_context(self) -> ChatContext:
        """
        Get the current conversation context.
        
        Returns:
            ChatContext object containing message history, model, and provider info
        """
        # Get tool schemas if tool registry is available
        tools = None
        if self.tool_registry:
            tools = self.tool_registry.get_tools_schema()
        
        return ChatContext(
            messages=self._messages.copy(),
            model=self.model,
            provider=self.provider.get_name(),
            tools=tools
        )
    
    async def send_message(self, message: str) -> Response:
        """
        Send a message to the AI provider and receive a response.
        
        This method adds the user message to history, sends it to the provider
        with full conversation context, receives the response, handles tool calls
        if any, and returns the final response.
        
        Args:
            message: The user's message to send
            
        Returns:
            Response object containing the AI's reply
            
        Raises:
            Exception: If the API call fails
        """
        from colorama import Fore, Style
        import json
        
        # Add user message to history
        self.add_message("user", message)
        
        # Check if any skills should be activated based on the message
        skill_activation = self._check_and_activate_skills(message)
        if skill_activation:
            # Add skill content to context
            self.add_message("user", skill_activation)
        
        # Get current context with tools
        context = self.get_context()
        
        # Send message to provider
        response = await self.provider.send_message(message, context)
        
        # Check if model wants to call tools
        if response.tool_calls and self.tool_registry:
            # Store tool calls for later reference
            tool_call_messages = []
            
            # Execute tool calls
            for tool_call in response.tool_calls:
                tool_name = tool_call["function"]["name"]
                tool_args = json.loads(tool_call["function"]["arguments"])
                tool_call_id = tool_call["id"]
                
                print(f"\n{Fore.CYAN}[AI is calling tool: {tool_name}]{Style.RESET_ALL}")
                
                # Show truncated arguments
                args_str = json.dumps(tool_args, indent=2)
                if len(args_str) > 200:
                    # Truncate long arguments
                    truncated = args_str[:200] + "..."
                    print(f"\n{Fore.LIGHTBLACK_EX}Arguments: {truncated}{Style.RESET_ALL}")
                    print(f"{Fore.LIGHTBLACK_EX}(Full arguments hidden - {len(args_str)} chars total){Style.RESET_ALL}\n")
                else:
                    print(f"\n{Fore.LIGHTBLACK_EX}Arguments: {args_str}{Style.RESET_ALL}\n")
                
                # Execute the tool
                from src.tools import ToolCall
                result = self.tool_registry.execute_tool(ToolCall(tool_name, tool_args))
                
                if result.success:
                    # Don't print success message
                    tool_call_messages.append({
                        "role": "tool",
                        "tool_call_id": tool_call_id,
                        "content": result.output
                    })
                else:
                    # Show error in a cleaner format
                    error_msg = result.error
                    if "Traceback" in error_msg:
                        # Extract just the error message, not full traceback
                        lines = error_msg.split('\n')
                        # Get the last non-empty line (usually the actual error)
                        error_line = next((line for line in reversed(lines) if line.strip()), error_msg)
                        print(f"{Fore.RED}✗ Error: {error_line}{Style.RESET_ALL}\n")
                    else:
                        print(f"{Fore.RED}✗ Tool execution failed: {result.error}{Style.RESET_ALL}\n")
                    
                    tool_call_messages.append({
                        "role": "tool",
                        "tool_call_id": tool_call_id,
                        "content": f"Error: {result.error}"
                    })
            
            # Add assistant's tool call message with actual tool_calls data
            # Store as JSON so we can reconstruct it
            tool_calls_json = json.dumps(response.tool_calls)
            self.add_message("assistant", f"__TOOL_CALLS__:{tool_calls_json}")
            
            # Add tool results to history
            for tool_msg in tool_call_messages:
                # Store tool call ID in the message
                self.add_message("tool", f"__TOOL_CALL_ID__:{tool_msg['tool_call_id']}:{tool_msg['content']}")
            
            # Get final response from AI with tool results
            context = self.get_context()
            final_response = await self.provider.send_message("", context)
            
            # Add final response to history
            self.add_message("assistant", final_response.content)
            
            return final_response
        
        # No tool calls, just add response to history
        self.add_message("assistant", response.content)
        
        return response
    
    def get_message_history(self) -> List[Message]:
        """
        Get the complete message history.
        
        Returns:
            List of Message objects in chronological order
        """
        return self._messages.copy()
    
    def clear_history(self) -> None:
        """
        Clear the conversation history.
        
        This removes all messages from the session but keeps the
        provider and model configuration intact.
        """
        self._messages.clear()
    
    def get_message_count(self) -> int:
        """
        Get the number of messages in the conversation history.
        
        Returns:
            The total number of messages (user + assistant)
        """
        return len(self._messages)
