"""
Core logging infrastructure with global context management and fail-safe operation.
"""

import logging
import logging.handlers
import sys
import threading
import time
import traceback
from datetime import datetime
from functools import wraps
from pathlib import Path
from typing import Any, Dict, Optional

from .context import LogContext


class FailSafeLogger:
    """
    Fail-safe logger that never crashes main operations due to logging failures.

    Implements fallback chain: File logging → stderr → silent operation
    """

    def __init__(self, name: str, context: LogContext):
        self.name = name
        self.context = context
        self.failures = []
        self._loggers = {}
        self._setup_loggers()

    def _setup_loggers(self):
        """Setup file-based loggers for different log levels."""
        try:
            # Ensure log directories exist
            self.context.ensure_directories()

            # Create core loggers
            self._create_core_loggers()

            # Add optional org logger if available
            self._add_org_logger_if_available()

            # Create symlink for easy access to latest run
            self._create_latest_symlink_if_needed()

        except Exception as e:
            self._track_failure(e)

    def _create_file_logger(self, level: str, log_file: Path) -> logging.Logger:
        """Create a file logger with rotation."""
        logger = logging.getLogger(f"{self.name}.{level}")
        logger.setLevel(logging.DEBUG)

        # Remove existing handlers to avoid duplicates
        for handler in logger.handlers[:]:
            logger.removeHandler(handler)

        # Create rotating file handler
        handler = logging.handlers.RotatingFileHandler(
            log_file,
            maxBytes=100 * 1024 * 1024,  # 100MB
            backupCount=5,
        )

        # Set formatter with context information
        formatter = logging.Formatter(
            "%(asctime)s.%(msecs)03d [%(levelname)s] "
            f"[{self.context.org or 'system'}] "
            f"[{self.context.run_id or 'global'}] "
            f"[{self.context.stage or 'init'}] "
            f"[{self.context.component or 'unknown'}] "
            "%(message)s",
            datefmt="%Y-%m-%d %H:%M:%S",
        )
        handler.setFormatter(formatter)

        logger.addHandler(handler)
        logger.propagate = False

        return logger

    def _create_latest_symlink(self):
        """Create symlink to latest run for easy access."""
        try:
            latest_link = self.context.base_dir / "latest"
            run_dir = self.context.run_dir

            # Remove existing symlink
            if latest_link.is_symlink():
                latest_link.unlink()

            # Create new symlink
            latest_link.symlink_to(run_dir.relative_to(self.context.base_dir))

        except Exception as e:
            self._track_failure(e)

    def _track_failure(self, error: Exception):
        """Track logging failures for end-of-run reporting."""
        self.failures.append(
            {
                "timestamp": datetime.now().isoformat(),
                "error": str(error),
                "traceback": traceback.format_exc(),
            }
        )

    def _log(self, level: str, message: str, **context):
        """Internal logging with fail-safe fallback."""
        try:
            # Process and distribute log message
            self._process_and_distribute_log_message(level, message, context)

        except OSError as e:
            # Handle logging failures gracefully
            self._handle_logging_failure(e, level, message, context)

    def _get_target_loggers(self, level: str) -> list:
        """Determine which log files should receive this message."""
        base_loggers = []

        if level in ["info"]:
            base_loggers = ["user", "debug", "trace"]
        elif level in ["debug"]:
            base_loggers = ["debug", "trace"]
        elif level in ["warning", "error"]:
            base_loggers = ["user", "debug", "trace", "errors"]
        elif level in ["perf"]:
            base_loggers = ["performance", "trace"]
        elif level in ["trace"]:
            base_loggers = ["trace"]
        else:
            base_loggers = ["debug", "trace"]

        # Add org logger for org-wide events (info, warnings, errors)
        if self.context.org and level in ["info", "warning", "error"]:
            base_loggers.append("org")

        return base_loggers

    def _format_message(self, message: str, context: Dict[str, Any]) -> str:
        """Format message with context data."""
        if not context:
            return message

        # Add provider/month context if available
        context_parts = []
        if "provider" in context:
            context_parts.append(f"provider={context['provider']}")
        if "month" in context:
            context_parts.append(f"month={context['month']}")
        if "job_id" in context:
            context_parts.append(f"job={context['job_id']}")

        # Add other context as key=value pairs
        for key, value in context.items():
            if key not in ["provider", "month", "job_id"]:
                if isinstance(value, (int, float)):
                    context_parts.append(f"{key}={value}")
                else:
                    context_parts.append(f"{key}={str(value)}")

        if context_parts:
            return f"{message} ({', '.join(context_parts)})"
        return message

    def _write_to_stderr(self, level: str, message: str, context: Dict[str, Any]):
        """Fallback logging to stderr."""
        timestamp = datetime.now().strftime("%H:%M:%S")
        formatted_message = self._format_message(message, context)

        level_prefix = {
            "info": "📋",
            "debug": "🔍",
            "warning": "⚠️",
            "error": "❌",
            "perf": "⏱️",
            "trace": "🔬",
        }.get(level, "📋")

        sys.stderr.write(f"{timestamp} {level_prefix} {formatted_message}\n")
        sys.stderr.flush()

    def info(self, message: str, **context):
        """Log business events and progress."""
        self._log("info", message, **context)

    def debug(self, message: str, **context):
        """Log technical details and calculations."""
        self._log("debug", message, **context)

    def warning(self, message: str, **context):
        """Log warnings that don't stop execution."""
        self._log("warning", message, **context)

    def error(self, message: str, **context):
        """Log errors and failures."""
        self._log("error", message, **context)

    def perf(self, message: str, **context):
        """Log performance metrics and timing."""
        self._log("perf", message, **context)

    def trace(self, message: str, **context):
        """Log verbose debugging information."""
        self._log("trace", message, **context)

    def provider_month(self, action: str, provider: str, month: str, **context):
        """Specialized logging for provider month tracking."""
        if action == "start":
            self.info(
                "Provider month started", provider=provider, month=month, **context
            )
        elif action == "complete":
            self.perf(
                "Provider month completed", provider=provider, month=month, **context
            )
        else:
            self.debug(
                f"Provider month {action}", provider=provider, month=month, **context
            )

    def timer(self, operation: str):
        """Context manager for timing operations."""
        return TimingContext(self, operation)

    def get_failure_summary(self) -> Optional[dict]:
        """Get summary of any logging failures for end-of-run reporting."""
        if not self.failures:
            return None

        return {
            "failure_count": len(self.failures),
            "first_failure": self.failures[0],
            "last_failure": self.failures[-1],
        }

    # Helper functions for _setup_loggers
    def _create_core_loggers(self):
        """Create core loggers for different log levels."""
        self._loggers = {
            "user": self._create_file_logger("user", self.context.user_log),
            "debug": self._create_file_logger("debug", self.context.debug_log),
            "performance": self._create_file_logger(
                "performance", self.context.performance_log
            ),
            "trace": self._create_file_logger("trace", self.context.trace_log),
            "system": self._create_file_logger("system", self.context.system_log),
            "errors": self._create_file_logger("errors", self.context.errors_log),
        }

    def _add_org_logger_if_available(self):
        """Add org logger if org context is available."""
        if self.context.org_log:
            self._loggers["org"] = self._create_file_logger("org", self.context.org_log)

    def _create_latest_symlink_if_needed(self):
        """Create latest symlink if this is an org-specific run."""
        if self.context.org and self.context.run_id:
            self._create_latest_symlink()

    # Helper functions for _log
    def _process_and_distribute_log_message(
        self, level: str, message: str, context: dict
    ):
        """Process and distribute log message to appropriate loggers."""
        # Determine target loggers based on level
        target_loggers = self._get_target_loggers(level)

        # Format message with context
        formatted_message = self._format_message(message, context)

        # Write to appropriate loggers
        self._write_to_target_loggers(level, formatted_message, target_loggers)

    def _write_to_target_loggers(
        self, level: str, formatted_message: str, target_loggers: list
    ):
        """Write formatted message to target loggers."""
        for logger_name in target_loggers:
            if logger_name in self._loggers:
                logger = self._loggers[logger_name]

                # Map our levels to Python logging levels
                log_level = self._map_to_python_log_level(level)
                logger.log(log_level, formatted_message)

    def _map_to_python_log_level(self, level: str) -> int:
        """Map custom log levels to Python logging levels."""
        level_mapping = {
            "info": logging.INFO,
            "debug": logging.DEBUG,
            "warning": logging.WARNING,
            "error": logging.ERROR,
            "perf": logging.INFO,
            "trace": logging.DEBUG,
        }
        return level_mapping.get(level, logging.INFO)

    def _handle_logging_failure(
        self, error: Exception, level: str, message: str, context: dict
    ):
        """Handle logging failures with fallback options."""
        try:
            # Fallback: Write to stderr
            self._write_to_stderr(level, message, context)
            self._track_failure(error)
        except Exception:
            # Last resort: Silent operation with failure tracking
            self._track_failure(error)


