"""Global structured logging configuration for dotpromptz.

Provides a small structured logger wrapper on top of stdlib logging,
with colored stderr output and file-based log persistence.
Log directory is resolved from explicit argument, DOTPROMPTZ_LOG_DIR
env var, or system temp directory.
"""

import logging
import os
import sys
import tempfile
from datetime import datetime
from pathlib import Path
from typing import Any

_STANDARD_LOG_RECORD_KEYS = frozenset(logging.makeLogRecord({}).__dict__.keys())

_RESET = '\x1b[0m'
_LEVEL_COLORS: dict[int, str] = {
    logging.DEBUG: '\x1b[36m',
    logging.INFO: '\x1b[32m',
    logging.WARNING: '\x1b[33m',
    logging.ERROR: '\x1b[31m',
    logging.CRITICAL: '\x1b[35m',
}
_DEBUG_VISIBLE_THIRD_PARTY_LOGGERS = frozenset(
    {
        'google_genai.models',
        'httpx',
    }
)


class StructuredLogger:
    """A lightweight structured logger API compatible with current call sites."""

    def __init__(self, logger: logging.Logger) -> None:
        """Initialize with the underlying stdlib logger."""
        self._logger = logger

    def _emit(
        self,
        level: int,
        event: str,
        fields: dict[str, Any],
        *,
        exc_info: bool = False,
    ) -> None:
        """Emit a structured log record."""
        extra = {'_dotpromptz_fields': fields} if fields else None
        kwargs: dict[str, Any] = {'stacklevel': 3}
        if extra is not None:
            kwargs['extra'] = extra
        if exc_info:
            kwargs['exc_info'] = True
        self._logger.log(level, event, **kwargs)

    def debug(self, event: str, **fields: Any) -> None:
        """Log at DEBUG level."""
        self._emit(logging.DEBUG, event, fields)

    def info(self, event: str, **fields: Any) -> None:
        """Log at INFO level."""
        self._emit(logging.INFO, event, fields)

    def warning(self, event: str, **fields: Any) -> None:
        """Log at WARNING level."""
        self._emit(logging.WARNING, event, fields)

    def error(self, event: str, **fields: Any) -> None:
        """Log at ERROR level."""
        self._emit(logging.ERROR, event, fields)

    def critical(self, event: str, **fields: Any) -> None:
        """Log at CRITICAL level."""
        self._emit(logging.CRITICAL, event, fields)

    def exception(self, event: str, **fields: Any) -> None:
        """Log at ERROR level with current exception traceback."""
        self._emit(logging.ERROR, event, fields, exc_info=True)


class _StructuredFormatter(logging.Formatter):
    """Formatter that renders event + structured key/value fields."""

    def __init__(self, *, colors: bool) -> None:
        """Initialize formatter options."""
        super().__init__(datefmt='%Y-%m-%d %H:%M:%S')
        self._colors = colors

    @staticmethod
    def _extract_fields(record: logging.LogRecord) -> dict[str, Any]:
        """Extract structured fields from the record."""
        fields = getattr(record, '_dotpromptz_fields', None)
        if isinstance(fields, dict):
            return dict(fields)

        result: dict[str, Any] = {}
        for key, value in record.__dict__.items():
            if key.startswith('_') or key in _STANDARD_LOG_RECORD_KEYS:
                continue
            result[key] = value
        return result

    @staticmethod
    def _format_field_value(value: Any) -> str:
        """Render one field value into a concise string."""
        return repr(value)

    def format(self, record: logging.LogRecord) -> str:
        """Format a log record."""
        timestamp = datetime.fromtimestamp(record.created).strftime('%Y-%m-%d %H:%M:%S')
        level = f'{record.levelname.lower():8}'
        event = record.getMessage()

        fields = self._extract_fields(record)
        if fields:
            rendered_fields = ' '.join(
                f'{key}={self._format_field_value(value)}' for key, value in sorted(fields.items())
            )
            event = f'{event} {rendered_fields}'

        rendered = f'{timestamp} [{level}] {event}'
        if self._colors:
            color = _LEVEL_COLORS.get(record.levelno)
            if color:
                rendered = f'{color}{rendered}{_RESET}'

        if record.exc_info:
            rendered = f'{rendered}\n{self.formatException(record.exc_info)}'
        elif record.stack_info:
            rendered = f'{rendered}\n{self.formatStack(record.stack_info)}'

        return rendered


