"""Hot/warm/cold context tier models and actor view definitions.

Provides the tiered context storage data types used by the ACMS
Context Tier Service for managing context fragment lifecycle:

| Type                | Role                                                   |
|---------------------|--------------------------------------------------------|
| ``ContextTier``     | Enumeration of hot/warm/cold storage tiers             |
| ``ActorRole``       | Enumeration of actor roles (strategist/executor/reviewer)|
| ``TieredFragment``  | A context fragment with tier and access metadata       |
| ``TierBudget``      | Token/decision budget limits per tier                  |
| ``ActorContextView``| Per-actor filtered view configuration                  |
| ``TierMetrics``     | Hit/miss counters for cache performance monitoring     |
| ``ScopedBackendView``| Project-scoped filter for resource isolation          |

Based on ``docs/specification.md`` ACMS tier sections and issue #208.
"""

from __future__ import annotations

from datetime import UTC, datetime
from enum import StrEnum
from typing import Any

from pydantic import BaseModel, ConfigDict, Field, field_validator

# ---------------------------------------------------------------------------
# Tier enumeration
# ---------------------------------------------------------------------------


class ContextTier(StrEnum):
    """Storage tier for context fragments.

    - ``HOT``  — in-memory, lowest latency, limited capacity
    - ``WARM`` — SQLite-backed, moderate latency, larger capacity
    - ``COLD`` — file-backed, highest latency, unlimited capacity
    """

    HOT = "hot"
    WARM = "warm"
    COLD = "cold"


# ---------------------------------------------------------------------------
# Actor role enumeration
# ---------------------------------------------------------------------------


class ActorRole(StrEnum):
    """Actor roles that determine context visibility.

    Each role has a default set of visible tiers and filtering rules.
    """

    STRATEGIST = "strategist"
    EXECUTOR = "executor"
    REVIEWER = "reviewer"
    ESTIMATOR = "estimator"


# ---------------------------------------------------------------------------
# TieredFragment
# ---------------------------------------------------------------------------


def _utc_now() -> datetime:
    """Return the current UTC timestamp."""
    return datetime.now(tz=UTC)


class TieredFragment(BaseModel):
    """A context fragment annotated with tier placement and access tracking.

    Extends the conceptual ``ContextFragment`` from the CRP with
    tier-specific metadata for LRU eviction and promotion/demotion
    decisions.
    """

    fragment_id: str = Field(
        ...,
        min_length=1,
        description="Stable unique identifier for this fragment",
    )
    content: str = Field(
        ...,
        description="Rendered text content of the fragment",
    )
    tier: ContextTier = Field(
        default=ContextTier.HOT,
        description="Current storage tier",
    )
    resource_id: str = Field(
        default="",
        description="Originating resource identifier",
    )
    project_name: str = Field(
        default="",
        description="Project scope for isolation filtering",
    )
    token_count: int = Field(
        default=0,
        ge=0,
        description="Token count of content",
    )
    last_accessed: datetime = Field(
        default_factory=_utc_now,
        description="Timestamp of last access for LRU eviction",
    )
    access_count: int = Field(
        default=0,
        ge=0,
        description="Number of times this fragment has been accessed",
    )
    created_at: datetime = Field(
        default_factory=_utc_now,
        description="Timestamp when the fragment was first stored",
    )
    metadata: dict[str, Any] = Field(
        default_factory=dict,
        description="Arbitrary tier-specific metadata",
    )

    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True,
    )


# ---------------------------------------------------------------------------
# TierBudget
# ---------------------------------------------------------------------------


class TierBudget(BaseModel):
    """Token and decision budget limits for each tier.

    Controls how many tokens can reside in the hot tier, and how
    many decisions (fragments) can be stored in warm and cold tiers.
    """

    max_tokens_hot: int = Field(
        default=8000,
        ge=0,
        description="Maximum total tokens in the hot tier",
    )
    max_decisions_warm: int = Field(
        default=500,
        ge=0,
        description="Maximum number of fragments in the warm tier",
    )
    max_decisions_cold: int = Field(
        default=5000,
        ge=0,
        description="Maximum number of fragments in the cold tier",
    )

    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True,
    )


