from datetime import datetime
import difflib
import os

class Logger:
    LEVELS = {
        "DEBUG": 10,
        "INFO": 20,
        "WARNING": 30,
        "ERROR": 40
    }

    COLORS = {
        "DEBUG": "\033[94m",
        "INFO": "\033[96m",
        "WARNING": "\033[93m",
        "ERROR": "\033[91m",
        "DATE": "\033[35m",
        "MESSAGE": "\033[96m",
        "RESET": "\033[0m",
    }

    def __init__(
        self,
        name: str = "root",
        min_level: str = "DEBUG",
        use_colors: bool = True,
        log_to_file: bool = False,
        log_dir: str = "logs",
        rotate_daily: bool = False,
        unique_dir: bool = False,
        console: bool = True
    ):
        self.name = name
        self.use_colors = use_colors
        self.log_to_file = log_to_file
        self.log_dir = log_dir
        self.rotate_daily = rotate_daily
        self.unique_dir = unique_dir
        self.console = console

        self._context = {}
        self._format = "[{time}] [{level}] ({name}) {msg}{context}"

        min_level = min_level.upper()
        if min_level not in self.LEVELS:
            raise ValueError(f"Invalid min_level: {min_level}")
        self.min_level_value = self.LEVELS[min_level]

        if self.log_to_file:
            if os.path.isdir(self.log_dir):
                if self.unique_dir:
                    base = self.log_dir
                    i = 1
                    folder_name = base
                    while os.path.exists(folder_name):
                        folder_name = f"{base}_{i}"
                        i += 1
                    os.makedirs(folder_name)
                    self.log_dir = folder_name
                else:
                    os.makedirs(self.log_dir, exist_ok=True)
            else:
                os.makedirs(self.log_dir)

            self._set_log_file()

    # ---------- file handling ----------
    def _set_log_file(self):
        name = datetime.now().strftime("%Y-%m-%d") if self.rotate_daily else "log"
        self.log_file_path = os.path.join(self.log_dir, f"{name}.log")

    def _write_file(self, text: str):
        with open(self.log_file_path, "a", encoding="utf-8") as f:
            f.write(text + "\n")

    def _format_context(self, ctx: dict):
        return ", ".join(f"{k}={v}" for k, v in ctx.items())

    def _check_rotate(self):
        if self.rotate_daily:
            today = datetime.now().strftime("%Y-%m-%d")
            if today not in self.log_file_path:
                self._set_log_file()

    # ---------- шаблон логирования ----------
    def set_format(self, fmt: str):
        self._format = fmt

    def _apply_format(self, level: str, msg: str, ctx: dict, for_file: bool = False):
        time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        context_str = self._format_context(ctx) if ctx else ""

        if context_str:
            context_str = " | " + context_str

        if self.use_colors and not for_file:
            lvl_col = self.COLORS.get(level, "")
            date_col = self.COLORS["DATE"]
            reset = self.COLORS["RESET"]
            msg_col = self.COLORS["MESSAGE"]

            return self._format.format(
                time=f"{date_col}{time_str}{reset}",
                level=f"{lvl_col}{level}{reset}",
                name=self.name,
                msg=f"{msg_col}{msg}{reset}",
                context=context_str
            )
        else:
            return self._format.format(
                time=time_str,
                level=level,
                name=self.name,
                msg=msg,
                context=context_str
            )

    # ---------- main log ----------
    def log(self, level: str, message: str, **ctx):
        level = level.upper()

        if level not in self.LEVELS:
            suggestion = self._suggest_level(level)
            raise ValueError(
                f'Unknown log level "{level}".'
                + (f' Did you mean "{suggestion}"?' if suggestion else "")
            )

        if self.LEVELS[level] < self.min_level_value:
            return

        merged_ctx = {**self._context, **ctx}

        # console
        if self.console:
            log_line_console = self._apply_format(level, message, merged_ctx, for_file=False)
            print(log_line_console)

        # file
        if self.log_to_file:
            self._check_rotate()
            log_line_file = self._apply_format(level, message, merged_ctx, for_file=True)
            self._write_file(log_line_file + "\n" + "-" * 20)

    # ---------- shortcuts ----------
    def debug(self, msg, **ctx): self.log("DEBUG", msg, **ctx)
    def info(self, msg, **ctx): self.log("INFO", msg, **ctx)
    def warning(self, msg, **ctx): self.log("WARNING", msg, **ctx)
    def error(self, msg, exc: Exception = None, **ctx):
        if exc:
            msg = f"{msg} | {type(exc).__name__}: {exc}"
        self.log("ERROR", msg, **ctx)

    # ---------- context management ----------
    def bind(self, **ctx):
        self._context.update(ctx)
        return self

    def unbind(self, *keys):
        if keys:
            for k in keys:
                self._context.pop(k, None)
        else:
            self._context.clear()
        return self

    # ---------- level suggestion ----------
    def _suggest_level(self, wrong: str):
        matches = difflib.get_close_matches(
            wrong, self.LEVELS.keys(), n=1, cutoff=0.5
        )
        return matches[0] if matches else None

    # ---------- help ----------
    def show_format_help(self):
        fields = ["time", "level", "name", "msg", "context"]
        example = "[{time}] [{level}] ({name}) {msg}{context}"
        
        # безопасный цвет для консоли
        notice = f"{self.COLORS['ERROR']}NOTICE{self.COLORS['RESET']}"
        
        help_text = (
            f"Available parameters: {fields}\n"
            f"Example: {example}\n"
            f"[{notice}] If you want to add 'context', do it as shown in the example!"
        )
        
        border = "=" * max(len(line) for line in help_text.split("\n"))
        print(f"\n{border}\n{help_text}\n{border}\n")