# Copyright 2025-present DatusAI, Inc.
# Licensed under the Apache License, Version 2.0.
# See http://www.apache.org/licenses/LICENSE-2.0 for details.

import logging
import os
import sys
import threading
import traceback
from contextlib import contextmanager
from logging.handlers import TimedRotatingFileHandler
from typing import Literal

import structlog

fileno = False

# Global log manager
_log_manager = None


def _is_source_environment() -> bool:
    """Check if running from source code directory (development mode).

    Returns:
        True if running from source directory, False if packaged/installed
    """
    try:
        # Get the directory where this module is located
        current_dir = os.path.dirname(os.path.abspath(__file__))
        # Navigate up to project root (from datus/utils/ to project root)
        project_root = os.path.dirname(os.path.dirname(current_dir))

        # Check for source code markers: pyproject.toml and datus/ directory
        has_pyproject = os.path.exists(os.path.join(project_root, "pyproject.toml"))
        has_datus_dir = os.path.exists(os.path.join(project_root, "datus"))

        return has_pyproject and has_datus_dir
    except Exception:
        return False


class DynamicLogManager:
    """Dynamic log manager that supports switching log output targets at runtime"""

    def __init__(self, debug=False, log_dir=None):
        self.debug = debug
        # Auto-detect log directory if not specified
        if log_dir is None:
            if _is_source_environment():
                log_dir = "./logs"
            else:
                from datus.utils.path_manager import get_path_manager

                log_dir = str(get_path_manager().logs_dir)
        # Expand user directory and convert to absolute path
        self.log_dir = os.path.abspath(os.path.expanduser(log_dir))
        self.root_logger = logging.getLogger()
        self.file_handler = None
        self.console_handler = None
        self.original_handlers = []
        self._lock = threading.RLock()
        self._setup_handlers()

    def _setup_handlers(self):
        """Set up file and console handlers"""
        if not os.path.exists(self.log_dir):
            os.makedirs(self.log_dir)

        # Create file handler
        from datetime import datetime

        current_date = datetime.now().strftime("%Y-%m-%d")
        log_file_base = os.path.join(self.log_dir, f"agent.{current_date}")

        self.file_handler = TimedRotatingFileHandler(
            log_file_base + ".log", when="midnight", interval=1, backupCount=30, encoding="utf-8"
        )
        self.file_handler.suffix = "%Y-%m-%d"

        # Use a custom formatter for the file handler that removes color codes
        class PlainTextFormatter(logging.Formatter):
            def format(self, record):
                # Get the original message
                msg = super().format(record)
                # Remove ANSI color codes
                import re

                ansi_escape = re.compile(r"\x1b\[[0-9;]*m")
                return ansi_escape.sub("", msg)

        file_formatter = PlainTextFormatter("%(message)s")
        self.file_handler.setFormatter(file_formatter)

        # Create console handler with normal formatter
        self.console_handler = logging.StreamHandler(sys.stdout)
        console_formatter = logging.Formatter("%(message)s")
        self.console_handler.setFormatter(console_formatter)

        # Set up root logger
        self.root_logger.setLevel(logging.DEBUG if self.debug else logging.INFO)
        self.original_handlers = self.root_logger.handlers.copy()

    def set_output_target(self, target: Literal["both", "file", "console", "none"]):
        """Set log output target

        Args:
            target: Output target
                - "both": Output to both file and console (default)
                - "file": Output to file only
                - "console": Output to console only
                - "none": No output
        """
        with self._lock:
            self.root_logger.handlers = []

            if target in ["both", "file"]:
                self.root_logger.addHandler(self.file_handler)

            if target in ["both", "console"]:
                self.root_logger.addHandler(self.console_handler)

    def restore_default(self):
        """Restore to default configuration (file + console)"""
        with self._lock:
            self.set_output_target("both")

    def restore_original(self):
        """Restore to original handler configuration"""
        with self._lock:
            self.root_logger.handlers = self.original_handlers.copy()

    @contextmanager
    def temporary_output(self, target: Literal["both", "file", "console", "none"]):
        """Context manager for temporarily setting output target

        Args:
            target: Temporary output target
        """
        with self._lock:
            original_handlers = self.root_logger.handlers.copy()
            try:
                self.set_output_target(target)
                yield
            finally:
                self.root_logger.handlers = original_handlers


def get_log_manager() -> DynamicLogManager:
    """Get global log manager"""
    global _log_manager
    if _log_manager is None:
        _log_manager = DynamicLogManager()
    return _log_manager


