"""Actor domain model for CleverAgents.

Defines the persisted representation of built-in and custom actors
used for provider/model selection and LangGraph configuration.
"""

from __future__ import annotations

import hashlib
import json
from datetime import datetime
from typing import Any

from pydantic import BaseModel, ConfigDict, Field, field_validator


class Actor(BaseModel):
    """Actor configuration stored in the config database.

    Built-in actors are generated from the provider registry using the
    ``<provider>/<model>`` naming pattern. Custom actors must use the
    ``local/<id>`` prefix and persist a canonical configuration blob.

    Attributes:
        yaml_text: Original YAML source text for the actor configuration.
        schema_version: Version of the actor config schema used.
        compiled_metadata: JSON-serialisable metadata produced by the
            actor compiler (graph topology, resolved tools, etc.).
    """

    id: int | None = Field(None, description="Actor ID")
    name: str = Field(..., min_length=3, description="Unique actor name")
    provider: str = Field(..., min_length=1, description="Provider identifier")
    model: str = Field(..., min_length=1, description="Model identifier")
    config_blob: dict[str, Any] = Field(
        default_factory=dict, description="Canonical actor configuration blob"
    )
    config_hash: str = Field(..., min_length=8, description="Hash of config_blob")
    graph_descriptor: dict[str, Any] | None = Field(
        default=None, description="Adapter-produced graph descriptor"
    )
    yaml_text: str | None = Field(default=None, description="Original YAML source text")
    schema_version: str = Field(
        default="1.0", description="Actor config schema version"
    )
    compiled_metadata: dict[str, Any] | None = Field(
        default=None, description="Compiler-produced metadata (JSON)"
    )
    unsafe: bool = Field(False, description="True when actor is marked unsafe")
    is_built_in: bool = Field(False, description="Generated from provider registry")
    is_default: bool = Field(False, description="Designated default actor")
    created_at: datetime = Field(default_factory=datetime.now)
    updated_at: datetime = Field(default_factory=datetime.now)

    @staticmethod
    def compute_hash(config: dict[str, Any]) -> str:
        """Compute a deterministic hash for a config blob."""

        normalized = json.dumps(config or {}, sort_keys=True, separators=(",", ":"))
        return hashlib.sha256(normalized.encode("utf-8")).hexdigest()

    @field_validator("name")
    @classmethod
    def validate_name(cls, value: str) -> str:
        if "/" not in value or value.count("/") != 1:
            raise ValueError(
                "Actor names must include exactly one '/' "
                "(e.g. provider/model or local/<id>)."
            )
        prefix, suffix = value.split("/", 1)
        if not prefix or not suffix:
            raise ValueError("Actor names must include both prefix and identifier.")
        if prefix == "local" and not suffix.strip():
            raise ValueError("Custom actors must be named 'local/<id>'.")
        return value

    model_config = ConfigDict(
        validate_assignment=True,
        str_strip_whitespace=True,
        extra="ignore",
    )
