"""
logger_setup.py

Central logging configuration. Every module calls get_logger(__name__)
to get a consistently formatted logger that writes to both console and
a log file under logs/.

[FEATURE 2026-08-20] Log files are now per trading day instead of a
single ever-growing sip_bot.log that only rotated once it hit a size
cap. Each day gets its own file named logs/sip_bot_YYYY-MM-DD.log --
the date is part of the filename from the very first message written
that day, not something bolted on after the fact via a rotated-file
suffix. A fresh file is opened automatically the moment logging
crosses into a new day, even if the process happens to keep running
across the boundary (this bot normally runs one session per trading
day and exits at EOD liquidation, but the day-check-on-emit makes this
correct too for any longer-running or overnight dry-run use).

The day boundary is anchored to market_time's America/New_York clock
(the same timezone every schedule check in this project already uses),
not naive server-local time -- on a UTC-default droplet, "server
midnight" and "trading-day midnight" are different moments, and using
the wrong one could split a single session's log across two files.

Old daily files beyond logging.retention_days are pruned on startup,
so disk usage stays bounded without needing size-based rotation.
"""

import glob
import logging
import os
from datetime import date
from pathlib import Path

import market_time
from config_loader import get_config

_CONFIGURED = False


class _DailyFileHandler(logging.Handler):
    """
    Writes to logs/{prefix}_{YYYY-MM-DD}.log (America/New_York date),
    opening a new file the first time a record is emitted on a new
    trading day.

    Deliberately simpler than logging.handlers.TimedRotatingFileHandler:
    that class only embeds the date into ROTATED (past) filenames by
    default -- the currently-active file keeps the plain base name until
    it's rotated away. That doesn't match "today's log file is already
    named with today's date," and making TimedRotatingFileHandler do
    that requires overriding both its rollover timing and its active
    filename, which ends up more convoluted than just checking the date
    on each emit.
    """

    def __init__(self, log_dir: Path, prefix: str, encoding: str = "utf-8"):
        super().__init__()
        self._log_dir = log_dir
        self._prefix = prefix
        self._encoding = encoding
        self._current_date = None
        self._file_handler = None

    def _path_for(self, day: date) -> Path:
        return self._log_dir / f"{self._prefix}_{day.isoformat()}.log"

    def _ensure_current_file(self):
        today = market_time.now_et().date()
        if self._file_handler is not None and self._current_date == today:
            return
        if self._file_handler is not None:
            self._file_handler.close()
        self._current_date = today
        self._file_handler = logging.FileHandler(
            self._path_for(today), mode="a", encoding=self._encoding
        )
        self._file_handler.setFormatter(self.formatter)

    def setFormatter(self, fmt):
        super().setFormatter(fmt)
        if self._file_handler is not None:
            self._file_handler.setFormatter(fmt)

    def emit(self, record):
        try:
            self._ensure_current_file()  # cheap date check; rolls over exactly at the trading-day boundary
            self._file_handler.emit(record)
        except Exception:
            self.handleError(record)

    def close(self):
        if self._file_handler is not None:
            self._file_handler.close()
        super().close()


def _prune_old_logs(log_dir: Path, prefix: str, retention_days):
    """Deletes dated log files older than retention_days. Best-effort --
    a file that can't be parsed as one of ours, or can't be removed, is
    silently left alone rather than raising during logging setup."""
    if not retention_days or retention_days <= 0:
        return
    cutoff_ordinal = market_time.now_et().date().toordinal() - retention_days
    for path in glob.glob(str(log_dir / f"{prefix}_*.log")):
        stem = os.path.basename(path)[len(prefix) + 1:-len(".log")]
        try:
            file_date = date.fromisoformat(stem)
        except ValueError:
            continue
        if file_date.toordinal() < cutoff_ordinal:
            try:
                os.remove(path)
            except OSError:
                pass


def _configure_root():
    global _CONFIGURED
    if _CONFIGURED:
        return
    cfg = get_config()
    log_cfg = cfg.get("logging", {})
    log_dir = Path(__file__).resolve().parent / log_cfg.get("log_dir", "logs")
    log_dir.mkdir(parents=True, exist_ok=True)

    level = getattr(logging, log_cfg.get("level", "INFO").upper(), logging.INFO)
    prefix = log_cfg.get("filename_prefix", "sip_bot")

    root = logging.getLogger("sip_bot")
    root.setLevel(level)
    root.propagate = False

    fmt = logging.Formatter(
        "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )

    console = logging.StreamHandler()
    console.setFormatter(fmt)
    root.addHandler(console)

    file_handler = _DailyFileHandler(log_dir, prefix)
    file_handler.setFormatter(fmt)
    root.addHandler(file_handler)

    _prune_old_logs(log_dir, prefix, log_cfg.get("retention_days", 30))

    _CONFIGURED = True


def get_logger(name: str) -> logging.Logger:
    _configure_root()
    return logging.getLogger(f"sip_bot.{name}")