"""
Callback monitoring and debugging for FSM state transitions.

This module provides tools for monitoring callback execution,
tracking performance, and debugging transition issues.
"""

import logging
import threading
import time
from collections import defaultdict
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Optional

from django.conf import settings

from .callback_base import TransitionContext

logger = logging.getLogger(__name__)


@dataclass
class CallbackExecutionRecord:
    """Record of a single callback execution."""

    callback_name: str
    model_name: str
    field_name: str
    source_state: str
    target_state: str
    stage: str
    timestamp: datetime
    duration_ms: float
    success: bool
    error_message: str | None = None
    instance_id: int | None = None
    user_id: int | None = None


@dataclass
class CallbackStats:
    """Statistics for a specific callback."""

    callback_name: str
    total_executions: int = 0
    successful_executions: int = 0
    failed_executions: int = 0
    total_duration_ms: float = 0.0
    min_duration_ms: float = float("inf")
    max_duration_ms: float = 0.0
    last_execution: datetime | None = None
    last_error: str | None = None

    @property
    def avg_duration_ms(self) -> float:
        """Calculate average execution duration."""
        if self.total_executions == 0:
            return 0.0
        return self.total_duration_ms / self.total_executions

    @property
    def success_rate(self) -> float:
        """Calculate success rate as percentage."""
        if self.total_executions == 0:
            return 0.0
        return (self.successful_executions / self.total_executions) * 100

    def record_execution(
        self,
        duration_ms: float,
        success: bool,
        error_message: str | None = None,
    ) -> None:
        """Record a callback execution."""
        self.total_executions += 1
        self.total_duration_ms += duration_ms
        self.min_duration_ms = min(self.min_duration_ms, duration_ms)
        self.max_duration_ms = max(self.max_duration_ms, duration_ms)
        self.last_execution = datetime.now()

        if success:
            self.successful_executions += 1
        else:
            self.failed_executions += 1
            self.last_error = error_message


