"""Audit log -- append-only JSONL event log for every pipeline state transition.

Each event is a single JSON line with timestamp, task ID, event type,
and optional payload.  The log is never truncated; old events are permanent.

Usage::

    log = AuditLog(data_dir=Path("data"))
    log.record("enqueued",  task_id="wq_abc", payload={"priority": "high"})
    log.record("claimed",   task_id="wq_abc")
    log.record("completed", task_id="wq_abc", payload={"result": "ok"})

    events = log.read(task_id="wq_abc")   # filter by task
    all_events = log.read()               # full history
"""

from __future__ import annotations

import json
import logging
import threading
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional

logger = logging.getLogger(__name__)

_FILENAME = "pipeline_audit.jsonl"


class AuditLog:
    """Append-only JSONL audit log.

    Parameters
    ----------
    data_dir:
        Directory where the log file is written.
        File: ``{data_dir}/pipeline_audit.jsonl``
    """

    def __init__(self, data_dir: Path) -> None:
        self._path = data_dir / _FILENAME
        self._lock = threading.Lock()

    def record(
        self,
        event: str,
        task_id: str,
        actor: Optional[str] = None,
        payload: Optional[Dict[str, Any]] = None,
    ) -> None:
        """Append one event to the log.

        Parameters
        ----------
        event:
            Event name, e.g. ``"enqueued"``, ``"claimed"``, ``"approved"``.
        task_id:
            ID of the task this event relates to.
        actor:
            Agent or user that triggered the event.
        payload:
            Arbitrary extra data to include in the log entry.
        """
        entry: Dict[str, Any] = {
            "ts": datetime.now(timezone.utc).isoformat(),
            "event": event,
            "task_id": task_id,
        }
        if actor:
            entry["actor"] = actor
        if payload:
            entry["payload"] = payload

        line = json.dumps(entry, ensure_ascii=False)
        with self._lock:
            try:
                self._path.parent.mkdir(parents=True, exist_ok=True)
                with self._path.open("a", encoding="utf-8") as fh:
                    fh.write(line + "\n")
            except Exception as exc:
                logger.warning("[!] AuditLog write error: %s", exc)

    def read(
        self,
        task_id: Optional[str] = None,
        event: Optional[str] = None,
        limit: Optional[int] = None,
    ) -> List[Dict[str, Any]]:
        """Read events from the log, optionally filtered.

        Parameters
        ----------
        task_id:
            If set, return only events for this task.
        event:
            If set, return only events with this name.
        limit:
            If set, return the last N matching events.
        """
        if not self._path.exists():
            return []

        events: List[Dict[str, Any]] = []
        try:
            with self._path.open("r", encoding="utf-8") as fh:
                for line in fh:
                    line = line.strip()
                    if not line:
                        continue
                    try:
                        entry = json.loads(line)
                    except json.JSONDecodeError:
                        continue
                    if task_id and entry.get("task_id") != task_id:
                        continue
                    if event and entry.get("event") != event:
                        continue
                    events.append(entry)
        except Exception as exc:
            logger.warning("[!] AuditLog read error: %s", exc)

        if limit:
            events = events[-limit:]
        return events
