"""
Telemetry data models for releaseops.

Defines immutable structures for bundle metadata that gets injected
into traces and observability spans.
"""

from __future__ import annotations

import json
from dataclasses import dataclass, field
from typing import Any, Dict


@dataclass(frozen=True)
class TelemetryContext:
    """
    Bundle metadata for injection into traces and spans.

    Adapter over BundleManifest — converts bundle data into a flat
    key-value structure suitable for OpenTelemetry attributes.
    """

    bundle_id: str
    bundle_version: str
    bundle_hash: str
    environment: str
    prompt_refs: Dict[str, str] = field(default_factory=dict)
    policy_refs: Dict[str, str] = field(default_factory=dict)
    model_config: Dict[str, Any] = field(default_factory=dict)

    def __post_init__(self) -> None:
        if not self.bundle_id:
            raise ValueError("bundle_id cannot be empty")
        if not self.bundle_version:
            raise ValueError("bundle_version cannot be empty")

    def to_flat_dict(self) -> Dict[str, str]:
        """
        Convert to flat key-value pairs prefixed with 'releaseops.'.

        All values are strings. Nested dicts use dot-separated keys.
        Example: prompt_refs={"system": "prompts/system@1.0.1"}
                 → {"releaseops.prompt.system": "prompts/system@1.0.1"}
        """
        flat: Dict[str, str] = {
            "releaseops.bundle_id": self.bundle_id,
            "releaseops.bundle_version": self.bundle_version,
            "releaseops.bundle_hash": self.bundle_hash,
            "releaseops.environment": self.environment,
        }

        for role, ref in self.prompt_refs.items():
            flat[f"releaseops.prompt.{role}"] = ref

        for role, ref in self.policy_refs.items():
            flat[f"releaseops.policy.{role}"] = ref

        if self.model_config:
            flat["releaseops.model_config"] = json.dumps(
                self.model_config, sort_keys=True
            )

        return flat

    @classmethod
    def from_flat_dict(cls, data: Dict[str, str]) -> TelemetryContext:
        """
        Reconstruct from flat key-value pairs.

        Reverse of to_flat_dict(). Handles missing keys gracefully.
        """
        prompt_refs: Dict[str, str] = {}
        policy_refs: Dict[str, str] = {}
        model_config: Dict[str, Any] = {}

        for key, value in data.items():
            if key.startswith("releaseops.prompt."):
                role = key[len("releaseops.prompt."):]
                prompt_refs[role] = value
            elif key.startswith("releaseops.policy."):
                role = key[len("releaseops.policy."):]
                policy_refs[role] = value

        model_config_str = data.get("releaseops.model_config")
        if model_config_str:
            model_config = json.loads(model_config_str)

        return cls(
            bundle_id=data.get("releaseops.bundle_id", ""),
            bundle_version=data.get("releaseops.bundle_version", ""),
            bundle_hash=data.get("releaseops.bundle_hash", ""),
            environment=data.get("releaseops.environment", ""),
            prompt_refs=prompt_refs,
            policy_refs=policy_refs,
            model_config=model_config,
        )

    def to_dict(self) -> dict:
        """Standard serialization to dict."""
        return {
            "bundle_id": self.bundle_id,
            "bundle_version": self.bundle_version,
            "bundle_hash": self.bundle_hash,
            "environment": self.environment,
            "prompt_refs": dict(self.prompt_refs),
            "policy_refs": dict(self.policy_refs),
            "model_config": dict(self.model_config),
        }

    @classmethod
    def from_dict(cls, data: dict) -> TelemetryContext:
        """Standard deserialization from dict."""
        return cls(
            bundle_id=data["bundle_id"],
            bundle_version=data["bundle_version"],
            bundle_hash=data["bundle_hash"],
            environment=data["environment"],
            prompt_refs=data.get("prompt_refs", {}),
            policy_refs=data.get("policy_refs", {}),
            model_config=data.get("model_config", {}),
        )
