"""
Interaction Logger - Logs agent interactions for analysis

This module provides comprehensive logging of all agent interactions including
prompts, generated queries, results, and performance metrics.
"""

import os
import json
import sys
from datetime import datetime
from typing import Dict, List, Optional, Any
from pathlib import Path
from dataclasses import dataclass, asdict

from .models import QuestionResponse, SPARQLQuery, QueryResult


@dataclass
class InteractionLog:
    """Complete log entry for a single agent interaction."""
    
    # Basic interaction info
    timestamp: str
    session_id: str
    interaction_id: str
    
    # Input
    original_question: str
    include_reasoning: bool
    include_execution_details: bool
    
    # Prompt engineering
    ontology_context_used: str  # The ontology context provided to LLM
    system_prompt: str          # System prompt used
    user_prompt: str           # Final user prompt to LLM
    
    # Query generation and iteration
    sparql_queries_attempted: List[Dict[str, Any]]  # All SPARQL attempts with metadata
    total_query_attempts: int
    final_sparql_query: str     # Final expanded query that was executed
    
    # Execution results  
    execution_success: bool
    execution_error: Optional[str]
    results_count: int
    raw_results: List[Any]      # Raw SPARQL results
    
    # Response generation
    natural_language_answer: str
    confidence_score: float
    reasoning_steps: Optional[List[str]]
    
    # Performance metrics
    total_execution_time: float
    ontology_discovery_time: Optional[float]
    query_generation_time: Optional[float]
    query_execution_time: Optional[float]
    
    # Agent state
    agent_memory_updated: bool
    ontology_classes_used: List[str]
    ontology_properties_used: List[str]


