"""
Domain Policy Registry for Loom Receipt Infrastructure.

Loads domain policies from config/domain_policies.yaml and provides
policy lookup for the ReceiptWriter.

Usage:
    from receipts.policy_registry import get_policy_registry, DomainPolicy
    
    registry = get_policy_registry()
    policy = registry.get("clinical")
    
    if policy.require_lineage:
        # Enforce lineage for this receipt
        ...
"""

from __future__ import annotations

import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional
import yaml

from loom.proof_config import AttestationLevel


@dataclass
class LineagePolicy:
    """
    Policy for lineage validation within a domain.
    """
    enforce_temporal_order: bool = True
    """Parent transaction_time must be <= child observed_at."""
    
    require_parent_in_vault: bool = True
    """Parent receipts must exist in the vault before child can reference them."""
    
    max_chain_depth: int = 1000
    """Maximum depth of lineage chain (prevents infinite loops)."""
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "LineagePolicy":
        return cls(
            enforce_temporal_order=data.get("enforce_temporal_order", True),
            require_parent_in_vault=data.get("require_parent_in_vault", True),
            max_chain_depth=data.get("max_chain_depth", 1000),
        )


@dataclass
class DomainPolicy:
    """
    Policy configuration for a specific domain.
    
    Defines what attestation level, lineage rules, and schema validation
    are required for receipts in this domain.
    """
    name: str
    """Domain identifier (e.g., 'clinical', 'finance', 'test')."""
    
    description: str = ""
    """Human-readable description of the domain."""
    
    min_attestation: AttestationLevel = AttestationLevel.L1_SIGNED
    """Minimum attestation level required for receipts in this domain."""
    
    require_lineage: bool = False
    """Whether receipts with parents must have validated lineage."""
    
    require_schema_validation: bool = True
    """Whether receipts must pass JSON schema validation."""
    
    tsa_required: bool = False
    """Whether RFC 3161 timestamp is mandatory (implies L2+ attestation)."""
    
    fail_on_missing_key: bool = True
    """Whether to fail if signing key unavailable (vs. allowing unsigned)."""
    
    allowed_schemas: List[str] = field(default_factory=list)
    """List of allowed schema IDs. Empty = any schema allowed."""
    
    lineage_policy: LineagePolicy = field(default_factory=LineagePolicy)
    """Detailed lineage validation rules."""
    
    def allows_attestation(self, level: AttestationLevel) -> bool:
        """Check if a given attestation level meets domain requirements."""
        return level >= self.min_attestation
    
    def allows_schema(self, schema_id: str) -> bool:
        """Check if a schema is allowed for this domain."""
        if not self.allowed_schemas:
            return True  # Empty list = any schema
        return schema_id in self.allowed_schemas
    
    @classmethod
    def from_dict(cls, name: str, data: Dict[str, Any]) -> "DomainPolicy":
        """Create DomainPolicy from YAML dictionary."""
        # Parse attestation level
        min_attestation_str = data.get("min_attestation", "L1_SIGNED")
        try:
            min_attestation = AttestationLevel(min_attestation_str)
        except ValueError:
            # Try without L prefix for backwards compatibility
            min_attestation = AttestationLevel(f"L{min_attestation_str}" if min_attestation_str[0].isdigit() else min_attestation_str)
        
        # Parse lineage policy
        lineage_data = data.get("lineage_policy", {})
        lineage_policy = LineagePolicy.from_dict(lineage_data)
        
        return cls(
            name=name,
            description=data.get("description", ""),
            min_attestation=min_attestation,
            require_lineage=data.get("require_lineage", False),
            require_schema_validation=data.get("require_schema_validation", True),
            tsa_required=data.get("tsa_required", False),
            fail_on_missing_key=data.get("fail_on_missing_key", True),
            allowed_schemas=data.get("allowed_schemas", []),
            lineage_policy=lineage_policy,
        )


@dataclass
class SchemaMapping:
    """Mapping of schema ID to file and default domain."""
    schema_id: str
    file: str
    default_domain: str
    
    @classmethod
    def from_dict(cls, schema_id: str, data: Dict[str, Any]) -> "SchemaMapping":
        return cls(
            schema_id=schema_id,
            file=data.get("file", ""),
            default_domain=data.get("default_domain", "default"),
        )