class CallbackMonitor:
    """
    Monitor for tracking callback execution and collecting metrics.

    Provides:
    - Execution time tracking
    - Success/failure counting
    - Error logging
    - Performance statistics
    """

    _instance: Optional["CallbackMonitor"] = None
    _lock = threading.Lock()

    def __new__(cls) -> "CallbackMonitor":
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
                    cls._instance._initialized = False
        return cls._instance

    def __init__(self):
        if self._initialized:
            return

        self._stats: dict[str, CallbackStats] = defaultdict(lambda: CallbackStats(callback_name=""))
        self._recent_executions: list[CallbackExecutionRecord] = []
        self._max_recent_records = 1000
        self._enabled = self._check_enabled()
        self._debug_mode = self._check_debug_mode()
        self._initialized = True

    def _check_enabled(self) -> bool:
        """Check if monitoring is enabled."""
        callback_settings = getattr(settings, "STATE_MACHINE_CALLBACKS", {})
        return callback_settings.get("monitoring_enabled", True)

    def _check_debug_mode(self) -> bool:
        """Check if debug mode is enabled."""
        callback_settings = getattr(settings, "STATE_MACHINE_CALLBACKS", {})
        return callback_settings.get("debug_mode", settings.DEBUG)

    def is_enabled(self) -> bool:
        """Check if monitoring is currently enabled."""
        return self._enabled

    def enable(self) -> None:
        """Enable monitoring."""
        self._enabled = True
        logger.info("Callback monitoring enabled")

    def disable(self) -> None:
        """Disable monitoring."""
        self._enabled = False
        logger.info("Callback monitoring disabled")

    def set_debug_mode(self, enabled: bool) -> None:
        """Set debug mode."""
        self._debug_mode = enabled
        logger.info(f"Callback debug mode {'enabled' if enabled else 'disabled'}")

    def record_execution(
        self,
        callback_name: str,
        context: TransitionContext,
        stage: str,
        duration_ms: float,
        success: bool,
        error_message: str | None = None,
    ) -> None:
        """
        Record a callback execution.

        Args:
            callback_name: Name of the executed callback.
            context: The transition context.
            stage: Callback stage (pre/post/error).
            duration_ms: Execution duration in milliseconds.
            success: Whether execution was successful.
            error_message: Error message if execution failed.
        """
        if not self._enabled:
            return

        # Update stats
        stats = self._stats[callback_name]
        stats.callback_name = callback_name
        stats.record_execution(duration_ms, success, error_message)

        # Create execution record
        record = CallbackExecutionRecord(
            callback_name=callback_name,
            model_name=context.model_name,
            field_name=context.field_name,
            source_state=context.source_state,
            target_state=context.target_state,
            stage=stage,
            timestamp=datetime.now(),
            duration_ms=duration_ms,
            success=success,
            error_message=error_message,
            instance_id=context.instance.pk if context.instance else None,
            user_id=context.user.id if context.user else None,
        )

        # Store recent executions (with size limit)
        self._recent_executions.append(record)
        if len(self._recent_executions) > self._max_recent_records:
            self._recent_executions = self._recent_executions[-self._max_recent_records :]

        # Log in debug mode
        if self._debug_mode:
            self._log_execution(record)

    def _log_execution(self, record: CallbackExecutionRecord) -> None:
        """Log callback execution details."""
        status = "✓" if record.success else "✗"
        log_message = (
            f"{status} Callback: {record.callback_name} "
            f"({record.model_name}.{record.field_name}: "
            f"{record.source_state} → {record.target_state}) "
            f"[{record.stage}] {record.duration_ms:.2f}ms"
        )

        if record.success:
            logger.debug(log_message)
        else:
            logger.warning(f"{log_message} - Error: {record.error_message}")

    def get_stats(self, callback_name: str | None = None) -> dict[str, CallbackStats]:
        """
        Get callback statistics.

        Args:
            callback_name: If provided, return stats for this callback only.

        Returns:
            Dictionary of callback stats.
        """
        if callback_name:
            if callback_name in self._stats:
                return {callback_name: self._stats[callback_name]}
            return {}
        return dict(self._stats)

    def get_recent_executions(
        self,
        limit: int = 100,
        callback_name: str | None = None,
        model_name: str | None = None,
        success_only: bool | None = None,
    ) -> list[CallbackExecutionRecord]:
        """
        Get recent execution records.

        Args:
            limit: Maximum number of records to return.
            callback_name: Filter by callback name.
            model_name: Filter by model name.
            success_only: If True, only successful; if False, only failed.

        Returns:
            List of execution records.
        """
        records = self._recent_executions.copy()

        # Apply filters
        if callback_name:
            records = [r for r in records if r.callback_name == callback_name]
        if model_name:
            records = [r for r in records if r.model_name == model_name]
        if success_only is not None:
            records = [r for r in records if r.success == success_only]

        # Return most recent first
        return list(reversed(records[-limit:]))

    def get_failure_summary(self) -> dict[str, Any]:
        """Get a summary of callback failures."""
        failures = [r for r in self._recent_executions if not r.success]

        # Group by callback
        by_callback: dict[str, list[CallbackExecutionRecord]] = defaultdict(list)
        for record in failures:
            by_callback[record.callback_name].append(record)

        # Build summary
        summary = {
            "total_failures": len(failures),
            "by_callback": {
                name: {
                    "count": len(records),
                    "last_error": records[-1].error_message if records else None,
                    "last_occurrence": records[-1].timestamp if records else None,
                }
                for name, records in by_callback.items()
            },
        }

        return summary

    def get_performance_report(self) -> dict[str, Any]:
        """Get a performance report for all callbacks."""
        report = {
            "callbacks": {},
            "summary": {
                "total_callbacks": len(self._stats),
                "total_executions": sum(s.total_executions for s in self._stats.values()),
                "total_failures": sum(s.failed_executions for s in self._stats.values()),
                "avg_duration_ms": 0.0,
            },
        }

        total_duration = 0.0
        total_count = 0

        for name, stats in self._stats.items():
            report["callbacks"][name] = {
                "executions": stats.total_executions,
                "success_rate": f"{stats.success_rate:.1f}%",
                "avg_duration_ms": f"{stats.avg_duration_ms:.2f}",
                "min_duration_ms": f"{stats.min_duration_ms:.2f}" if stats.min_duration_ms != float("inf") else "N/A",
                "max_duration_ms": f"{stats.max_duration_ms:.2f}",
                "last_execution": stats.last_execution.isoformat() if stats.last_execution else None,
            }
            total_duration += stats.total_duration_ms
            total_count += stats.total_executions

        if total_count > 0:
            report["summary"]["avg_duration_ms"] = total_duration / total_count

        return report

    def clear_stats(self) -> None:
        """Clear all statistics."""
        self._stats.clear()
        self._recent_executions.clear()
        logger.info("Callback statistics cleared")

    @classmethod
    def reset_instance(cls) -> None:
        """Reset the singleton instance. For testing."""
        cls._instance = None


