"""
logger.py — JSONL structured logger with daily rotation.

One file per UTC day under logs/events-YYYY-MM-DD.jsonl. Append-only.
Crash-safe (each line is flushed). Schema is a thin convention: every line
has at least {ts, type}; the rest of the keys depend on the event type.
"""

from __future__ import annotations

import json
import sys
import time
from pathlib import Path
from threading import Lock


class JsonlLogger:
    def __init__(self, log_dir: str = "logs", echo_to_stdout: bool = True):
        self.log_dir = Path(log_dir)
        self.log_dir.mkdir(parents=True, exist_ok=True)
        self.echo = echo_to_stdout
        self._lock = Lock()
        self._current_day: str | None = None
        self._fh = None

    def _rotate_if_needed(self):
        day = time.strftime("%Y-%m-%d", time.gmtime())
        if day != self._current_day:
            if self._fh is not None:
                self._fh.close()
            self._fh = open(self.log_dir / f"events-{day}.jsonl", "a",
                            encoding="utf-8")
            self._current_day = day

    def log(self, event_type: str, **fields):
        line = {"ts": time.time(), "type": event_type, **fields}
        encoded = json.dumps(line, default=str, separators=(",", ":")) + "\n"
        with self._lock:
            self._rotate_if_needed()
            self._fh.write(encoded)
            self._fh.flush()
            if self.echo:
                # Compact, human-friendly stderr mirror
                sys.stderr.write(self._human(line) + "\n")
                sys.stderr.flush()

    def _human(self, line: dict) -> str:
        ts = time.strftime("%H:%M:%S", time.gmtime(line["ts"]))
        type_ = line["type"]
        rest = ", ".join(
            f"{k}={v}" for k, v in line.items()
            if k not in ("ts", "type") and not isinstance(v, (dict, list))
        )
        return f"[{ts}] {type_}  {rest}"

    def close(self):
        with self._lock:
            if self._fh is not None:
                self._fh.close()
                self._fh = None