class TimingContext:
    """Context manager for automatic timing and logging."""

    def __init__(self, logger: FailSafeLogger, operation: str):
        self.logger = logger
        self.operation = operation
        self.start_time = None
        self.context = {}

    def __enter__(self):
        self.start_time = time.time()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.start_time:
            duration_ms = (time.time() - self.start_time) * 1000
            self.logger.perf(
                f"{self.operation} completed", duration_ms=duration_ms, **self.context
            )

    def add_context(self, **context):
        """Add context to be logged with the timing result."""
        self.context.update(context)


# Global context management
_global_context: Optional[LogContext] = None
_context_lock = threading.Lock()

# Global logger registry
_loggers = {}


def set_pipeline_context(
    run_dir: str, org: Optional[str] = None, stage: Optional[str] = None
):
    """
    Set global logging context for entire pipeline execution.

    This should be called once at the start of pipeline execution to set
    the logging context that all modules will automatically use.

    Args:
        run_dir: Pipeline run directory path
        org: Organization name (extracted from run_dir if not provided)
        stage: Current stage name
    """
    global _global_context
    with _context_lock:
        _global_context = LogContext.from_run_dir(run_dir, stage)
        # Override org if explicitly provided
        if org:
            _global_context.org = org


def get_logger(name: str) -> FailSafeLogger:
    """
    Get or create a logger with automatic global context resolution.

    This is the new simplified interface - no parameters needed!
    The logger automatically uses the global context set by set_pipeline_context().

    Args:
        name: Logger name (typically __name__)

    Returns:
        FailSafeLogger instance with global context

    Usage:
        # Simple usage - automatically uses global context
        logger = get_logger(__name__)
        logger.info("Processing started")  # Automatically includes context
    """
    with _context_lock:
        context = _global_context or LogContext.detect_current_context()

    # Create unique key for this logger configuration
    key = f"{name}:{context.org}:{context.run_id}:{context.stage}"

    if key not in _loggers:
        _loggers[key] = FailSafeLogger(name, context)

    return _loggers[key]


