"""
CapabilityReceipt: Receipt domain for agent capability permits.

A CapabilityReceipt is a cryptographically signed permit that grants
a specific agent permission to perform a class of actions under constraints.

This is Local v0 of the Receipt Internet: one-machine prototype where
dangerous capabilities are gated by cryptographic receipts.

Schema: capability.v1
"""
from __future__ import annotations

import hashlib
import json
import logging
import uuid
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from receipts.compat.pyd import BaseModel, Field, field_validator
from receipts.base import BaseReceipt, Domain

logger = logging.getLogger(__name__)


class CapabilityScope(BaseModel):
    """
    Where this capability applies.

    For filesystem actions, defines the allowed path prefix and depth.
    Future versions may add network scopes, API scopes, etc.
    """

    path_prefix: Optional[str] = Field(
        default=None,
        description="For fs.* actions: allowed root path (absolute).",
    )
    max_depth: Optional[int] = Field(
        default=None,
        description="Max directory depth under path_prefix (0 = just root, None = unlimited).",
    )

    # Future scope fields
    url_pattern: Optional[str] = Field(
        default=None,
        description="For http.* actions: allowed URL pattern.",
    )
    command_pattern: Optional[str] = Field(
        default=None,
        description="For shell.* actions: allowed command pattern.",
    )


class CapabilityConstraints(BaseModel):
    """
    Limits on capability use.

    Every capability has an expiry. Optional constraints include
    call budgets, domain restrictions, and policy version binding.
    """

    expires_at: datetime = Field(
        description="Hard expiry for this capability (timezone-aware UTC)."
    )
    max_calls: Optional[int] = Field(
        default=None,
        description="Optional call budget; None = unlimited within expiry.",
    )
    domain: Optional[str] = Field(
        default=None,
        description="Loom domain this capability applies to (e.g., 'development').",
    )
    policy_hash: Optional[str] = Field(
        default=None,
        description="Hash of policy version this cap was issued under. On policy change, Guardian can invalidate.",
    )

    @field_validator("expires_at", mode="before")
    @classmethod
    def _ensure_timezone(cls, v: Any) -> datetime:
        """Ensure expires_at is timezone-aware."""
        if isinstance(v, str):
            v = datetime.fromisoformat(v.replace("Z", "+00:00"))
        if isinstance(v, datetime) and v.tzinfo is None:
            raise ValueError("expires_at must be timezone-aware")
        return v


