from __future__ import annotations

import json
import os
import shutil
import sys
import time
from contextlib import contextmanager
from datetime import datetime
from typing import Any, Iterator


def _supports_color() -> bool:
    return sys.stdout.isatty() and os.getenv("TERM", "") != "dumb"


def format_duration(seconds: float) -> str:
    if seconds < 1:
        return f"{seconds:.2f}s"
    if seconds < 60:
        return f"{seconds:.1f}s"

    minutes, remainder = divmod(seconds, 60)
    if minutes < 60:
        return f"{int(minutes)}m {remainder:.0f}s"

    hours, minutes = divmod(minutes, 60)
    return f"{int(hours)}h {int(minutes)}m {remainder:.0f}s"


class ConsoleLogger:
    COLORS = {
        "reset": "\033[0m",
        "bold": "\033[1m",
        "cyan": "\033[36m",
        "green": "\033[32m",
        "yellow": "\033[33m",
        "red": "\033[31m",
        "blue": "\033[34m",
        "magenta": "\033[35m",
    }

    def __init__(self) -> None:
        self.use_color = _supports_color()

    def _color(self, name: str) -> str:
        if not self.use_color:
            return ""
        return self.COLORS[name]

    def _timestamp(self) -> str:
        return datetime.now().strftime("%H:%M:%S")

    def _write(self, line: str) -> None:
        print(line, flush=True)

    def _emit(self, level: str, message: str, *, color: str = "reset") -> None:
        level_text = level.rjust(5)
        colored_level = f"{self._color(color)}{level_text}{self._color('reset')}"
        self._write(f"[{self._timestamp()}] {colored_level} {message}")

    def banner(self, title: str, subtitle: str | None = None) -> None:
        width = min(shutil.get_terminal_size((100, 20)).columns, 100)
        line = "=" * width
        self._write(line)
        self._write(f"{title}")
        if subtitle:
            self._write(subtitle)
        self._write(line)

    def rule(self, title: str) -> None:
        width = min(shutil.get_terminal_size((100, 20)).columns, 100)
        body = f" {title} "
        remaining = max(width - len(body), 0)
        left = "-" * (remaining // 2)
        right = "-" * (remaining - len(left))
        self._write(f"{left}{body}{right}")

    def info(self, message: str) -> None:
        self._emit("INFO", message, color="blue")

    def progress(self, message: str) -> None:
        self._emit("PROG", message, color="cyan")

    def success(self, message: str) -> None:
        self._emit("DONE", message, color="green")

    def warn(self, message: str) -> None:
        self._emit("WARN", message, color="yellow")

    def error(self, message: str) -> None:
        self._emit("FAIL", message, color="red")

    @contextmanager
    def step(
        self,
        title: str,
        *,
        index: int | None = None,
        total: int | None = None,
        detail: str | None = None,
    ) -> Iterator[None]:
        prefix = "STEP"
        if index is not None and total is not None:
            prefix = f"STEP {index}/{total}"
        self._emit(prefix, title, color="magenta")
        if detail:
            self.info(detail)

        start = time.perf_counter()
        try:
            yield
        except Exception as exc:
            elapsed = format_duration(time.perf_counter() - start)
            self.error(f"{title} failed after {elapsed}: {exc}")
            raise
        else:
            elapsed = format_duration(time.perf_counter() - start)
            self.success(f"{title} completed in {elapsed}")

    def summary(self, title: str, payload: dict[str, Any]) -> None:
        self.rule(title)
        if not payload:
            self.info("No summary data.")
            return

        longest_key = max(len(key) for key in payload)
        for key, value in payload.items():
            if isinstance(value, dict):
                rendered = json.dumps(value, sort_keys=True)
            elif isinstance(value, list):
                rendered = json.dumps(value)
            else:
                rendered = str(value)
            self._write(f"{key.ljust(longest_key)} : {rendered}")


console = ConsoleLogger()
