"""Structured logging for brawny.

Provides JSON-formatted logs with correlation IDs for request tracing.
"""

from __future__ import annotations

import logging
import re
import sys
import uuid
from contextvars import ContextVar
from datetime import datetime, timezone
from typing import Any, Literal

import structlog

from brawny.model.enums import LogFormat

# Module-level state for mode switching
_runtime_log_level: str = "INFO"
_runtime_log_format: LogFormat = LogFormat.JSON
_runtime_chain_id: int | None = None

# Context variable for correlation ID propagation
_correlation_id: ContextVar[str | None] = ContextVar("correlation_id", default=None)


def get_correlation_id() -> str:
    """Get the current correlation ID or generate a new one."""
    cid = _correlation_id.get()
    if cid is None:
        cid = generate_correlation_id()
        _correlation_id.set(cid)
    return cid


def set_correlation_id(correlation_id: str) -> None:
    """Set the correlation ID for the current context."""
    _correlation_id.set(correlation_id)


def generate_correlation_id() -> str:
    """Generate a new correlation ID."""
    return f"req_{uuid.uuid4().hex[:12]}"


def add_correlation_id(
    logger: logging.Logger, method_name: str, event_dict: dict[str, Any]
) -> dict[str, Any]:
    """Processor to add correlation_id to log entries."""
    event_dict["correlation_id"] = get_correlation_id()
    return event_dict


def add_timestamp(
    logger: logging.Logger, method_name: str, event_dict: dict[str, Any]
) -> dict[str, Any]:
    """Processor to add ISO8601 timestamp to log entries."""
    event_dict["timestamp"] = datetime.now(timezone.utc).isoformat()
    return event_dict


def rename_event_to_event_key(
    logger: logging.Logger, method_name: str, event_dict: dict[str, Any]
) -> dict[str, Any]:
    """Rename 'event' to match our log format."""
    # structlog uses 'event' by default, we keep it
    return event_dict


def add_log_level(
    logger: logging.Logger, method_name: str, event_dict: dict[str, Any]
) -> dict[str, Any]:
    """Add uppercase log level."""
    event_dict["level"] = method_name.upper()
    return event_dict


_URL_CRED_RE = re.compile(r"(https?://)([^/@\s]+):([^/@\s]+)@")


def _redact_url_creds(value: str) -> str:
    return _URL_CRED_RE.sub(r"\1***@", value)


def _redact_value(value: Any) -> Any:
    if isinstance(value, str):
        return _redact_url_creds(value)
    if isinstance(value, dict):
        redacted: dict[str, Any] = {}
        for key, item in value.items():
            key_lower = str(key).lower()
            if key_lower in {"authorization", "proxy-authorization"}:
                redacted[key] = "***"
            else:
                redacted[key] = _redact_value(item)
        return redacted
    if isinstance(value, (list, tuple)):
        return type(value)(_redact_value(item) for item in value)
    return value


def redact_sensitive(
    logger: logging.Logger, method_name: str, event_dict: dict[str, Any]
) -> dict[str, Any]:
    """Redact credentials or auth tokens from log fields."""
    return {key: _redact_value(value) for key, value in event_dict.items()}


def setup_logging(
    log_level: str = "INFO",
    log_format: LogFormat = LogFormat.JSON,
    chain_id: int | None = None,
    mode: Literal["startup", "runtime"] = "runtime",
) -> None:
    """Configure structured logging for the application.

    Args:
        log_level: Log level (DEBUG, INFO, WARNING, ERROR)
        log_format: Output format (json or text)
        chain_id: Chain ID to include in all log entries
        mode: "startup" uses human-readable ConsoleRenderer with WARNING level,
              "runtime" uses configured format and level
    """
    global _runtime_log_level, _runtime_log_format, _runtime_chain_id

    # Store runtime config for later switch
    _runtime_log_level = log_level
    _runtime_log_format = log_format
    _runtime_chain_id = chain_id

    # Startup mode: only show warnings/errors, human-readable format
    effective_level = "WARNING" if mode == "startup" else log_level

    # Set up stdlib logging
    logging.basicConfig(
        format="%(message)s",
        stream=sys.stdout,
        level=getattr(logging, effective_level.upper()),
        force=True,  # Allow reconfiguration
    )

    # Suppress noisy HTTP client logs (they can leak tokens in URLs)
    logging.getLogger("httpx").setLevel(logging.WARNING)
    logging.getLogger("httpcore").setLevel(logging.WARNING)

    # Common processors
    processors: list[structlog.types.Processor] = [
        structlog.stdlib.filter_by_level,
        add_timestamp,
        add_log_level,
        add_correlation_id,
        structlog.stdlib.add_logger_name,
        structlog.stdlib.PositionalArgumentsFormatter(),
        structlog.processors.format_exc_info,
        redact_sensitive,
        structlog.processors.StackInfoRenderer(),
        structlog.processors.UnicodeDecoder(),
    ]

    # Add chain_id to all log entries if provided
    if chain_id is not None:

        def add_chain_id(
            logger: logging.Logger, method_name: str, event_dict: dict[str, Any]
        ) -> dict[str, Any]:
            event_dict.setdefault("chain_id", chain_id)
            return event_dict

        processors.insert(3, add_chain_id)

    # Choose renderer based on mode
    if mode == "startup":
        # Human-friendly with colors for startup warnings/errors
        processors.append(structlog.dev.ConsoleRenderer(colors=True))
    elif log_format == LogFormat.JSON:
        processors.append(structlog.processors.JSONRenderer())
    else:
        processors.append(structlog.dev.ConsoleRenderer(colors=True))

    structlog.configure(
        processors=processors,
        wrapper_class=structlog.stdlib.BoundLogger,
        context_class=dict,
        logger_factory=structlog.stdlib.LoggerFactory(),
        cache_logger_on_first_use=False,  # Allow reconfiguration
    )