class CapabilityReceipt(BaseReceipt):
    """
    CapabilityReceipt: permit to perform a class of actions under constraints.

    This is the core primitive for the Receipt Internet. It answers:
    - WHO may do this? (subject, issuer)
    - WHAT may they do? (action)
    - WHERE may they do it? (scope)
    - UNTIL WHEN? (constraints.expires_at)

    Memory hook: WIPE - Who, Intent, Place, Expiry

    Example:
        {
            "receipt_type": "CapabilityReceipt",
            "capability_id": "cap_2025-12-10T03:01:00Z_fs_read_ccio",
            "subject": "agent:loom.screen_mac",
            "issuer": "human:tim",
            "action": "fs.read",
            "scope": {"path_prefix": "/Users/tim/ccio", "max_depth": 4},
            "constraints": {"expires_at": "2025-12-17T03:01:00Z", "domain": "development"},
            ...
        }

    Schema: capability.v1
    """

    receipt_type: str = Field(default="CapabilityReceipt")
    domain: str = Field(default=Domain.GOVERNANCE.value)

    # Capability identification
    capability_id: str = Field(
        description="Stable ID for this permit (e.g., cap_2025-12-10T03:01:00Z_fs_read)"
    )

    # Who
    subject: str = Field(
        description="Who uses this capability (e.g., agent:loom.screen_mac, service:mcp_server)"
    )
    issuer: str = Field(
        description="Who granted this capability (e.g., human:tim, service:admin_console)"
    )

    # What (Intent)
    action: str = Field(
        description="Action verb namespace: fs.read, fs.write, shell.exec, http.fetch, etc."
    )

    # Where (Place)
    scope: CapabilityScope = Field(
        default_factory=CapabilityScope,
        description="Where this capability applies (paths, URLs, patterns)."
    )

    # When and limits (Expiry)
    constraints: CapabilityConstraints = Field(
        description="Constraints on use: expiry, budget, domain, policy binding."
    )

    # Provenance
    issued_at: datetime = Field(
        description="When this permit was minted (timezone-aware UTC)."
    )
    policy_version: Optional[str] = Field(
        default=None,
        description="Human-readable policy version (e.g., policy.v1)."
    )
    parent_receipt_id: Optional[str] = Field(
        default=None,
        description="Optional link if this permit was derived from another receipt."
    )

    # Extension point
    extra: Dict[str, Any] = Field(
        default_factory=dict,
        description="Freeform reserved area for future constraints, notes, etc."
    )

    @field_validator("issued_at", mode="before")
    @classmethod
    def _ensure_issued_at_tz(cls, v: Any) -> datetime:
        """Ensure issued_at is timezone-aware."""
        if isinstance(v, str):
            v = datetime.fromisoformat(v.replace("Z", "+00:00"))
        if isinstance(v, datetime) and v.tzinfo is None:
            raise ValueError("issued_at must be timezone-aware")
        return v

    def is_expired(self, now: Optional[datetime] = None) -> bool:
        """Check if this capability has expired."""
        if now is None:
            now = datetime.now(timezone.utc)
        return now > self.constraints.expires_at

    def allows_action(self, action: str) -> bool:
        """Check if this capability allows the given action."""
        # Exact match
        if self.action == action:
            return True
        # Wildcard match (e.g., fs.* matches fs.read)
        if self.action.endswith(".*"):
            prefix = self.action[:-1]  # "fs."
            if action.startswith(prefix):
                return True
        # Universal wildcard
        if self.action == "*":
            return True
        return False

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for serialization."""
        # Use model_dump for pydantic v2, dict for v1
        if hasattr(self, "model_dump"):
            data = self.model_dump(mode="json")
        else:
            data = self.dict()

        # Ensure nested models are serialized
        if hasattr(self.scope, "model_dump"):
            data["scope"] = self.scope.model_dump()
        elif hasattr(self.scope, "dict"):
            data["scope"] = self.scope.dict()
        if hasattr(self.constraints, "model_dump"):
            data["constraints"] = self.constraints.model_dump()
        elif hasattr(self.constraints, "dict"):
            data["constraints"] = self.constraints.dict()
        return data


def compute_capability_id(action: str, scope: CapabilityScope, issued_at: datetime) -> str:
    """
    Generate a stable capability ID from action, scope, and timestamp.
    """
    scope_str = scope.path_prefix or "global"
    ts = issued_at.strftime("%Y-%m-%dT%H:%M:%SZ")
    return f"cap_{ts}_{action.replace('.', '_')}_{scope_str.split('/')[-1][:20]}"


def create_capability_receipt(
    subject: str,
    action: str,
    scope: CapabilityScope,
    constraints: CapabilityConstraints,
    issuer: str = "human:operator",
    policy_version: Optional[str] = None,
    parent_receipt_id: Optional[str] = None,
    extra: Optional[Dict[str, Any]] = None,
) -> CapabilityReceipt:
    """
    Create a CapabilityReceipt with proper defaults.

    This is the primary factory function for creating capability permits.
    """
    now = datetime.now(timezone.utc)
    capability_id = compute_capability_id(action, scope, now)
    receipt_id = f"capr_{uuid.uuid4().hex[:16]}"

    return CapabilityReceipt(
        receipt_id=receipt_id,
        capability_id=capability_id,
        subject=subject,
        issuer=issuer,
        action=action,
        scope=scope,
        constraints=constraints,
        issued_at=now,
        policy_version=policy_version,
        parent_receipt_id=parent_receipt_id,
        extra=extra or {},
    )
