"""Structured logging setup for Cerbero Bite.

Outputs JSONL to a daily file under ``data/log/`` and pretty-prints to
stderr in development mode. Every event line is the canonical record for
audit purposes; the audit chain (see ``safety/audit_log.py``) is built on
top of this stream for trade-relevant events.
"""

from __future__ import annotations

import logging
import sys
from datetime import UTC, datetime
from pathlib import Path
from typing import Any

import structlog
from structlog.stdlib import BoundLogger
from structlog.types import EventDict, Processor


def _utc_timestamp(_: object, __: str, event_dict: EventDict) -> EventDict:
    """Add ISO-8601 UTC timestamp with millisecond precision."""
    event_dict["ts"] = datetime.now(UTC).isoformat(timespec="milliseconds")
    return event_dict


def _daily_log_path(log_dir: Path) -> Path:
    today = datetime.now(UTC).date().isoformat()
    return log_dir / f"cerbero-bite-{today}.jsonl"


def configure(
    log_dir: Path | str = "data/log",
    *,
    level: str = "INFO",
    pretty_console: bool = True,
) -> None:
    """Configure structlog + stdlib logging.

    Args:
        log_dir: Directory where the daily JSONL log file is written.
            Created if missing.
        level: Minimum log level for both handlers.
        pretty_console: When True, also write a human-readable stream to
            stderr. Disable in production daemon mode if not desired.
    """
    log_dir_path = Path(log_dir)
    log_dir_path.mkdir(parents=True, exist_ok=True)

    log_file = _daily_log_path(log_dir_path)
    file_handler = logging.FileHandler(log_file, encoding="utf-8")
    file_handler.setLevel(level)

    handlers: list[logging.Handler] = [file_handler]
    if pretty_console:
        console_handler = logging.StreamHandler(sys.stderr)
        console_handler.setLevel(level)
        handlers.append(console_handler)

    logging.basicConfig(
        format="%(message)s",
        handlers=handlers,
        level=level,
        force=True,
    )

    shared_processors: list[Processor] = [
        structlog.contextvars.merge_contextvars,
        structlog.stdlib.add_log_level,
        structlog.stdlib.add_logger_name,
        _utc_timestamp,
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
        structlog.processors.UnicodeDecoder(),
    ]

    structlog.configure(
        processors=[
            *shared_processors,
            structlog.processors.JSONRenderer(),
        ],
        wrapper_class=structlog.stdlib.BoundLogger,
        logger_factory=structlog.stdlib.LoggerFactory(),
        cache_logger_on_first_use=True,
    )

    if pretty_console:
        # Replace stderr handler's formatter with a pretty renderer
        formatter = structlog.stdlib.ProcessorFormatter(
            processors=[
                structlog.stdlib.ProcessorFormatter.remove_processors_meta,
                structlog.dev.ConsoleRenderer(colors=True),
            ],
            foreign_pre_chain=shared_processors,
        )
        for handler in handlers:
            if isinstance(handler, logging.StreamHandler) and not isinstance(
                handler, logging.FileHandler
            ):
                handler.setFormatter(formatter)


def get_logger(name: str | None = None, **initial_context: Any) -> BoundLogger:
    """Return a bound structlog logger with optional initial context."""
    logger: BoundLogger = structlog.get_logger(name)
    if initial_context:
        logger = logger.bind(**initial_context)
    return logger