def set_runtime_logging() -> None:
    """Switch from startup mode to runtime mode.

    Call after "--- Starting brawny ---" to enable full structured logging.
    """
    setup_logging(_runtime_log_level, _runtime_log_format, _runtime_chain_id, mode="runtime")


def get_logger(name: str | None = None, **initial_context: Any) -> structlog.stdlib.BoundLogger:
    """Get a structured logger instance.

    Args:
        name: Logger name (usually module name)
        **initial_context: Initial context to bind to the logger

    Returns:
        Bound logger instance
    """
    logger = structlog.get_logger(name)
    if initial_context:
        logger = logger.bind(**initial_context)
    return logger


class LogContext:
    """Context manager for scoped logging context."""

    def __init__(self, **context: Any) -> None:
        self._context = context
        self._token: Any = None

    def __enter__(self) -> LogContext:
        # Generate correlation ID if not present
        if "correlation_id" not in self._context:
            cid = generate_correlation_id()
            self._context["correlation_id"] = cid
            set_correlation_id(cid)
        return self

    def __exit__(self, *args: Any) -> None:
        pass

    @property
    def correlation_id(self) -> str:
        """Get the correlation ID for this context."""
        return self._context.get("correlation_id", get_correlation_id())


# Pre-defined log event names per SPEC §11
class LogEvents:
    """Standard log event names."""

    # Block processing
    BLOCK_INGEST_START = "block.ingest.start"
    BLOCK_INGEST_DONE = "block.ingest.done"
    BLOCK_REORG_DETECTED = "block.reorg.detected"
    BLOCK_REORG_REWIND = "block.reorg.rewind"
    BLOCK_REORG_DEEP = "block.reorg.deep"

    # Job execution
    JOB_CHECK_START = "job.check.start"
    JOB_CHECK_SKIP = "job.check.skip"
    JOB_CHECK_TRIGGERED = "job.check.triggered"
    JOB_CHECK_TIMEOUT = "job.check.timeout"

    # Intent lifecycle
    INTENT_CREATE = "intent.create"
    INTENT_DEDUPE = "intent.dedupe"
    INTENT_CLAIM = "intent.claim"
    INTENT_STATUS = "intent.status"
    INTENT_REORG = "intent.reorg"

    # Nonce management
    NONCE_RESERVE = "nonce.reserve"
    NONCE_RECONCILE = "nonce.reconcile"
    NONCE_ORPHANED = "nonce.orphaned"

    # Transaction lifecycle
    TX_SIGN = "tx.sign"
    TX_BROADCAST = "tx.broadcast"
    TX_PENDING = "tx.pending"
    TX_CONFIRMED = "tx.confirmed"
    TX_FAILED = "tx.failed"
    TX_REPLACED = "tx.replaced"
    TX_ABANDONED = "tx.abandoned"

    # Alerts
    ALERT_SEND = "alert.send"
    ALERT_ERROR = "alert.error"

    # RPC
    RPC_REQUEST = "rpc.request"
    RPC_ERROR = "rpc.error"
    RPC_ALL_ENDPOINTS_FAILED = "rpc.all_endpoints_failed"
    RPC_CIRCUIT_BREAKER_OPEN = "rpc.circuit_breaker_open"
    RPC_CIRCUIT_BREAKER_CLOSED = "rpc.circuit_breaker_closed"

    # Shutdown
    SHUTDOWN_INITIATED = "shutdown.initiated"
    SHUTDOWN_COMPLETE = "shutdown.complete"