class PolicyRegistry:
    """
    Registry of domain policies loaded from YAML configuration.
    
    Provides lookup by domain name and schema-to-domain mapping.
    """
    
    def __init__(self, policies: Dict[str, DomainPolicy], schemas: Dict[str, SchemaMapping]):
        self._policies = policies
        self._schemas = schemas
        self._default = policies.get("default", DomainPolicy(name="default"))
    
    def get(self, domain: str) -> DomainPolicy:
        """
        Get policy for a domain.
        
        Returns default policy if domain not found.
        """
        return self._policies.get(domain, self._default)
    
    def get_for_schema(self, schema_id: str) -> DomainPolicy:
        """
        Get policy for a schema ID (using schema's default domain).
        """
        mapping = self._schemas.get(schema_id)
        if mapping:
            return self.get(mapping.default_domain)
        return self._default
    
    def list_domains(self) -> List[str]:
        """List all registered domain names."""
        return list(self._policies.keys())
    
    def list_high_stakes_domains(self) -> List[str]:
        """List domains requiring L3+ attestation."""
        return [
            name for name, policy in self._policies.items()
            if policy.min_attestation >= AttestationLevel.L3_ANCHORED
        ]
    
    @classmethod
    def from_yaml(cls, yaml_path: Path) -> "PolicyRegistry":
        """Load registry from YAML file."""
        with open(yaml_path) as f:
            data = yaml.safe_load(f)
        
        # Parse domain policies
        policies = {}
        for name, policy_data in data.get("domains", {}).items():
            policies[name] = DomainPolicy.from_dict(name, policy_data)
        
        # Parse schema mappings
        schemas = {}
        for schema_id, schema_data in data.get("schema_registry", {}).items():
            schemas[schema_id] = SchemaMapping.from_dict(schema_id, schema_data)
        
        return cls(policies, schemas)
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "PolicyRegistry":
        """Create registry from dictionary (for testing)."""
        policies = {}
        for name, policy_data in data.get("domains", {}).items():
            policies[name] = DomainPolicy.from_dict(name, policy_data)
        
        schemas = {}
        for schema_id, schema_data in data.get("schema_registry", {}).items():
            schemas[schema_id] = SchemaMapping.from_dict(schema_id, schema_data)
        
        return cls(policies, schemas)


# =============================================================================
# Global Registry Access
# =============================================================================

_global_registry: Optional[PolicyRegistry] = None


def get_policy_registry(config_path: Optional[Path] = None) -> PolicyRegistry:
    """
    Get the global policy registry.
    
    Loads from config/domain_policies.yaml on first call.
    
    Args:
        config_path: Optional path to YAML config (for testing)
    
    Returns:
        PolicyRegistry instance
    """
    global _global_registry
    
    if _global_registry is None:
        if config_path is None:
            # Find config relative to this file or workspace root
            possible_paths = [
                Path(__file__).parent.parent / "config" / "domain_policies.yaml",
                Path("config/domain_policies.yaml"),
                Path(os.environ.get("LOOM_CONFIG_PATH", "")) / "domain_policies.yaml",
            ]
            for path in possible_paths:
                if path.exists():
                    config_path = path
                    break
            else:
                # Return minimal default registry
                return PolicyRegistry(
                    policies={"default": DomainPolicy(name="default")},
                    schemas={},
                )
        
        _global_registry = PolicyRegistry.from_yaml(config_path)
    
    return _global_registry


def configure_policy_registry(registry: PolicyRegistry) -> None:
    """
    Explicitly set the global policy registry.
    
    Useful for testing or programmatic configuration.
    """
    global _global_registry
    _global_registry = registry


def reset_policy_registry() -> None:
    """Reset global registry (for testing)."""
    global _global_registry
    _global_registry = None


# =============================================================================
# Convenience Functions
# =============================================================================

def get_domain_policy(domain: str) -> DomainPolicy:
    """Shortcut to get policy for a domain."""
    return get_policy_registry().get(domain)


def is_high_stakes_domain(domain: str) -> bool:
    """Check if domain requires L3+ attestation."""
    policy = get_domain_policy(domain)
    return policy.min_attestation >= AttestationLevel.L3_ANCHORED


def requires_tsa(domain: str) -> bool:
    """Check if domain requires TSA timestamp."""
    return get_domain_policy(domain).tsa_required


def requires_lineage(domain: str) -> bool:
    """Check if domain requires lineage validation."""
    return get_domain_policy(domain).require_lineage