class InteractionLogger:
    """Logs all agent interactions to structured files."""
    
    def __init__(self, log_dir: str = None, enable_debug_output: bool = None):
        """Initialize the interaction logger.
        
        Args:
            log_dir: Directory to store log files. Defaults to ../logs/
            enable_debug_output: Whether to output debug messages to stderr. 
                                Defaults to False for MCP mode, True for testing.
                                Can be controlled via ENABLE_LOGGING_DEBUG env var.
        """
        if log_dir is None:
            log_dir = os.path.join(os.path.dirname(__file__), '..', 'logs')
        
        self.log_dir = Path(log_dir)
        self.log_dir.mkdir(parents=True, exist_ok=True)
        
        # Control debug output - default to quiet for MCP compatibility
        if enable_debug_output is None:
            # Check environment variable, default to False for MCP mode
            enable_debug_output = os.getenv('ENABLE_LOGGING_DEBUG', 'false').lower() == 'true'
        
        self.enable_debug_output = enable_debug_output
        
        # Create session ID for this run
        self.session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
        self.interaction_counter = 0
        
        # Log files
        self.interactions_file = self.log_dir / f"interactions_{self.session_id}.md"
        self.summary_file = self.log_dir / f"session_summary_{self.session_id}.json"
        
        # Session statistics
        self.session_stats = {
            "session_id": self.session_id,
            "start_time": datetime.now().isoformat(),
            "total_interactions": 0,
            "successful_interactions": 0,
            "failed_interactions": 0,
            "avg_execution_time": 0.0,
            "avg_confidence": 0.0,
            "most_used_classes": {},
            "most_used_properties": {},
            "common_error_types": {}
        }
        
        self._debug_log(f"🗂️  Interaction logger initialized: {self.log_dir}")
        self._debug_log(f"📝 Session ID: {self.session_id}")
    
    def start_interaction(self, question: str, include_reasoning: bool = False, 
                         include_execution_details: bool = False) -> str:
        """Start logging a new interaction and return interaction ID.
        
        Args:
            question: The user's question
            include_reasoning: Whether reasoning was requested
            include_execution_details: Whether execution details were requested
            
        Returns:
            Unique interaction ID for this interaction
        """
        self.interaction_counter += 1
        interaction_id = f"{self.session_id}_{self.interaction_counter:03d}"
        
        # Store interaction start info
        self._current_interaction = {
            "interaction_id": interaction_id,
            "start_time": datetime.now(),
            "question": question,
            "include_reasoning": include_reasoning,
            "include_execution_details": include_execution_details,
            "ontology_context": None,
            "prompts": {
                "system_prompt": None,
                "user_prompt": None
            },
            "sparql_attempts": [],
            "execution_times": {}
        }
        
        self._debug_log(f"🔄 Started interaction {interaction_id}: {question[:50]}...")
        return interaction_id
    
    def log_ontology_context(self, ontology_context: str, discovery_time: float = None):
        """Log the ontology context provided to the LLM.
        
        Args:
            ontology_context: The ontology context string
            discovery_time: Time taken for ontology discovery (optional)
        """
        if hasattr(self, '_current_interaction'):
            self._current_interaction["ontology_context"] = ontology_context
            if discovery_time:
                self._current_interaction["execution_times"]["ontology_discovery"] = discovery_time
            
            self._debug_log(f"📋 Logged ontology context ({len(ontology_context)} chars)")
    
    def log_prompts(self, system_prompt: str, user_prompt: str):
        """Log the prompts sent to the LLM.
        
        Args:
            system_prompt: System prompt for the LLM
            user_prompt: User prompt for the LLM
        """
        if hasattr(self, '_current_interaction'):
            self._current_interaction["prompts"]["system_prompt"] = system_prompt
            self._current_interaction["prompts"]["user_prompt"] = user_prompt
            
            self._debug_log(f"💬 Logged prompts (system: {len(system_prompt)} chars, user: {len(user_prompt)} chars)")
    
    def log_sparql_attempt(self, sparql_query: SPARQLQuery, attempt_number: int, 
                          generation_time: float = None, execution_time: float = None,
                          result: QueryResult = None):
        """Log a SPARQL query attempt.
        
        Args:
            sparql_query: The SPARQL query object
            attempt_number: Which attempt this is (1, 2, 3...)
            generation_time: Time taken to generate this query
            execution_time: Time taken to execute this query
            result: Execution result (optional)
        """
        if hasattr(self, '_current_interaction'):
            attempt_data = {
                "attempt_number": attempt_number,
                "compact_query": sparql_query.latest_compact_query,
                "expanded_query": sparql_query.latest_expanded_query,
                "confidence": sparql_query.confidence,
                "explanation": sparql_query.explanation,
                "classes_used": sparql_query.ontology_classes_used,
                "properties_used": sparql_query.ontology_properties_used,
                "generation_time": generation_time,
                "execution_time": execution_time
            }
            
            if result:
                attempt_data.update({
                    "success": result.success,
                    "error_message": result.error_message,
                    "results_count": len(result.data) if result.data else 0,
                    "column_names": result.column_names
                })
            
            self._current_interaction["sparql_attempts"].append(attempt_data)
            
            success_status = "✅" if (result and result.success) else "❌" if result else "⏳"
            self._debug_log(f"🔍 Logged SPARQL attempt {attempt_number} {success_status}")
    
    def _format_interaction_as_markdown(self, interaction_log: InteractionLog) -> str:
        """Format an interaction log as readable markdown."""
        md_lines = []
        
        # Header
        md_lines.append(f"# Interaction {interaction_log.interaction_id}")
        md_lines.append(f"**Timestamp:** {interaction_log.timestamp}")
        md_lines.append(f"**Execution Time:** {interaction_log.total_execution_time:.2f}s")
        md_lines.append(f"**Confidence:** {interaction_log.confidence_score:.2f}")
        md_lines.append("")
        
        # Question and Answer
        md_lines.append("## Question")
        md_lines.append(f"> {interaction_log.original_question}")
        md_lines.append("")
        
        md_lines.append("## Answer")
        md_lines.append(interaction_log.natural_language_answer)
        md_lines.append("")
        
        # Execution Details
        md_lines.append("## Execution Details")
        md_lines.append(f"- **Success:** {'✅ Yes' if interaction_log.execution_success else '❌ No'}")
        md_lines.append(f"- **Query Attempts:** {interaction_log.total_query_attempts}")
        md_lines.append(f"- **Results Count:** {interaction_log.results_count}")
        
        if interaction_log.ontology_discovery_time:
            md_lines.append(f"- **Ontology Discovery:** {interaction_log.ontology_discovery_time:.2f}s")
        if interaction_log.query_generation_time:
            md_lines.append(f"- **Query Generation:** {interaction_log.query_generation_time:.2f}s")
        if interaction_log.query_execution_time:
            md_lines.append(f"- **Query Execution:** {interaction_log.query_execution_time:.2f}s")
        
        md_lines.append("")
        
        # SPARQL Query Attempts
        if interaction_log.sparql_queries_attempted:
            md_lines.append("## SPARQL Query Evolution")
            
            for attempt in interaction_log.sparql_queries_attempted:
                attempt_num = attempt.get("attempt_number", "?")
                success = attempt.get("success", False)
                status = "✅ Success" if success else "❌ Failed"
                
                md_lines.append(f"### Attempt {attempt_num} - {status}")
                
                if attempt.get("explanation"):
                    md_lines.append(f"**Explanation:** {attempt['explanation']}")
                    md_lines.append("")
                
                # Compact query (more readable)
                if attempt.get("compact_query"):
                    md_lines.append("**Compact Query:**")
                    md_lines.append("```sparql")
                    md_lines.append(attempt["compact_query"])
                    md_lines.append("```")
                    md_lines.append("")
                
                # Show execution details for this attempt
                exec_time = attempt.get("execution_time")
                if exec_time:
                    md_lines.append(f"**Execution Time:** {exec_time:.2f}s")
                
                if not success and attempt.get("error_message"):
                    md_lines.append(f"**Error:** {attempt['error_message']}")
                elif success:
                    results_count = attempt.get("results_count", 0)
                    md_lines.append(f"**Results:** {results_count} rows returned")
                
                md_lines.append("")
        
        # Final Query (expanded version)
        if interaction_log.final_sparql_query:
            md_lines.append("## Final SPARQL Query (Expanded)")
            md_lines.append("```sparql")
            md_lines.append(interaction_log.final_sparql_query)
            md_lines.append("```")
            md_lines.append("")
        
        # Reasoning Steps
        if interaction_log.reasoning_steps:
            md_lines.append("## Reasoning Steps")
            for i, step in enumerate(interaction_log.reasoning_steps, 1):
                md_lines.append(f"{i}. {step}")
            md_lines.append("")
        
        # Ontology Usage
        if interaction_log.ontology_classes_used or interaction_log.ontology_properties_used:
            md_lines.append("## Ontology Usage")
            if interaction_log.ontology_classes_used:
                md_lines.append(f"**Classes:** {', '.join(interaction_log.ontology_classes_used)}")
            if interaction_log.ontology_properties_used:
                md_lines.append(f"**Properties:** {', '.join(interaction_log.ontology_properties_used)}")
            md_lines.append("")
        
        # Prompts (collapsible for readability)
        md_lines.append("## Prompts Used")
        md_lines.append("<details>")
        md_lines.append("<summary>Click to expand prompts</summary>")
        md_lines.append("")
        
        md_lines.append("### System Prompt")
        md_lines.append("```")
        md_lines.append(interaction_log.system_prompt[:500] + "..." if len(interaction_log.system_prompt) > 500 else interaction_log.system_prompt)
        md_lines.append("```")
        md_lines.append("")
        
        md_lines.append("### User Prompt")
        md_lines.append("```")
        md_lines.append(interaction_log.user_prompt[:500] + "..." if len(interaction_log.user_prompt) > 500 else interaction_log.user_prompt)
        md_lines.append("```")
        md_lines.append("")
        
        md_lines.append("### Ontology Context")
        md_lines.append("```")
        context_preview = interaction_log.ontology_context_used[:300] + "..." if len(interaction_log.ontology_context_used) > 300 else interaction_log.ontology_context_used
        md_lines.append(context_preview)
        md_lines.append("```")
        md_lines.append("")
        md_lines.append("</details>")
        md_lines.append("")
        
        # Separator
        md_lines.append("---")
        md_lines.append("")
        
        return "\n".join(md_lines)

    def _write_session_header(self):
        """Write the session header to the markdown file."""
        header_lines = [
            f"# AGS SPARQL Agent Interaction Log",
            f"**Session ID:** {self.session_id}",
            f"**Started:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
            "",
            "This file contains detailed logs of all interactions with the AGS SPARQL Agent during this session.",
            "",
            "---",
            ""
        ]
        
        with open(self.interactions_file, 'w', encoding='utf-8') as f:
            f.write("\n".join(header_lines))

    def complete_interaction(self, response: QuestionResponse, execution_times: Dict[str, float] = None) -> str:
        """Complete and save the interaction log.
        
        Args:
            response: The final QuestionResponse
            execution_times: Dict of execution times for different phases
            
        Returns:
            Path to the saved log file
        """
        if not hasattr(self, '_current_interaction'):
            self._debug_log("⚠️  No current interaction to complete")
            return None
        
        # Calculate total execution time
        end_time = datetime.now()
        total_time = (end_time - self._current_interaction["start_time"]).total_seconds()
        
        # Update execution times
        if execution_times:
            self._current_interaction["execution_times"].update(execution_times)
        
        # Extract final SPARQL query info
        final_sparql = ""
        classes_used = []
        properties_used = []
        query_attempts = len(self._current_interaction["sparql_attempts"])
        
        if self._current_interaction["sparql_attempts"]:
            last_attempt = self._current_interaction["sparql_attempts"][-1]
            final_sparql = last_attempt.get("expanded_query", "")
            classes_used = last_attempt.get("classes_used", [])
            properties_used = last_attempt.get("properties_used", [])
        
        # Create complete interaction log
        interaction_log = InteractionLog(
            timestamp=self._current_interaction["start_time"].isoformat(),
            session_id=self.session_id,
            interaction_id=self._current_interaction["interaction_id"],
            
            # Input
            original_question=self._current_interaction["question"],
            include_reasoning=self._current_interaction["include_reasoning"],
            include_execution_details=self._current_interaction["include_execution_details"],
            
            # Prompt engineering
            ontology_context_used=self._current_interaction.get("ontology_context", ""),
            system_prompt=self._current_interaction["prompts"].get("system_prompt", ""),
            user_prompt=self._current_interaction["prompts"].get("user_prompt", ""),
            
            # Query generation and iteration
            sparql_queries_attempted=self._current_interaction["sparql_attempts"],
            total_query_attempts=query_attempts,
            final_sparql_query=final_sparql,
            
            # Execution results
            execution_success=response.confidence > 0,  # Simple heuristic
            execution_error=None,  # Could extract from response if available
            results_count=response.sources_used,
            raw_results=[],  # Would need to pass this separately
            
            # Response generation
            natural_language_answer=response.answer,
            confidence_score=response.confidence,
            reasoning_steps=response.reasoning_steps,
            
            # Performance metrics
            total_execution_time=total_time,
            ontology_discovery_time=self._current_interaction["execution_times"].get("ontology_discovery"),
            query_generation_time=self._current_interaction["execution_times"].get("query_generation"),
            query_execution_time=self._current_interaction["execution_times"].get("query_execution"),
            
            # Agent state
            agent_memory_updated=False,  # Would need to track this
            ontology_classes_used=classes_used,
            ontology_properties_used=properties_used
        )
        
        # Initialize markdown file with session header if this is the first interaction
        if not self.interactions_file.exists():
            self._write_session_header()
        
        # Format and append interaction as markdown
        markdown_content = self._format_interaction_as_markdown(interaction_log)
        with open(self.interactions_file, 'a', encoding='utf-8') as f:
            f.write(markdown_content)
        
        # Update session statistics
        self._update_session_stats(interaction_log)
        
        # Save session summary
        self._save_session_summary()
        
        # Clean up current interaction
        delattr(self, '_current_interaction')
        
        self._debug_log(f"💾 Completed interaction {interaction_log.interaction_id}")
        self._debug_log(f"📊 Total time: {total_time:.2f}s, Confidence: {response.confidence:.2f}")
        
        return str(self.interactions_file)
    
    def _update_session_stats(self, interaction_log: InteractionLog):
        """Update session statistics with the completed interaction."""
        self.session_stats["total_interactions"] += 1
        
        if interaction_log.execution_success:
            self.session_stats["successful_interactions"] += 1
        else:
            self.session_stats["failed_interactions"] += 1
        
        # Update averages
        total = self.session_stats["total_interactions"]
        
        # Running average of execution time
        current_avg_time = self.session_stats["avg_execution_time"]
        self.session_stats["avg_execution_time"] = (
            (current_avg_time * (total - 1) + interaction_log.total_execution_time) / total
        )
        
        # Running average of confidence
        current_avg_conf = self.session_stats["avg_confidence"]
        self.session_stats["avg_confidence"] = (
            (current_avg_conf * (total - 1) + interaction_log.confidence_score) / total
        )
        
        # Track class usage
        for class_ref in interaction_log.ontology_classes_used:
            current = self.session_stats["most_used_classes"].get(class_ref, 0)
            self.session_stats["most_used_classes"][class_ref] = current + 1
        
        # Track property usage
        for prop_ref in interaction_log.ontology_properties_used:
            current = self.session_stats["most_used_properties"].get(prop_ref, 0)
            self.session_stats["most_used_properties"][prop_ref] = current + 1
    
    def _save_session_summary(self):
        """Save the current session summary to file."""
        self.session_stats["last_updated"] = datetime.now().isoformat()
        
        with open(self.summary_file, 'w', encoding='utf-8') as f:
            json.dump(self.session_stats, f, indent=2)
    
    def _debug_log(self, message: str):
        """Log debug messages to stderr to avoid MCP protocol interference."""
        if self.enable_debug_output:
            print(f"[LOGGER] {message}", file=sys.stderr)
    
    def get_session_summary(self) -> Dict[str, Any]:
        """Get the current session summary."""
        return self.session_stats.copy()
    
    def list_log_files(self) -> List[str]:
        """List all log files in the log directory."""
        return [str(f) for f in self.log_dir.glob("*.json*") if f.is_file()]


# Global logger instance (will be initialized when first used)
_global_logger: Optional[InteractionLogger] = None

def get_logger() -> InteractionLogger:
    """Get the global interaction logger instance."""
    global _global_logger
    if _global_logger is None:
        _global_logger = InteractionLogger()
    return _global_logger

def reset_logger():
    """Reset the global logger (useful for testing)."""
    global _global_logger
    _global_logger = None
