"""Structured logging configuration for AiCippy.

Provides correlation ID tracking, JSON and console output formats,
and secure handling to prevent secret leakage.
"""

from __future__ import annotations

import logging
import sys
from typing import TYPE_CHECKING, Any

import structlog

if TYPE_CHECKING:
    from structlog.types import EventDict, Processor

    from aicippy.config import Settings

# Import the canonical correlation ID functions from correlation module
# to ensure a SINGLE ContextVar is used throughout the codebase.
from aicippy.utils.correlation import (
    get_correlation_id as _get_canonical_correlation_id,
)
from aicippy.utils.correlation import (
    set_correlation_id as _set_canonical_correlation_id,
)


def get_correlation_id() -> str | None:
    """Get current correlation ID from context.

    Delegates to the canonical ContextVar in ``aicippy.utils.correlation``
    so that correlation IDs set anywhere in the codebase are visible in
    log output.
    """
    value = _get_canonical_correlation_id()
    return value if value else None


def set_correlation_id(correlation_id: str) -> None:
    """Set correlation ID in context.

    Delegates to the canonical ContextVar in ``aicippy.utils.correlation``.
    """
    _set_canonical_correlation_id(correlation_id)


def add_correlation_id(
    _logger: logging.Logger,
    _method_name: str,
    event_dict: EventDict,
) -> EventDict:
    """Add correlation ID to log event if available."""
    correlation_id = get_correlation_id()
    if correlation_id:
        event_dict["correlation_id"] = correlation_id
    return event_dict


def add_service_info(
    _logger: logging.Logger,
    _method_name: str,
    event_dict: EventDict,
) -> EventDict:
    """Add service information to log event."""
    event_dict["service"] = "aicippy"
    return event_dict


# Patterns that indicate sensitive data - NEVER log these
SENSITIVE_PATTERNS = frozenset(
    {
        "password",
        "passwd",
        "secret",
        "token",
        "api_key",
        "apikey",
        "api-key",
        "authorization",
        "bearer",
        "credential",
        "private_key",
        "privatekey",
        "access_key",
        "accesskey",
        "session_id",
        "sessionid",
        "jwt",
        "cookie",
        "refresh_token",
        "code_verifier",
        "pkce",
        "otp_code",
        "session_token",
        "id_token",
        "access_token",
    },
)


def mask_sensitive_data(
    _logger: logging.Logger,
    _method_name: str,
    event_dict: EventDict,
) -> EventDict:
    """Mask sensitive data in log events.

    Replaces values for keys matching sensitive patterns with '[REDACTED]'.
    This is a critical security measure to prevent credential leakage.
    """

    def _mask_dict(d: dict[str, Any]) -> dict[str, Any]:
        masked: dict[str, Any] = {}
        for key, value in d.items():
            key_lower = key.lower()
            is_sensitive = any(pattern in key_lower for pattern in SENSITIVE_PATTERNS)

            if is_sensitive:
                masked[key] = "[REDACTED]"
            elif isinstance(value, dict):
                masked[key] = _mask_dict(value)
            elif isinstance(value, list):
                masked[key] = [_mask_dict(item) if isinstance(item, dict) else item for item in value]
            else:
                masked[key] = value
        return masked

    return _mask_dict(dict(event_dict))


def _suppress_default_logging() -> None:
    """Suppress all logging output until setup_logging() is called.

    This prevents structlog and stdlib logging from printing to stderr
    during module imports, before the application configures logging.
    """
    root = logging.getLogger()
    root.setLevel(logging.CRITICAL)
    root.addHandler(logging.NullHandler())
    
    structlog.configure(
        processors=[
            structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
        ],
        logger_factory=structlog.stdlib.LoggerFactory(),
        cache_logger_on_first_use=False,
    )

# Suppress logging at import time - setup_logging() will reconfigure
_suppress_default_logging()


def setup_logging(settings: Settings) -> None:
    """Configure structured logging for the application.

    Args:
        settings: Application settings containing log configuration.

    """
    logger = structlog.get_logger(__name__)
    # Determine processors based on output format
    shared_processors: list[Processor] = [
        structlog.contextvars.merge_contextvars,
        structlog.processors.add_log_level,
        structlog.processors.TimeStamper(fmt="iso"),
        add_correlation_id,
        add_service_info,
        mask_sensitive_data,
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
        structlog.processors.UnicodeDecoder(),
    ]

    if settings.log_format == "json":
        # JSON format for production/cloud logging
        renderer: Processor = structlog.processors.JSONRenderer()
    else:
        # Console format for local development
        renderer = structlog.dev.ConsoleRenderer(
            colors=True,
            exception_formatter=structlog.dev.plain_traceback,
        )

    structlog.configure(
        processors=[
            *shared_processors,
            structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
        ],
        wrapper_class=structlog.stdlib.BoundLogger,
        context_class=dict,
        logger_factory=structlog.stdlib.LoggerFactory(),
        cache_logger_on_first_use=True,
    )

    root_logger = logging.getLogger()
    root_logger.handlers.clear()
    
    # Always add a NullHandler to prevent 'lastResort' from printing to stderr
    root_logger.addHandler(logging.NullHandler())

    # Add a global debug log file in ~/.aicippy/debug.log
    try:
        from pathlib import Path
        global_log_dir = Path.home() / ".aicippy"
        global_log_dir.mkdir(parents=True, exist_ok=True)
        global_log_file = global_log_dir / "debug.log"
        
        global_handler = logging.FileHandler(str(global_log_file), mode="a", encoding="utf-8")
        json_renderer = structlog.processors.JSONRenderer()
        global_handler.setFormatter(
            structlog.stdlib.ProcessorFormatter(
                processor=json_renderer,
                foreign_pre_chain=shared_processors,
            ),
        )
        root_logger.addHandler(global_handler)
    except Exception:
        pass

    # Add project-specific file logging if temp_dir is available
    try:
        project_temp = settings.get_project_temp_dir()
        log_file = project_temp / "agent.log"
        file_handler = logging.FileHandler(str(log_file), mode="a", encoding="utf-8")
        # Use JSON format for file logs for better indexing/analysis
        json_renderer = structlog.processors.JSONRenderer()
        file_handler.setFormatter(
            structlog.stdlib.ProcessorFormatter(
                processor=json_renderer,
                foreign_pre_chain=shared_processors,
            ),
        )
        root_logger.addHandler(file_handler)
        logger.info("file_logging_initialized", path=str(log_file))
    except Exception as e:
        # Fallback if file logging fails (e.g. read-only FS)
        pass # Silently fail file logging so it doesn't break the CLI

    log_level_int = getattr(logging, settings.log_level, None)
    if not isinstance(log_level_int, int):
        log_level_int = logging.ERROR
    root_logger.setLevel(log_level_int)

    # Reduce noise from third-party libraries
    for logger_name in ("boto3", "botocore", "urllib3", "httpx", "httpcore"):
        logging.getLogger(logger_name).setLevel(logging.ERROR)


def get_logger(name: str) -> structlog.stdlib.BoundLogger:
    """Get a structured logger for the given name.

    Args:
        name: Logger name, typically __name__ of the calling module.

    Returns:
        A bound structured logger instance.

    """
    return structlog.get_logger(name)