def get_logger(name: str | None = None) -> StructuredLogger:
    """Return a structured logger wrapper."""
    return StructuredLogger(logging.getLogger(name))


def _remove_and_close_handlers(logger: logging.Logger) -> None:
    """Detach and close all handlers currently attached to *logger*.

    Args:
        logger: Logger instance whose handlers should be cleared.
    """
    for handler in list(logger.handlers):
        logger.removeHandler(handler)
        handler.close()


def _configure_third_party_loggers(*, effective_level: int) -> None:
    """Apply log-level overrides for noisy third-party libraries."""
    third_party_level = logging.INFO if effective_level <= logging.DEBUG else logging.WARNING
    for logger_name in _DEBUG_VISIBLE_THIRD_PARTY_LOGGERS:
        level = third_party_level
        logging.getLogger(logger_name).setLevel(level)


def _resolve_log_dir(log_dir: Path | str | None = None) -> Path:
    """Determine the directory where log and output files are written.

    Args:
        log_dir: Explicit override.  When ``None``, falls back to the
            ``DOTPROMPTZ_LOG_DIR`` env-var then to a platform temp dir.

    Returns:
        Resolved ``Path`` (directory is created if it doesn't exist).
    """
    if log_dir is not None:
        resolved = Path(log_dir)
    else:
        env = os.environ.get('DOTPROMPTZ_LOG_DIR')
        resolved = Path(env) if env else Path(tempfile.gettempdir()) / 'dotpromptz'

    resolved.mkdir(parents=True, exist_ok=True)
    return resolved


def _generate_log_filename() -> str:
    """Build a unique log-file name using timestamp + PID.

    Returns:
        Filename string like ``dotpromptz_20260301_143022_12345.log``.
    """
    ts = datetime.now().strftime('%Y%m%d_%H%M%S')
    return f'dotpromptz_{ts}_{os.getpid()}.log'


def generate_build_output_filename(prompt_stem: str) -> str:
    """Build a unique build-output file name.

    Args:
        prompt_stem: The ``.prompt`` file stem (e.g. ``"explain"``).

    Returns:
        Filename string like ``build_explain_20260301_143022_12345.yaml``.
    """
    ts = datetime.now().strftime('%Y%m%d_%H%M%S')
    return f'build_{prompt_stem}_{ts}_{os.getpid()}.yaml'


def configure_logging(
    *,
    level: int | str = logging.INFO,
    log_dir: Path | str | None = None,
) -> Path:
    """Set up stdlib logging for the entire process.

    This function is idempotent - calling it multiple times simply
    reconfigures the root logger.

    Args:
        level: Logging level — accepts a stdlib level name (e.g.
            ``"DEBUG"``, ``"WARNING"``) or an ``int`` constant from
            the :mod:`logging` module.  Default ``logging.INFO``.
        log_dir: Explicit log directory override (see module docstring
            for resolution order).

    Returns:
        The resolved log directory ``Path``.
    """
    resolved_dir = _resolve_log_dir(log_dir)
    effective_level = logging._checkLevel(level)  # noqa: SLF001 - normalize str to int

    # stdlib root logger setup
    root = logging.getLogger()
    # Remove any pre-existing handlers to ensure idempotency.
    _remove_and_close_handlers(root)
    root.setLevel(effective_level)

    # Console to stderr (with colors).
    console_formatter = _StructuredFormatter(colors=True)
    console_handler = logging.StreamHandler(sys.stderr)
    console_handler.setFormatter(console_formatter)
    root.addHandler(console_handler)

    # File (no colors).
    log_file = resolved_dir / _generate_log_filename()
    file_formatter = _StructuredFormatter(colors=False)
    file_handler = logging.FileHandler(str(log_file), encoding='utf-8')
    file_handler.setFormatter(file_formatter)
    root.addHandler(file_handler)

    _configure_third_party_loggers(effective_level=effective_level)

    return resolved_dir
