import logging
import sys
import time as _time
from datetime import datetime
from pathlib import Path
from typing import Optional
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler

from .config import LOG_COLORS, process_log_colors
from .levels import register_custom_levels
from .loop import loop_status


class _MonthlyRotatingFileHandler(TimedRotatingFileHandler):
    """Rotates the log file at the start of each calendar month."""

    def __init__(self, filename, backupCount=0, encoding=None):
        super().__init__(filename, when="midnight", interval=1,
                         backupCount=backupCount, encoding=encoding)
        self.rolloverAt = self._next_month_start()

    def _next_month_start(self) -> float:
        now = datetime.now()
        year = now.year + 1 if now.month == 12 else now.year
        month = 1 if now.month == 12 else now.month + 1
        return _time.mktime(datetime(year, month, 1).timetuple())

    def doRollover(self):
        super().doRollover()
        self.rolloverAt = self._next_month_start()

_VALID_FILE_ROTATIONS = {"daily", "weekly", "monthly", "custom"}

# Bind loop_status method to Logger
logging.Logger.loop_status = loop_status
register_custom_levels()


def get_logger(
    name: str,
    level: int = logging.DEBUG,
    log_to_file: bool = False,
    log_file_path: Optional[str] = None,
    use_colors: bool = True,
    overwrite_handlers: bool = False,
    show_seconds: bool = True,
    custom_colors: Optional[dict] = None,
    file_rotation: Optional[str] = None,
    file_retention: int = 30,
    max_file_size: int = 0,
    custom_retention: int = 1,
) -> logging.Logger:
    """
    Creates a logger with color and file output support.

    Args:
        name: Logger name (usually script/module name).
        level: Logging level (e.g., logging.INFO).
        log_to_file: If True, writes logs to a file.
        log_file_path: Path to the .log file. Required when log_to_file=True.
        use_colors: Enables ANSI color output (terminal only).
        overwrite_handlers: If True, clears existing handlers.
        show_seconds: Include seconds in the log timestamp.
        custom_colors: Override log level colors. Accepts named colors
            (e.g. 'cyan', 'bold_red') or HEX codes (e.g. '#FF5733').
            Only the specified levels are overridden; others use defaults.
        file_rotation: Time-based rotation strategy.
            'daily'   — rotates every 24 h.
            'weekly'  — rotates every 7 days from creation.
            'monthly' — rotates at the start of each calendar month.
            'custom'  — rotates every custom_retention days.
            None      — no time rotation (default).
        file_retention: How many days of rotated files to keep.
            Converted to backupCount automatically based on file_rotation.
            For max_file_size rotation, used as the number of backup files.
            Default: 30.
        max_file_size: Rotate by size when the file exceeds this value in bytes.
            Only active when file_rotation is None. 0 = disabled (default).
        custom_retention: Rotation interval in days when file_rotation='custom'.
            Default: 1.
    """
    if file_rotation is not None and file_rotation not in _VALID_FILE_ROTATIONS:
        raise ValueError(
            f"file_rotation must be one of {sorted(_VALID_FILE_ROTATIONS)} or None, "
            f"got '{file_rotation}'"
        )
    if file_rotation == "custom" and custom_retention < 1:
        raise ValueError(
            f"custom_retention must be >= 1, got {custom_retention}"
        )

    logger = logging.getLogger(name)
    logger.setLevel(level)

    merged_colors = {**LOG_COLORS, **(custom_colors or {})}
    logger._custom_colors = merged_colors

    if overwrite_handlers:
        logger.handlers.clear()

    if not logger.handlers:
        time_format = "%H:%M:%S" if show_seconds else "%H:%M"
        plain_formatter = logging.Formatter(
            fmt=f"[%(asctime)s] [{name}] [%(levelname)s] : %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S",
        )

        if use_colors:
            try:
                import colorlog

                formatter = colorlog.ColoredFormatter(
                    fmt=f"%(log_color)s[%(asctime)s] [{name}] [%(levelname)s] : %(message)s",
                    datefmt=time_format,
                    log_colors=process_log_colors(merged_colors),
                )
            except ImportError:
                formatter = plain_formatter
        else:
            formatter = plain_formatter

        console_handler = logging.StreamHandler(sys.stdout)
        console_handler.setFormatter(formatter)
        logger.addHandler(console_handler)

        if log_to_file and log_file_path:
            log_path = Path(log_file_path)
            log_path.parent.mkdir(parents=True, exist_ok=True)

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

            if file_rotation == "daily":
                file_handler = TimedRotatingFileHandler(
                    log_path,
                    when="midnight",
                    interval=1,
                    backupCount=file_retention,
                    encoding="utf-8",
                )
            elif file_rotation == "weekly":
                file_handler = TimedRotatingFileHandler(
                    log_path,
                    when="D",
                    interval=7,
                    backupCount=max(1, file_retention // 7),
                    encoding="utf-8",
                )
            elif file_rotation == "monthly":
                file_handler = _MonthlyRotatingFileHandler(
                    log_path,
                    backupCount=max(1, file_retention // 30),
                    encoding="utf-8",
                )
            elif file_rotation == "custom":
                file_handler = TimedRotatingFileHandler(
                    log_path,
                    when="D",
                    interval=custom_retention,
                    backupCount=max(1, file_retention // custom_retention),
                    encoding="utf-8",
                )
            elif max_file_size > 0:
                file_handler = RotatingFileHandler(
                    log_path,
                    maxBytes=max_file_size,
                    backupCount=file_retention,
                    encoding="utf-8",
                )
            else:
                file_handler = logging.FileHandler(log_path, encoding="utf-8")

            file_handler.setFormatter(file_formatter)
            logger.addHandler(file_handler)

    logger.propagate = False
    return logger
