from __future__ import annotations

import json
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Optional


def _utc_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


SCHEMA_VERSION = "1.0.0"

# Public event types (stable contract for runtime reliability).
EVENT_REQUEST = "request"
EVENT_RESPONSE = "response"
EVENT_TOOL = "tool"
EVENT_INTERVENTION = "intervention"
EVENT_OUTCOME = "outcome"
EVENT_ERROR = "error"


@dataclass(frozen=True)
class RuntimeEvent:
    """Runtime observability event (local, structured).

    This is distinct from `entropic_core.telemetry` which is opt-in anonymous usage telemetry.
    """

    type: str
    run_id: str
    request_id: str
    ts_utc: str = field(default_factory=_utc_iso)
    event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    schema_version: str = SCHEMA_VERSION
    provider: str = ""
    model: str = ""
    payload: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        return {
            "schema_version": self.schema_version,
            "type": self.type,
            "run_id": self.run_id,
            "request_id": self.request_id,
            "ts_utc": self.ts_utc,
            "event_id": self.event_id,
            "provider": self.provider,
            "model": self.model,
            "payload": self.payload,
        }


class JsonlEventSink:
    """Append-only JSONL sink for RuntimeEvent."""

    def __init__(self, path: Path):
        self.path = Path(path)
        self.path.parent.mkdir(parents=True, exist_ok=True)

    def emit(self, event: RuntimeEvent) -> None:
        d = event.to_dict()
        # ensure_ascii=True to avoid Windows console encoding issues in logs/tools.
        line = json.dumps(d, ensure_ascii=True)
        with self.path.open("a", encoding="utf-8") as f:
            f.write(line + "\n")


def safe_emit(sink: Optional[Any], event: RuntimeEvent) -> None:
    """Best-effort emission; never let observability break the call path."""
    if sink is None:
        return
    try:
        emit = getattr(sink, "emit", None)
        if callable(emit):
            emit(event)
    except Exception:
        return
