"""Structured logger for Fluxibly components.

Provides component-level logging with type tagging for easy filtering.
Built on loguru. Each Logger instance respects a configurable minimum level,
set via LLMConfig.log_level or AgentConfig.log_level.
"""

from __future__ import annotations

from typing import Any

import loguru

_SENSITIVE_KEYS = frozenset(
    {"api_key", "password", "secret", "token", "authorization"}
)

# Numeric log levels for comparison (matches loguru internals)
_LEVEL_MAP: dict[str, int] = {
    "TRACE": 5,
    "DEBUG": 10,
    "INFO": 20,
    "SUCCESS": 25,
    "WARNING": 30,
    "ERROR": 40,
    "CRITICAL": 50,
}


class Logger:
    """Structured logger for Fluxibly components.

    Each log entry includes:
    - component: "llm" | "agent" | "tools" | etc.
    - level: "DEBUG" | "INFO" | "WARNING" | "ERROR"
    - type: specific log type for filtering (e.g. "llm.input", "agent.error")
    - data: structured log data

    The minimum log level is set at construction time via the `level` parameter
    (from LLMConfig.log_level or AgentConfig.log_level). Messages below this
    level are silently dropped.
    """

    def __init__(
        self, component: str, level: str = "INFO", **tags: Any
    ) -> None:
        self.component = component
        self.tags = tags
        self._min_level = _LEVEL_MAP.get(level.upper(), 20)
        self._logger = loguru.logger.bind(component=component, **tags)

    def _should_log(self, level: str) -> bool:
        """Check if a message at the given level should be emitted."""
        return _LEVEL_MAP.get(level, 20) >= self._min_level

    def log_input(self, data: dict[str, Any]) -> None:
        """Log input to LLM/Agent.

        INFO: sanitized summary (secrets masked, long strings truncated).
        DEBUG: full unsanitized payload.
        """
        if self._should_log("DEBUG"):
            self._logger.bind(type=f"{self.component}.input").debug(
                f"{self.component.upper()} input (full)",
                data=data,
            )
        if self._should_log("INFO"):
            self._logger.bind(type=f"{self.component}.input").info(
                f"{self.component.upper()} input",
                data=self._sanitize(data),
            )

    def log_output(self, data: Any) -> None:
        """Log output from LLM/Agent.

        INFO: preview (first 200 chars, token usage, status).
        DEBUG: full output text and all content.
        """
        # Defer import to avoid circular dependency at module load time
        from fluxibly.schema.response import LLMResponse

        if isinstance(data, LLMResponse):
            if self._should_log("DEBUG"):
                full_data: dict[str, Any] = {
                    "output_text": data.output.output_text,
                    "tool_calls": [
                        tc.model_dump() for tc in data.output.tool_calls
                    ],
                    "reasoning": data.output.reasoning,
                    "usage": (
                        data.metadata.usage.model_dump()
                        if data.metadata.usage
                        else None
                    ),
                    "stop_reason": data.metadata.stop_reason,
                    "model": data.metadata.model,
                }
                self._logger.bind(type=f"{self.component}.output").debug(
                    f"{self.component.upper()} output (full)",
                    data=full_data,
                )
            if self._should_log("INFO"):
                log_data: dict[str, Any] = {
                    "output_text_preview": (
                        data.output.output_text[:200]
                        if data.output.output_text
                        else ""
                    ),
                    "tool_calls": len(data.output.tool_calls),
                    "usage": (
                        data.metadata.usage.model_dump()
                        if data.metadata.usage
                        else None
                    ),
                    "status": data.metadata.status,
                }
                self._logger.bind(type=f"{self.component}.output").info(
                    f"{self.component.upper()} output",
                    data=log_data,
                )
        else:
            if self._should_log("INFO"):
                self._logger.bind(type=f"{self.component}.output").info(
                    f"{self.component.upper()} output",
                    data=data,
                )

    def log_error(
        self, error: Exception, context: dict[str, Any] | None = None
    ) -> None:
        """Log error (ERROR level)."""
        if not self._should_log("ERROR"):
            return
        self._logger.bind(type=f"{self.component}.error").opt(depth=1).error(
            "{component} error: {err_type}: {err_msg}",
            component=self.component.upper(),
            err_type=type(error).__name__,
            err_msg=str(error),
            data={"context": context},
        )

    def log_event(
        self,
        event_type: str,
        data: dict[str, Any] | None = None,
        level: str = "INFO",
    ) -> None:
        """Log a structured event.

        Use for domain-specific events: handoffs, skill loads, tool calls, etc.

        Args:
            event_type: Short event identifier (e.g. "handoff", "skill.load", "tool.call").
            data: Optional structured data to include.
            level: Log level (default: INFO).
        """
        if not self._should_log(level):
            return
        log_fn = getattr(
            self._logger.bind(type=f"{self.component}.{event_type}"),
            level.lower(),
            self._logger.info,
        )
        log_fn(
            "{component} {event}",
            component=self.component.upper(),
            event=event_type,
            data=data or {},
        )

    def debug(self, msg: str, **kwargs: Any) -> None:
        if self._should_log("DEBUG"):
            self._logger.debug(msg, **kwargs)

    def info(self, msg: str, **kwargs: Any) -> None:
        if self._should_log("INFO"):
            self._logger.info(msg, **kwargs)

    def warning(self, msg: str, **kwargs: Any) -> None:
        if self._should_log("WARNING"):
            self._logger.warning(msg, **kwargs)

    def error(self, msg: str, **kwargs: Any) -> None:
        if self._should_log("ERROR"):
            self._logger.error(msg, **kwargs)

    def _sanitize(self, data: dict[str, Any]) -> dict[str, Any]:
        """Sanitize data for logging (truncate long values, mask secrets)."""
        sanitized: dict[str, Any] = {}
        for key, value in data.items():
            if any(s in key.lower() for s in _SENSITIVE_KEYS):
                sanitized[key] = "***"
            elif isinstance(value, str) and len(value) > 500:
                sanitized[key] = value[:500] + "..."
            elif isinstance(value, list) and len(value) > 10:
                sanitized[key] = f"[{len(value)} items]"
            else:
                sanitized[key] = value
        return sanitized