def clear_pipeline_context():
    """
    Clear global context (useful for testing).
    """
    global _global_context
    with _context_lock:
        _global_context = None

    # Also clear logger cache to allow fresh context detection
    _loggers.clear()


# Legacy function for backward compatibility during migration
def get_logger_legacy(
    name: str,
    run_dir: Optional[str] = None,
    org: Optional[str] = None,
    run_id: Optional[str] = None,
    stage: Optional[str] = None,
) -> FailSafeLogger:
    """
    Legacy get_logger interface for backward compatibility during migration.

    This will be removed after all modules are updated to use global context.
    """
    # Use pipeline-driven context if run_dir is provided
    if run_dir:
        context = LogContext.from_run_dir(run_dir, stage)
    else:
        # Fallback to auto-detection
        context = LogContext.detect_current_context(org, run_id, stage)

    # Create unique key for this logger configuration
    key = f"{name}:{context.org}:{context.run_id}:{context.stage}"

    if key not in _loggers:
        _loggers[key] = FailSafeLogger(name, context)

    return _loggers[key]


def log_timing(func):
    """Decorator for automatic timing of functions."""

    @wraps(func)
    def wrapper(*args, **kwargs):
        # Use simplified interface - automatically uses global context
        logger = get_logger(func.__module__)

        with logger.timer(func.__name__) as timer:
            result = func(*args, **kwargs)

            # Add result context if it's a simple value
            if isinstance(result, (int, float, str)):
                timer.add_context(result=result)

            return result

    return wrapper