# Global monitor instance
callback_monitor = CallbackMonitor()


class TimedCallbackExecution:
    """
    Context manager for timing callback execution.

    Usage:
        with TimedCallbackExecution(callback, context, stage) as timer:
            callback.execute(context)
        # Timer automatically records execution
    """

    def __init__(
        self,
        callback_name: str,
        context: TransitionContext,
        stage: str,
    ):
        self.callback_name = callback_name
        self.context = context
        self.stage = stage
        self.start_time = 0.0
        self.success = True
        self.error_message: str | None = None

    def __enter__(self) -> "TimedCallbackExecution":
        self.start_time = time.perf_counter()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
        duration_ms = (time.perf_counter() - self.start_time) * 1000

        if exc_type is not None:
            self.success = False
            self.error_message = str(exc_val)

        callback_monitor.record_execution(
            callback_name=self.callback_name,
            context=self.context,
            stage=self.stage,
            duration_ms=duration_ms,
            success=self.success,
            error_message=self.error_message,
        )

        # Don't suppress exceptions
        return False

    def mark_failure(self, error_message: str) -> None:
        """Mark execution as failed."""
        self.success = False
        self.error_message = error_message


def log_transition_start(context: TransitionContext) -> None:
    """Log the start of a transition."""
    if callback_monitor._debug_mode:
        logger.debug(
            f"→ Starting transition: {context.model_name}.{context.field_name} "
            f"{context.source_state} → {context.target_state}"
        )


def log_transition_end(
    context: TransitionContext,
    success: bool,
    duration_ms: float,
) -> None:
    """Log the end of a transition."""
    if callback_monitor._debug_mode:
        status = "✓" if success else "✗"
        logger.debug(
            f"{status} Completed transition: {context.model_name}.{context.field_name} "
            f"{context.source_state} → {context.target_state} [{duration_ms:.2f}ms]"
        )


def get_callback_execution_order(
    model_name: str,
    source: str,
    target: str,
) -> list[tuple[str, str, int]]:
    """
    Get the order of callback execution for a transition.

    Args:
        model_name: Name of the model.
        source: Source state.
        target: Target state.

    Returns:
        List of (stage, callback_name, priority) tuples in execution order.
    """
    from .callback_base import CallbackStage

    order = []

    for stage in [CallbackStage.PRE, CallbackStage.POST, CallbackStage.ERROR]:
        # We need to get the model class, but we only have the name
        # This is mainly for debugging, so we'll return what we can
        order.append((stage.value, f"[{model_name}:{source}→{target}]", 0))

    return order


__all__ = [
    "CallbackExecutionRecord",
    "CallbackStats",
    "CallbackMonitor",
    "callback_monitor",
    "TimedCallbackExecution",
    "log_transition_start",
    "log_transition_end",
    "get_callback_execution_order",
]
