from __future__ import annotations

import hashlib
import json
import os
import threading
import uuid
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from .safety import contains_credential_like_value


EVENT_TYPES = {
    "run_started",
    "model_request",
    "model_response",
    "tool_proposed",
    "proposal_conformance",
    "policy_decision",
    "tool_result",
    "oracle_result",
    "cost_update",
    "run_finished",
    "run_aborted",
}

SENSITIVE_KEYS = {
    "api_key",
    "apikey",
    "authorization",
    "password",
    "secret",
    "token",
    "access_token",
    "refresh_token",
}


def canonical_json(value: Any) -> str:
    return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)


def sha256_json(value: Any) -> str:
    return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()


def redact(value: Any, key: str = "") -> Any:
    if key.lower() in SENSITIVE_KEYS:
        return "[REDACTED]"
    if isinstance(value, dict):
        return {str(k): redact(v, str(k)) for k, v in value.items()}
    if isinstance(value, list):
        return [redact(item) for item in value]
    if contains_credential_like_value(value):
        return "[REDACTED]"
    return value


@dataclass(frozen=True)
class RunEvent:
    schema_version: str
    event_id: str
    run_id: str
    timestamp: str
    event_type: str
    payload_hash: str
    payload: dict[str, Any]

    @classmethod
    def create(cls, run_id: str, event_type: str, payload: dict[str, Any]) -> "RunEvent":
        if event_type not in EVENT_TYPES:
            raise ValueError(f"Unsupported event type: {event_type}")
        clean = redact(payload)
        return cls(
            schema_version="1.0",
            event_id=str(uuid.uuid4()),
            run_id=run_id,
            timestamp=datetime.now(timezone.utc).isoformat(),
            event_type=event_type,
            payload_hash=sha256_json(clean),
            payload=clean,
        )

    def as_dict(self) -> dict[str, Any]:
        return asdict(self)


class EventWriter:
    def __init__(self, path: Path) -> None:
        self.path = path
        self.path.parent.mkdir(parents=True, exist_ok=True)
        self._lock = threading.Lock()

    def append(self, event: RunEvent) -> None:
        line = canonical_json(event.as_dict()) + "\n"
        with self._lock:
            with self.path.open("a", encoding="utf-8") as handle:
                handle.write(line)
                handle.flush()
                os.fsync(handle.fileno())