# ---------------------------------------------------------------------------
# ActorContextView
# ---------------------------------------------------------------------------

# Default visible tiers per actor role
_DEFAULT_VISIBLE_TIERS: dict[ActorRole, list[ContextTier]] = {
    ActorRole.STRATEGIST: [ContextTier.HOT, ContextTier.WARM, ContextTier.COLD],
    ActorRole.EXECUTOR: [ContextTier.HOT, ContextTier.WARM],
    ActorRole.REVIEWER: [ContextTier.HOT],
    ActorRole.ESTIMATOR: [ContextTier.HOT, ContextTier.WARM],
}


class ActorContextView(BaseModel):
    """Configuration for a per-actor filtered context view.

    Determines which tiers an actor can see and optionally restricts
    results to specific projects.
    """

    actor_role: ActorRole = Field(
        ...,
        description="The role of the actor requesting context",
    )
    visible_tiers: list[ContextTier] = Field(
        default_factory=list,
        description="Tiers this actor can see (empty = use role defaults)",
    )
    project_filter: list[str] | None = Field(
        default=None,
        description="Restrict to these project names (None = no filter)",
    )
    max_fragments: int = Field(
        default=100,
        ge=1,
        description="Maximum fragments to return in a single view",
    )

    @field_validator("visible_tiers", mode="before")
    @classmethod
    def _default_visible_tiers(
        cls: type[ActorContextView],
        v: list[ContextTier],
    ) -> list[ContextTier]:
        """Populate visible_tiers from role defaults when empty."""
        # Actual default population happens in get_effective_tiers()
        return v

    def get_effective_tiers(self) -> list[ContextTier]:
        """Return the effective visible tiers (role defaults if empty)."""
        if self.visible_tiers:
            return list(self.visible_tiers)
        return list(_DEFAULT_VISIBLE_TIERS.get(self.actor_role, [ContextTier.HOT]))

    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True,
    )


# ---------------------------------------------------------------------------
# TierMetrics
# ---------------------------------------------------------------------------


class TierMetrics(BaseModel):
    """Hit/miss performance counters for the tier cache system."""

    hot_count: int = Field(default=0, ge=0, description="Fragments in hot tier")
    warm_count: int = Field(default=0, ge=0, description="Fragments in warm tier")
    cold_count: int = Field(default=0, ge=0, description="Fragments in cold tier")
    hot_hit_count: int = Field(default=0, ge=0, description="Hot tier cache hits")
    hot_miss_count: int = Field(default=0, ge=0, description="Hot tier cache misses")
    warm_hit_count: int = Field(default=0, ge=0, description="Warm tier cache hits")
    warm_miss_count: int = Field(default=0, ge=0, description="Warm tier cache misses")

    @property
    def total_fragments(self) -> int:
        """Total fragments across all tiers."""
        return self.hot_count + self.warm_count + self.cold_count

    @property
    def hot_hit_rate(self) -> float:
        """Hot tier hit rate (0.0 if no accesses)."""
        total = self.hot_hit_count + self.hot_miss_count
        if total == 0:
            return 0.0
        return self.hot_hit_count / total

    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True,
    )


# ---------------------------------------------------------------------------
# ScopedBackendView — re-exported from scoped_view module
# ---------------------------------------------------------------------------
# The full implementation (backend wrapping, resource-level filtering,
# allowlist/denylist, scope validation) lives in ``scoped_view.py``.
# This re-export preserves backward compatibility with existing imports
# from this module.
from cleveragents.domain.models.acms.scoped_view import (  # noqa: E402
    ScopedBackendView,
)

__all__: list[str] = [
    "ActorContextView",
    "ActorRole",
    "ContextTier",
    "ScopedBackendView",
    "TierBudget",
    "TierMetrics",
    "TieredFragment",
]
