"""Verbose logging for Promethian agent execution."""

from __future__ import annotations

import json
import logging
import re
import time
from datetime import datetime
from pathlib import Path
from typing import Any, TYPE_CHECKING
from uuid import uuid4

if TYPE_CHECKING:
    from promethian.config import PromethianConfig
    from promethian.core.types import Plan, PlanStep


# Global logger instance (set during initialization)
_verbose_logger: "VerboseLogger | None" = None


def get_logger() -> "VerboseLogger":
    """Get the global verbose logger instance."""
    global _verbose_logger
    if _verbose_logger is None:
        # Return a disabled logger if not initialized
        _verbose_logger = VerboseLogger(enabled=False)
    return _verbose_logger


def init_logger(config: "PromethianConfig") -> "VerboseLogger":
    """Initialize the global verbose logger with config."""
    global _verbose_logger
    _verbose_logger = VerboseLogger(
        enabled=config.verbose_logging,
        log_dir=config.log_dir,
    )
    return _verbose_logger


class VerboseLogger:
    """
    Structured JSON logging for promethian execution.

    Logs every decision, tool call, API call, and execution detail
    to a per-session log file named after the user's task.
    """

    def __init__(
        self,
        enabled: bool = False,
        log_dir: Path | None = None,
    ):
        self.enabled = enabled
        self.session_id = uuid4().hex[:8]
        self._logger: logging.Logger | None = None
        self._log_dir = Path(log_dir).expanduser() if log_dir else None
        self._log_file: Path | None = None

    def _sanitize_filename(self, task: str) -> str:
        """Convert task text to a safe filename."""
        # Take first 50 chars of task
        name = task[:50].strip()
        # Replace unsafe chars with underscores
        name = re.sub(r'[<>:"/\\|?*\n\r\t]', '_', name)
        # Replace multiple spaces/underscores with single underscore
        name = re.sub(r'[\s_]+', '_', name)
        # Remove leading/trailing underscores
        name = name.strip('_')
        # Fallback if empty
        if not name:
            name = "session"
        return name

    def _setup_logger(self, task: str) -> None:
        """Set up the file logger for this session's task."""
        if not self._log_dir:
            return

        # Ensure log directory exists
        self._log_dir.mkdir(parents=True, exist_ok=True)

        # Create filename: timestamp_task-name.log
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        safe_name = self._sanitize_filename(task)
        self._log_file = self._log_dir / f"{timestamp}_{safe_name}.log"

        # Create logger
        self._logger = logging.getLogger(f"promethian.verbose.{self.session_id}")
        self._logger.setLevel(logging.DEBUG)
        self._logger.propagate = False  # Don't propagate to root logger

        # Remove existing handlers
        self._logger.handlers.clear()

        # Add file handler (no rotation - one file per session)
        handler = logging.FileHandler(
            self._log_file,
            encoding="utf-8",
        )
        handler.setLevel(logging.DEBUG)

        # Use a simple formatter that just outputs the message (we format as JSON)
        formatter = logging.Formatter("%(message)s")
        handler.setFormatter(formatter)

        self._logger.addHandler(handler)

    def _serialize_value(self, value: Any) -> Any:
        """Serialize a value for JSON output."""
        if isinstance(value, (str, int, float, bool, type(None))):
            return value
        if isinstance(value, (list, tuple)):
            return [self._serialize_value(v) for v in value]
        if isinstance(value, dict):
            return {k: self._serialize_value(v) for k, v in value.items()}
        if hasattr(value, "__dict__"):
            # Dataclass or object - try to serialize key attributes
            return str(value)
        return str(value)

    def log(self, event_type: str, message: str, **details: Any) -> None:
        """
        Log a structured event.

        Args:
            event_type: Category of event (session, plan, step, tool, api, etc.)
            message: Human-readable description
            **details: Additional structured data
        """
        if not self.enabled or not self._logger:
            return

        entry = {
            "timestamp": datetime.now().isoformat(),
            "session_id": self.session_id,
            "event_type": event_type,
            "message": message,
        }

        # Add serialized details
        for key, value in details.items():
            entry[key] = self._serialize_value(value)

        try:
            self._logger.info(json.dumps(entry, ensure_ascii=False))
            # Flush immediately so logs are visible in real-time
            for handler in self._logger.handlers:
                handler.flush()
        except Exception:
            # Don't let logging errors break execution
            pass

    # ===== Session Events =====

    def session_start(self, task: str) -> None:
        """Log session start. Creates the log file named after the task."""
        # Create the log file on first session_start call
        if self.enabled and self._log_dir and not self._logger:
            self._setup_logger(task)

        self.log(
            "session_start",
            f"Starting session for task",
            task=task,
        )

    def session_end(
        self,
        task: str,
        success: bool,
        duration_s: float,
        total_tokens: int = 0,
        total_cost_usd: float = 0.0,
    ) -> None:
        """Log session completion."""
        self.log(
            "session_end",
            f"Session completed: {'success' if success else 'failed'}",
            task=task,
            success=success,
            duration_s=round(duration_s, 2),
            total_tokens=total_tokens,
            total_cost_usd=round(total_cost_usd, 4),
        )

    # ===== Context Gathering Events =====

    def context_gathering_start(self, task: str) -> None:
        """Log start of context gathering."""
        self.log(
            "context_gathering_start",
            "Starting context gathering",
            task=task,
        )

    def context_gathered(
        self,
        sources: list[str],
        duration_s: float,
    ) -> None:
        """Log context gathering completion."""
        self.log(
            "context_gathered",
            f"Context gathered from {len(sources)} sources",
            sources=sources,
            duration_s=round(duration_s, 2),
        )

    # ===== Planning Events =====

    def planning_start(self, task: str) -> None:
        """Log start of planning."""
        self.log(
            "planning_start",
            "Starting task planning",
            task=task,
        )

    def plan_created(
        self,
        task: str,
        confidence: float,
        strategy: str,
        steps: list[str],
        reasoning: str,
        direct_response: bool = False,
    ) -> None:
        """Log plan creation."""
        if direct_response:
            self.log(
                "plan_created",
                "Direct response (no tools needed)",
                task=task,
                confidence=confidence,
                direct_response=True,
            )
        else:
            self.log(
                "plan_created",
                f"Plan created with {len(steps)} steps",
                task=task,
                confidence=confidence,
                strategy=strategy,
                steps=steps,
                reasoning=reasoning,
            )

    # ===== Execution Events =====

    def execution_start(self, task: str, step_count: int) -> None:
        """Log start of plan execution."""
        self.log(
            "execution_start",
            f"Starting execution of {step_count} steps",
            task=task,
            step_count=step_count,
        )

    def step_start(
        self,
        step_id: str,
        description: str,
        step_number: int,
        total_steps: int,
    ) -> None:
        """Log step execution start."""
        self.log(
            "step_start",
            f"Executing step {step_number}/{total_steps}: {description[:80]}",
            step_id=step_id,
            description=description,
            step_number=step_number,
            total_steps=total_steps,
        )

    def step_complete(
        self,
        step_id: str,
        success: bool,
        attempts: int,
        duration_s: float,
        output_preview: str | None = None,
        error: str | None = None,
    ) -> None:
        """Log step completion."""
        self.log(
            "step_complete",
            f"Step {'completed' if success else 'failed'} after {attempts} attempt(s)",
            step_id=step_id,
            success=success,
            attempts=attempts,
            duration_s=round(duration_s, 2),
            output_preview=output_preview[:500] if output_preview else None,
            error=error,
        )

    def plan_updated(
        self,
        after_step: str,
        added_steps: list[str],
        removed_steps: list[str],
        reasoning: str,
    ) -> None:
        """Log dynamic plan update."""
        self.log(
            "plan_updated",
            f"Plan updated: +{len(added_steps)} -{len(removed_steps)} steps",
            after_step=after_step,
            added_steps=added_steps,
            removed_steps=removed_steps,
            reasoning=reasoning,
        )

    # ===== Tool Call Events =====

    def tool_call_start(self, tool_name: str, inputs: dict) -> float:
        """Log tool call start. Returns start time for duration calculation."""
        # Truncate large input values for logging
        truncated_inputs = {}
        for k, v in inputs.items():
            if isinstance(v, str) and len(v) > 500:
                truncated_inputs[k] = v[:500] + "...(truncated)"
            else:
                truncated_inputs[k] = v

        self.log(
            "tool_call_start",
            f"Calling tool: {tool_name}",
            tool=tool_name,
            inputs=truncated_inputs,
        )
        return time.time()

    def tool_call_complete(
        self,
        tool_name: str,
        success: bool,
        start_time: float,
        result: Any = None,
        error: str | None = None,
    ) -> None:
        """Log tool call completion."""
        duration_ms = int((time.time() - start_time) * 1000)

        # Truncate result for logging
        result_preview = None
        if result is not None:
            result_str = str(result)
            result_preview = result_str[:500] + "..." if len(result_str) > 500 else result_str

        self.log(
            "tool_call_complete",
            f"Tool {tool_name} {'succeeded' if success else 'failed'} ({duration_ms}ms)",
            tool=tool_name,
            success=success,
            duration_ms=duration_ms,
            result_preview=result_preview,
            error=error,
        )

    # ===== Self-Healing Events =====

    def self_heal_start(
        self,
        step_id: str,
        attempt: int,
        max_attempts: int,
        error: str,
    ) -> None:
        """Log start of self-healing attempt."""
        self.log(
            "self_heal_start",
            f"Self-healing attempt {attempt}/{max_attempts}",
            step_id=step_id,
            attempt=attempt,
            max_attempts=max_attempts,
            error=error[:500],
        )

    def self_heal_suggestion(
        self,
        step_id: str,
        attempt: int,
        suggestion: str,
    ) -> None:
        """Log self-healing suggestion."""
        self.log(
            "self_heal_suggestion",
            f"Healing suggestion for attempt {attempt}",
            step_id=step_id,
            attempt=attempt,
            suggestion=suggestion[:1000],
        )

    # ===== API Call Events =====

    def api_call(
        self,
        model: str,
        input_tokens: int,
        output_tokens: int,
        latency_ms: int,
        cache_read_tokens: int = 0,
        cache_write_tokens: int = 0,
        cost_usd: float = 0.0,
        purpose: str = "",
    ) -> None:
        """Log Claude API call."""
        self.log(
            "api_call",
            f"Claude API call to {model}",
            model=model,
            input_tokens=input_tokens,
            output_tokens=output_tokens,
            total_tokens=input_tokens + output_tokens,
            latency_ms=latency_ms,
            cache_read_tokens=cache_read_tokens,
            cache_write_tokens=cache_write_tokens,
            cost_usd=round(cost_usd, 6),
            purpose=purpose,
        )

    # ===== Learning Events =====

    def skill_extracted(
        self,
        skill_name: str,
        task: str,
    ) -> None:
        """Log skill extraction."""
        self.log(
            "skill_extracted",
            f"Extracted skill: {skill_name}",
            skill_name=skill_name,
            from_task=task,
        )

    def tool_built(
        self,
        tool_name: str,
        description: str,
    ) -> None:
        """Log tool building."""
        self.log(
            "tool_built",
            f"Built new tool: {tool_name}",
            tool_name=tool_name,
            description=description,
        )

    def contributions_submitted(
        self,
        skills_count: int,
        tools_count: int,
        skill_names: list[str] | None = None,
        tool_names: list[str] | None = None,
        session_success: bool = True,
    ) -> None:
        """Log contribution submission to knowledge base."""
        if skills_count == 0 and tools_count == 0:
            return  # Don't log if nothing was contributed

        self.log(
            "contributions_submitted",
            f"Contributed {skills_count} skill(s), {tools_count} tool(s) to knowledge base",
            skills_count=skills_count,
            tools_count=tools_count,
            skill_names=skill_names or [],
            tool_names=tool_names or [],
            session_success=session_success,
        )

    def confidence_calibration(
        self,
        predicted_confidence: float,
        actual_success: bool,
        task: str,
        strategy: str,
    ) -> None:
        """
        Log confidence calibration data for tracking prediction accuracy.

        This helps identify if the planner's confidence predictions are accurate.
        """
        calibration_error = abs(predicted_confidence - (1.0 if actual_success else 0.0))
        is_overconfident = predicted_confidence > 0.7 and not actual_success
        is_underconfident = predicted_confidence < 0.5 and actual_success

        self.log(
            "confidence_calibration",
            f"Confidence: {predicted_confidence:.0%}, Actual: {'success' if actual_success else 'failure'}",
            predicted_confidence=round(predicted_confidence, 2),
            actual_success=actual_success,
            calibration_error=round(calibration_error, 2),
            is_overconfident=is_overconfident,
            is_underconfident=is_underconfident,
            task=task[:100],
            strategy=strategy,
        )

    # ===== Retrieval Events =====

    def skills_retrieved(
        self,
        count: int,
        skill_names: list[str],
    ) -> None:
        """Log skill retrieval."""
        self.log(
            "skills_retrieved",
            f"Retrieved {count} relevant skills",
            count=count,
            skills=skill_names,
        )

    def tools_retrieved(
        self,
        count: int,
        tool_names: list[str],
    ) -> None:
        """Log tool retrieval."""
        self.log(
            "tools_retrieved",
            f"Retrieved {count} relevant tools",
            count=count,
            tools=tool_names,
        )

    # ===== Error Events =====

    def error(
        self,
        error_type: str,
        message: str,
        details: dict | None = None,
    ) -> None:
        """Log an error."""
        self.log(
            "error",
            f"Error ({error_type}): {message}",
            error_type=error_type,
            error_message=message,
            details=details or {},
        )

    # ===== Thought/Decision Events =====

    def decision(
        self,
        decision_type: str,
        choice: str,
        reasoning: str,
        alternatives: list[str] | None = None,
    ) -> None:
        """Log a decision made by the agent."""
        self.log(
            "decision",
            f"Decision ({decision_type}): {choice}",
            decision_type=decision_type,
            choice=choice,
            reasoning=reasoning,
            alternatives=alternatives or [],
        )

    def thought(
        self,
        context: str,
        thought: str,
    ) -> None:
        """Log agent reasoning/thought."""
        self.log(
            "thought",
            thought[:200],
            context=context,
            full_thought=thought,
        )
