import asyncio
import json
import sys
from pathlib import Path

# ANSI color codes
_RESET = "\033[0m"
_DIM = "\033[2m"
_BOLD = "\033[1m"

_LEVEL_COLORS = {
    "debug": "\033[36m",  # cyan
    "info": "\033[32m",  # green
    "warning": "\033[33m",  # yellow
    "error": "\033[31m",  # red
    "critical": "\033[1;31m",  # bold red
}

_LOGGER_COLORS = {
    "otto.daemon": "\033[35m",  # magenta
    "otto.telegram": "\033[34m",  # blue
    "otto.gateway": "\033[36m",  # cyan
    "otto.agent": "\033[33m",  # yellow
    "otto.scheduler": "\033[32m",  # green
}

_LEVEL_ORDER = {"debug": 0, "info": 1, "warning": 2, "error": 3, "critical": 4}


def _logger_color(name: str) -> str:
    if name in _LOGGER_COLORS:
        return _LOGGER_COLORS[name]
    for prefix, color in _LOGGER_COLORS.items():
        if name.startswith(prefix):
            return color
    return _DIM


def _format_line(record: dict) -> str:
    level = record.get("level", "info").lower()
    logger = record.get("logger", "?")
    event = record.get("event", "")
    timestamp = record.get("timestamp", "")

    level_color = _LEVEL_COLORS.get(level, "")
    log_color = _logger_color(logger)

    # Format timestamp: show only HH:MM:SS from ISO
    time_short = timestamp[11:19] if len(timestamp) >= 19 else timestamp

    # Collect extra context fields
    skip = {"event", "level", "logger", "timestamp", "positional_args", "exc_info"}
    extras = {k: v for k, v in record.items() if k not in skip}

    # Format positional args into event string if present
    if "positional_args" in record and "%" in event:
        try:
            event = event % tuple(record["positional_args"])
        except (TypeError, ValueError):
            pass

    parts = [
        f"{_DIM}{time_short}{_RESET}",
        f"{level_color}{level:<8}{_RESET}",
        f"{log_color}{logger:<25}{_RESET}",
        f"{event}",
    ]

    line = " ".join(parts)

    if extras:
        ctx = " ".join(f"{_DIM}{k}{_RESET}={v}" for k, v in extras.items())
        line += f"  {ctx}"

    # Append exception info if present
    if "exc_info" in record:
        exc = record["exc_info"]
        if isinstance(exc, list) and len(exc) >= 2:
            line += f"\n         {_LEVEL_COLORS.get('error', '')}{exc[0]}: {exc[1]}{_RESET}"

    return line


def _parse_and_format(raw: str, level_filter: str | None) -> str | None:
    try:
        record = json.loads(raw)
    except (json.JSONDecodeError, ValueError):
        return raw.rstrip() if raw.strip() else None

    if level_filter:
        record_level = record.get("level", "info").lower()
        if _LEVEL_ORDER.get(record_level, 1) < _LEVEL_ORDER.get(level_filter.lower(), 0):
            return None

    return _format_line(record)


async def render_logs(
    log_file: Path,
    *,
    follow: bool = False,
    tail: int = 50,
    level_filter: str | None = None,
) -> None:
    is_tty = sys.stdout.isatty()

    # Read existing lines (tail)
    try:
        content = log_file.read_text(encoding="utf-8", errors="replace")
    except Exception as exc:
        print(f"Failed to read log file: {exc}")
        return

    lines = content.splitlines()
    tail_lines = lines[-tail:] if tail > 0 else lines

    for raw in tail_lines:
        formatted = _parse_and_format(raw, level_filter)
        if formatted is not None:
            print(formatted)

    if not follow:
        return

    # Follow mode: watch for new lines
    if is_tty:
        print(f"{_DIM}--- following {log_file} (ctrl+c to stop) ---{_RESET}")

    offset = log_file.stat().st_size
    try:
        while True:
            await asyncio.sleep(0.5)
            current_size = log_file.stat().st_size
            if current_size <= offset:
                if current_size < offset:
                    offset = 0  # file was truncated/rotated
                continue

            with open(log_file, encoding="utf-8", errors="replace") as fh:
                fh.seek(offset)
                new_data = fh.read()
                offset = fh.tell()

            for raw in new_data.splitlines():
                formatted = _parse_and_format(raw, level_filter)
                if formatted is not None:
                    print(formatted, flush=True)
    except (KeyboardInterrupt, asyncio.CancelledError):
        pass