def configure_logging(debug=False, log_dir=None, console_output=True) -> DynamicLogManager:
    """Configure logging with the specified debug level.
    Args:
        debug: If True, set log level to DEBUG
        log_dir: Directory for log files. If None, automatically determine:
                 - "./logs" for source code environment
                 - "~/.datus/logs" for packaged installation
        console_output: If False, disable logging to console
    """
    global fileno
    fileno = debug

    # Auto-detect log directory if not specified
    if log_dir is None:
        if _is_source_environment():
            log_dir = "./logs"
        else:
            from datus.utils.path_manager import get_path_manager

            log_dir = str(get_path_manager().logs_dir)

    # Create or get log manager with specified parameters
    global _log_manager
    _log_manager = DynamicLogManager(debug=debug, log_dir=log_dir)

    # Set output target based on console_output parameter
    if console_output:
        _log_manager.set_output_target("both")
    else:
        _log_manager.set_output_target("file")
    return _log_manager


def add_exc_info(logger, method_name, event_dict):
    """Add exception info to error logs."""
    if method_name == "error":
        event_dict["exc_info"] = True
    return event_dict


def add_code_location(logger, method_name, event_dict):
    """Add the correct code location by inspecting the call stack."""
    if method_name == "debug" or fileno:
        try:
            frames = traceback.extract_stack()
            # Find the first frame that is not in structlog or logging modules
            for frame in reversed(frames[:-1]):  # Exclude the current frame
                if "structlog" not in frame.filename and "logging" not in frame.filename:
                    event_dict["fileno"] = f" {frame.filename}:{frame.lineno}"
                    break
        except Exception as e:
            print(str(e))
    return event_dict


def get_logger(name: str) -> structlog.BoundLogger:
    return structlog.get_logger(name)


def setup_web_chatbot_logging(debug=False, log_dir=None):
    """Setup simplified logging for web chatbot using same format as agent.log

    Args:
        debug: Enable debug logging
        log_dir: Directory for log files. If None, automatically determine:
                 - "./logs" for source code environment
                 - "~/.datus/logs" for packaged installation

    Returns:
        structlog.BoundLogger: Configured logger for web chatbot
    """
    # Auto-detect log directory if not specified
    if log_dir is None:
        if _is_source_environment():
            log_dir = "./logs"
        else:
            from datus.utils.path_manager import get_path_manager

            log_dir = str(get_path_manager().logs_dir)

    # Expand user directory and convert to absolute path
    log_dir = os.path.abspath(os.path.expanduser(log_dir))
    if not os.path.exists(log_dir):
        os.makedirs(log_dir)

    # Create independent logger for web chatbot
    web_logger = logging.getLogger("web_chatbot")
    web_logger.setLevel(logging.DEBUG if debug else logging.INFO)

    # Remove existing handlers to avoid duplicates
    web_logger.handlers.clear()

    # Create file handler with same naming pattern as agent.log
    from datetime import datetime

    current_date = datetime.now().strftime("%Y-%m-%d")
    log_file_base = os.path.join(log_dir, f"web_chatbot.{current_date}")

    file_handler = TimedRotatingFileHandler(
        log_file_base + ".log", when="midnight", interval=1, backupCount=30, encoding="utf-8"
    )
    file_handler.suffix = "%Y-%m-%d"

    # Use same formatter as agent.log (simple message format)
    formatter = logging.Formatter("%(message)s")
    file_handler.setFormatter(formatter)

    web_logger.addHandler(file_handler)
    web_logger.propagate = False  # Prevent propagation to root logger

    return structlog.get_logger("web_chatbot")


@contextmanager
def log_context(target: Literal["both", "file", "console", "none"]):
    """Log output context manager

    Args:
        target: Output target

    Example:
        with log_context("console"):
            logger.info("This log will only output to console")
    """
    with get_log_manager().temporary_output(target):
        yield


class AdaptiveRenderer:
    """Adaptive renderer that uses colored output by default"""

    def __init__(self):
        self.colored_renderer = structlog.dev.ConsoleRenderer(
            colors=True, exception_formatter=structlog.dev.plain_traceback
        )

    def __call__(self, logger, name, event_dict):
        """Always use colored renderer - file handler will strip colors with its formatter"""
        return self.colored_renderer(logger, name, event_dict)


if not structlog.is_configured():
    # Initialize event dict to avoid NoneType errors
    structlog.configure_once(
        processors=[
            structlog.stdlib.filter_by_level,
            structlog.stdlib.add_log_level,
            structlog.stdlib.add_logger_name,
            add_code_location,
            add_exc_info,
            structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S"),
            structlog.processors.StackInfoRenderer(),
            structlog.processors.format_exc_info,
            structlog.processors.UnicodeDecoder(),
            AdaptiveRenderer(),
        ],
        context_class=dict,
        logger_factory=structlog.stdlib.LoggerFactory(),
        wrapper_class=structlog.stdlib.BoundLogger,
        cache_logger_on_first_use=True,
    )
