"""
Monitoring and logging for Natural Language Query system.

Tracks query execution, performance metrics, and usage statistics.
Provides production-ready observability for the NLQ engine.
"""

import logging
import time
import json
from typing import Dict, Any, Optional, List
from pathlib import Path
from datetime import datetime
from collections import defaultdict


class NLQMonitoring:
    """
    Monitor NLQ system performance and usage.

    Tracks queries, latency, success rates, token usage, and cache performance.
    Provides metrics for production monitoring and optimization.
    """

    def __init__(
        self,
        log_file: Optional[str] = None,
        log_level: int = logging.INFO,
        enable_console: bool = False,
    ):
        """
        Initialize NLQ monitoring.

        Args:
            log_file: Path to log file (default: ./logs/nlq_queries.log)
            log_level: Logging level (default: INFO)
            enable_console: Also log to console (default: False)
        """
        self.logger = logging.getLogger("outhad_edge.nlq")
        self.logger.setLevel(log_level)

        # Remove existing handlers to avoid duplicates
        self.logger.handlers = []

        # Create logs directory if needed
        if log_file is None:
            log_dir = Path("./logs")
            log_dir.mkdir(exist_ok=True)
            log_file = str(log_dir / "nlq_queries.log")

        # File handler
        file_handler = logging.FileHandler(log_file)
        formatter = logging.Formatter(
            "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S",
        )
        file_handler.setFormatter(formatter)
        self.logger.addHandler(file_handler)

        # Console handler (optional)
        if enable_console:
            console_handler = logging.StreamHandler()
            console_handler.setFormatter(formatter)
            self.logger.addHandler(console_handler)

        # In-memory metrics for aggregation
        self._metrics: Dict[str, Any] = {
            "total_queries": 0,
            "successful_queries": 0,
            "failed_queries": 0,
            "cache_hits": 0,
            "cache_misses": 0,
            "total_latency": 0.0,
            "total_tokens": 0,
            "queries_by_model": defaultdict(int),
            "errors_by_type": defaultdict(int),
        }

        self._query_history: List[Dict[str, Any]] = []
        self._max_history = 1000  # Keep last 1000 queries in memory

        self.logger.info("NLQ Monitoring initialized")

    def log_query(
        self,
        query: str,
        success: bool,
        latency: float,
        model: str = "unknown",
        tokens_used: int = 0,
        cached: bool = False,
        error: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> None:
        """
        Log query execution.

        Args:
            query: User query text
            success: Whether query succeeded
            latency: Execution time in seconds
            model: LLM model used
            tokens_used: Number of tokens consumed
            cached: Whether result was from cache
            error: Error message if failed
            metadata: Additional metadata
        """
        log_data = {
            "timestamp": datetime.now().isoformat(),
            "query": query[:200],  # Truncate long queries
            "success": success,
            "latency_ms": round(latency * 1000, 2),
            "model": model,
            "tokens": tokens_used,
            "cached": cached,
        }

        if error:
            log_data["error"] = error

        if metadata:
            log_data.update(metadata)

        # Log as JSON for easy parsing
        log_message = json.dumps(log_data)

        if success:
            self.logger.info(log_message)
        else:
            self.logger.error(log_message)

        # Update metrics
        self._update_metrics(
            success=success,
            latency=latency,
            model=model,
            tokens=tokens_used,
            cached=cached,
            error=error,
        )

        # Store in history (limited size)
        self._query_history.append(log_data)
        if len(self._query_history) > self._max_history:
            self._query_history.pop(0)

    def _update_metrics(
        self,
        success: bool,
        latency: float,
        model: str,
        tokens: int,
        cached: bool,
        error: Optional[str],
    ) -> None:
        """
        Update in-memory metrics.

        Args:
            success: Whether query succeeded
            latency: Execution time in seconds
            model: LLM model used
            tokens: Token count
            cached: Whether cached
            error: Error message
        """
        self._metrics["total_queries"] += 1

        if success:
            self._metrics["successful_queries"] += 1
        else:
            self._metrics["failed_queries"] += 1

        if cached:
            self._metrics["cache_hits"] += 1
        else:
            self._metrics["cache_misses"] += 1

        self._metrics["total_latency"] += latency
        self._metrics["total_tokens"] += tokens
        self._metrics["queries_by_model"][model] += 1

        if error:
            # Extract error type
            error_type = error.split(":")[0] if ":" in error else "Unknown"
            self._metrics["errors_by_type"][error_type] += 1

    def get_metrics(self) -> Dict[str, Any]:
        """
        Get aggregated usage metrics.

        Returns:
            dict: Performance and usage statistics
        """
        total_queries = self._metrics["total_queries"]

        if total_queries == 0:
            return {
                "total_queries": 0,
                "success_rate": 0.0,
                "avg_latency_ms": 0.0,
                "cache_hit_rate": 0.0,
                "total_tokens": 0,
            }

        metrics = {
            "total_queries": total_queries,
            "successful_queries": self._metrics["successful_queries"],
            "failed_queries": self._metrics["failed_queries"],
            "success_rate": round(
                self._metrics["successful_queries"] / total_queries * 100, 2
            ),
            "cache_hits": self._metrics["cache_hits"],
            "cache_misses": self._metrics["cache_misses"],
            "cache_hit_rate": round(
                self._metrics["cache_hits"] / total_queries * 100, 2
            ),
            "avg_latency_ms": round(
                (self._metrics["total_latency"] / total_queries) * 1000, 2
            ),
            "total_tokens": self._metrics["total_tokens"],
            "avg_tokens_per_query": round(
                self._metrics["total_tokens"] / total_queries, 2
            ),
            "queries_by_model": dict(self._metrics["queries_by_model"]),
            "errors_by_type": dict(self._metrics["errors_by_type"]),
        }

        return metrics

    def get_recent_queries(self, limit: int = 10) -> List[Dict[str, Any]]:
        """
        Get recent query history.

        Args:
            limit: Number of recent queries to return

        Returns:
            list: Recent queries
        """
        return self._query_history[-limit:]

    def reset_metrics(self) -> None:
        """Reset all metrics to zero."""
        self._metrics = {
            "total_queries": 0,
            "successful_queries": 0,
            "failed_queries": 0,
            "cache_hits": 0,
            "cache_misses": 0,
            "total_latency": 0.0,
            "total_tokens": 0,
            "queries_by_model": defaultdict(int),
            "errors_by_type": defaultdict(int),
        }
        self._query_history = []
        self.logger.info("Metrics reset")

    def export_metrics(self, output_file: str) -> None:
        """
        Export metrics to JSON file.

        Args:
            output_file: Path to output file
        """
        metrics = self.get_metrics()
        metrics["timestamp"] = datetime.now().isoformat()
        metrics["recent_queries"] = self.get_recent_queries(20)

        with open(output_file, "w") as f:
            json.dump(metrics, f, indent=2, default=str)

        self.logger.info(f"Metrics exported to {output_file}")

    def log_performance_warning(self, message: str, metric_name: str, value: float, threshold: float) -> None:
        """
        Log performance warning when metrics exceed thresholds.

        Args:
            message: Warning message
            metric_name: Name of the metric
            value: Current value
            threshold: Threshold value
        """
        warning_data = {
            "type": "performance_warning",
            "metric": metric_name,
            "value": value,
            "threshold": threshold,
            "message": message,
            "timestamp": datetime.now().isoformat(),
        }

        self.logger.warning(json.dumps(warning_data))

    def log_error(self, error_type: str, error_message: str, context: Optional[Dict[str, Any]] = None) -> None:
        """
        Log error with context.

        Args:
            error_type: Type of error
            error_message: Error message
            context: Additional context
        """
        error_data = {
            "type": "error",
            "error_type": error_type,
            "message": error_message,
            "timestamp": datetime.now().isoformat(),
        }

        if context:
            error_data["context"] = context

        self.logger.error(json.dumps(error_data))


def test_monitoring():
    """Test the monitoring system."""
    print("=" * 80)
    print("Testing NLQ Monitoring")
    print("=" * 80)

    # Create monitoring instance
    monitor = NLQMonitoring(enable_console=True)

    # Test 1: Log successful query
    print("\nTest 1: Log successful query")
    monitor.log_query(
        query="What is the conversion rate?",
        success=True,
        latency=1.5,
        model="gpt-4",
        tokens_used=150,
        cached=False,
    )

    # Test 2: Log cached query
    print("\nTest 2: Log cached query")
    monitor.log_query(
        query="What is the conversion rate?",
        success=True,
        latency=0.1,
        model="gpt-4",
        tokens_used=0,
        cached=True,
    )

    # Test 3: Log failed query
    print("\nTest 3: Log failed query")
    monitor.log_query(
        query="Invalid query",
        success=False,
        latency=0.5,
        model="gpt-4",
        tokens_used=50,
        cached=False,
        error="ValidationError: Invalid query format",
    )

    # Test 4: Get metrics
    print("\nTest 4: Get metrics")
    metrics = monitor.get_metrics()
    print(json.dumps(metrics, indent=2))

    # Test 5: Get recent queries
    print("\nTest 5: Recent queries")
    recent = monitor.get_recent_queries(limit=3)
    for i, query in enumerate(recent, 1):
        print(f"  Query {i}: {query['query'][:50]} - Success: {query['success']}")

    # Test 6: Performance warning
    print("\nTest 6: Performance warning")
    monitor.log_performance_warning(
        message="High latency detected",
        metric_name="avg_latency_ms",
        value=2500,
        threshold=2000,
    )

    print("\n✅ Monitoring tests completed")
    print("=" * 80)


if __name__ == "__main__":
    test_monitoring()
