"""Output formatters — JSON and human-readable."""

import json
import sys
from datetime import datetime, timezone
from typing import Callable, TextIO, Any


def _default_serializer(obj: Any) -> Any:
    """JSON serializer for non-standard types."""
    if isinstance(obj, datetime):
        return obj.isoformat()
    if isinstance(obj, Exception):
        return {"error": type(obj).__name__, "message": str(obj)}
    if hasattr(obj, "__dict__"):
        return str(obj)
    return str(obj)


class JsonFormatter:
    """Formats log records as JSON lines."""

    def __init__(self, *, indent: int | None = None, sort_keys: bool = False,
                 ensure_ascii: bool = False, include: list[str] | None = None,
                 exclude: list[str] | None = None):
        self.indent = indent
        self.sort_keys = sort_keys
        self.ensure_ascii = ensure_ascii
        self.include = set(include) if include else None
        self.exclude = set(exclude) if exclude else None

    def format(self, record: dict) -> str:
        rec = dict(record)
        if self.include:
            rec = {k: v for k, v in rec.items() if k in self.include}
        if self.exclude:
            rec = {k: v for k, v in rec.items() if k not in self.exclude}
        return json.dumps(rec, default=_default_serializer, indent=self.indent,
                          sort_keys=self.sort_keys, ensure_ascii=self.ensure_ascii)


class HumanFormatter:
    """Formats log records as human-readable text.

    Output: 2026-03-24 21:08:00.123 [INFO] message  key=value key2=value2
    """

    def __init__(self, *, color: bool = False, include: list[str] | None = None,
                 exclude: list[str] | None = None):
        self.color = color
        self.include = set(include) if include else None
        self.exclude = set(exclude) if exclude else None
        self._colors = {
            "debug": "\033[36m",     # cyan
            "info": "\033[32m",      # green
            "warning": "\033[33m",   # yellow
            "error": "\033[31m",     # red
            "critical": "\033[1;31m", # bold red
            "trace": "\033[35m",     # magenta
        }
        self._reset = "\033[0m"

    def format(self, record: dict) -> str:
        rec = dict(record)
        level = rec.pop("level", "info").upper()
        msg = rec.pop("msg", "")
        ts = rec.pop("ts", "")
        logger = rec.pop("logger", "")

        # Extract known fields for the formatted prefix
        func = rec.pop("func", "")
        file = rec.pop("file", "")
        line = rec.pop("line", "")

        if self.include:
            rec = {k: v for k, v in rec.items() if k in self.include}
        if self.exclude:
            rec = {k: v for k, v in rec.items() if k not in self.exclude}

        # Build the prefix
        parts = [ts[:23]]  # trim microseconds for readability
        if self.color and level.lower() in self._colors:
            parts.append(f"{self._colors[level.lower()]}[{level}]{self._reset}")
        else:
            parts.append(f"[{level}]")

        parts.append(msg)

        # Add location info
        if func:
            parts.append(f"func={func}")

        # Add extra fields
        for k, v in rec.items():
            if k in ("ts", "level", "msg", "logger", "func", "file", "line"):
                continue
            parts.append(f"{k}={_format_value(v)}")

        return "  ".join(parts)


def _format_value(v: Any) -> str:
    if isinstance(v, str):
        return v
    if isinstance(v, (int, float, bool)):
        return str(v)
    return json.dumps(v, default=_default_serializer, ensure_ascii=False)
