"""
Membrane model for "No Action Without Receipt" invariant.

This module defines:
- EffectType: Classification of side effects at the membrane
- DomainProfile: Operational contexts with different receipt requirements
- ObservationReceipt: Receipt for privileged observations
- ObservationSessionReceipt: Receipt for continuous observation sessions

The membrane is the boundary between the organism's internal processing
and the outside world. Any operation crossing this boundary with an
effect type that's required for the current domain profile must emit
a receipt.

See: docs/NO_ACTION_WITHOUT_RECEIPT_ROADMAP_v2.md
"""
from __future__ import annotations

import uuid
from datetime import datetime, timezone
from enum import Enum
from typing import Any, Dict, List, Literal, Optional, Set

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


# ---------------------------------------------------------------------------
# EffectType Enum
# ---------------------------------------------------------------------------


class EffectType(str, Enum):
    """
    Classification of side effects for receipt requirements.

    Used to determine which operations require receipts in a given domain.
    Operations crossing the membrane are tagged with an EffectType, and
    the domain profile determines which types require receipts.
    """

    # Reads (inbound)
    READ = "read"          # Privileged read (PHI, PII, user files, secrets)
    OBSERVE = "observe"    # Sensor sampling (camera, mic, screen capture)

    # Writes (outbound)
    WRITE = "write"        # Changed something external (file, DB, state)
    EXECUTE = "execute"    # Ran code or command
    NETWORK = "network"    # Called external service (HTTP, RPC, etc.)

    # Internal durable
    MEMORY = "memory"      # Changed long-lived internal state
    POLICY = "policy"      # Changed decision-making rules or config

    # Human-facing
    SPEAK = "speak"        # Presented output to human (voice, text, UI)


# ---------------------------------------------------------------------------
# DomainProfile Enum
# ---------------------------------------------------------------------------


class DomainProfile(str, Enum):
    """
    Operational context determining receipt requirements.

    Different contexts have different requirements for what must be receipted.
    A clinical context requires receipts for everything including observations,
    while a dev context may only require receipts for writes and executions.
    """

    DEV = "dev"                    # Development / sandbox
    DESKTOP_AUTOPILOT = "desktop"  # Desktop automation
    ENTERPRISE = "enterprise"      # Business operations
    CLINICAL = "clinical"          # Healthcare / medical
    FINANCIAL = "financial"        # Markets / trading
    INFRASTRUCTURE = "infra"       # System administration


# ---------------------------------------------------------------------------
# Domain Profile Configuration
# ---------------------------------------------------------------------------


DOMAIN_REQUIRED_EFFECTS: Dict[DomainProfile, Set[EffectType]] = {
    DomainProfile.DEV: {
        EffectType.WRITE,
        EffectType.EXECUTE,
        EffectType.POLICY,
        EffectType.MEMORY,
        EffectType.NETWORK,
    },
    DomainProfile.DESKTOP_AUTOPILOT: {
        EffectType.WRITE,
        EffectType.EXECUTE,
        EffectType.SPEAK,
        EffectType.NETWORK,
    },
    DomainProfile.ENTERPRISE: {
        EffectType.READ,
        EffectType.WRITE,
        EffectType.EXECUTE,
        EffectType.POLICY,
        EffectType.MEMORY,
        EffectType.NETWORK,
        EffectType.SPEAK,
    },
    DomainProfile.CLINICAL: {
        EffectType.READ,
        EffectType.WRITE,
        EffectType.EXECUTE,
        EffectType.POLICY,
        EffectType.MEMORY,
        EffectType.NETWORK,
        EffectType.SPEAK,
        EffectType.OBSERVE,
    },
    DomainProfile.FINANCIAL: {
        EffectType.READ,
        EffectType.WRITE,
        EffectType.EXECUTE,
        EffectType.POLICY,
        EffectType.MEMORY,
        EffectType.NETWORK,
        EffectType.SPEAK,
    },
    DomainProfile.INFRASTRUCTURE: {
        EffectType.WRITE,
        EffectType.EXECUTE,
        EffectType.POLICY,
        EffectType.MEMORY,
        EffectType.NETWORK,
    },
}


def get_required_effects(domain: DomainProfile) -> Set[EffectType]:
    """Get the set of effect types that require receipts for a domain."""
    return DOMAIN_REQUIRED_EFFECTS.get(domain, DOMAIN_REQUIRED_EFFECTS[DomainProfile.DEV])


def effect_requires_receipt(effect: EffectType, domain: DomainProfile) -> bool:
    """Check if an effect type requires a receipt in the given domain."""
    return effect in get_required_effects(domain)


# ---------------------------------------------------------------------------
# Sensitivity Levels
# ---------------------------------------------------------------------------


class SensitivityLevel(str, Enum):
    """Data sensitivity classification for observations."""

    PUBLIC = "public"           # Publicly available data
    INTERNAL = "internal"       # Internal but not sensitive
    CONFIDENTIAL = "confidential"  # Business confidential
    RESTRICTED = "restricted"   # PHI, PII, secrets, etc.


# ---------------------------------------------------------------------------
# ObservationReceipt
# ---------------------------------------------------------------------------


class ObservationReceipt(BaseReceipt):
    """
    Receipt for privileged observations (sensor access, file reads, etc.)

    Used when the organism accesses sensitive data sources. This covers
    inbound operations at the membrane that access privileged information.

    Examples:
    - Reading user files
    - Accessing camera or microphone
    - Screen capture
    - Querying sensitive APIs
    """

    receipt_type: Literal["ObservationReceipt"] = "ObservationReceipt"
    domain: str = Field(default=Domain.GOVERNANCE.value)

    # What was observed
    observation_id: str = Field(description="Unique ID for this observation")
    source_type: str = Field(
        description="Source: screen, camera, mic, file, api, clipboard, etc."
    )
    source_identifier: str = Field(
        description="Specific source (path, URL, device ID, etc.)"
    )

    # Effect classification
    effect_type: EffectType = Field(
        default=EffectType.READ,
        description="READ or OBSERVE"
    )
    sensitivity: SensitivityLevel = Field(
        default=SensitivityLevel.INTERNAL,
        description="Data sensitivity classification"
    )

    # Content summary (not raw content for privacy)
    content_hash: Optional[str] = Field(
        default=None,
        description="SHA-256 hash of observed content"
    )
    content_size_bytes: Optional[int] = Field(
        default=None,
        description="Size of observed content in bytes"
    )
    content_summary: Optional[str] = Field(
        default=None,
        description="Brief human-readable description of what was observed"
    )

    # Context
    session_id: Optional[str] = Field(
        default=None,
        description="Session this observation belongs to"
    )
    episode_id: Optional[str] = Field(
        default=None,
        description="Episode this observation belongs to"
    )
    triggered_by_action_id: Optional[str] = Field(
        default=None,
        description="Action that led to this observation"
    )

    # For streaming observations (video, audio)
    is_session_start: bool = Field(
        default=False,
        description="True if this starts a continuous observation session"
    )
    is_session_end: bool = Field(
        default=False,
        description="True if this ends a continuous observation session"
    )
    frame_count: Optional[int] = Field(
        default=None,
        description="Number of frames (for video observations)"
    )
    duration_ms: Optional[float] = Field(
        default=None,
        description="Duration in milliseconds (for temporal observations)"
    )

    # Privacy flags
    contains_pii: bool = Field(
        default=False,
        description="True if observation may contain personally identifiable information"
    )
    contains_phi: bool = Field(
        default=False,
        description="True if observation may contain protected health information"
    )


def create_observation_receipt(
    source_type: str,
    source_identifier: str,
    effect_type: EffectType = EffectType.READ,
    sensitivity: SensitivityLevel = SensitivityLevel.INTERNAL,
    content_hash: Optional[str] = None,
    content_size_bytes: Optional[int] = None,
    content_summary: Optional[str] = None,
    session_id: Optional[str] = None,
    episode_id: Optional[str] = None,
    triggered_by_action_id: Optional[str] = None,
    contains_pii: bool = False,
    contains_phi: bool = False,
    **kwargs,
) -> ObservationReceipt:
    """
    Create an ObservationReceipt for a privileged observation.

    Args:
        source_type: Type of source (screen, camera, mic, file, api, etc.)
        source_identifier: Specific identifier (path, URL, device ID)
        effect_type: READ or OBSERVE
        sensitivity: Data sensitivity level
        content_hash: SHA-256 hash of content (for verification)
        content_size_bytes: Size of content
        content_summary: Human-readable summary
        session_id: Session context
        episode_id: Episode context
        triggered_by_action_id: Action that triggered this observation
        contains_pii: Whether PII may be present
        contains_phi: Whether PHI may be present

    Returns:
        ObservationReceipt
    """
    observation_id = f"obs_{uuid.uuid4().hex[:16]}"
    receipt_id = f"obsr_{uuid.uuid4().hex[:16]}"

    return ObservationReceipt(
        receipt_id=receipt_id,
        observation_id=observation_id,
        source_type=source_type,
        source_identifier=source_identifier,
        effect_type=effect_type,
        sensitivity=sensitivity,
        content_hash=content_hash,
        content_size_bytes=content_size_bytes,
        content_summary=content_summary,
        session_id=session_id,
        episode_id=episode_id,
        triggered_by_action_id=triggered_by_action_id,
        contains_pii=contains_pii,
        contains_phi=contains_phi,
        **kwargs,
    )


# ---------------------------------------------------------------------------
# ObservationSessionReceipt
# ---------------------------------------------------------------------------


class ObservationSessionReceipt(BaseReceipt):
    """
    Receipt for continuous observation sessions (video, audio streams).

    One receipt covers an entire session rather than individual frames.
    Use this for long-running observations where individual frame receipts
    would be impractical.

    Examples:
    - Screen recording session
    - Camera monitoring session
    - Audio transcription session
    """

    receipt_type: Literal["ObservationSessionReceipt"] = "ObservationSessionReceipt"
    domain: str = Field(default=Domain.GOVERNANCE.value)

    # Session identification
    session_id: str = Field(description="Unique ID for this observation session")
    source_type: str = Field(
        description="Source type: screen_capture, camera, microphone, etc."
    )
    source_identifier: str = Field(
        default="",
        description="Specific source device or target"
    )

    # Timing
    started_at: datetime = Field(description="When the session started")
    ended_at: Optional[datetime] = Field(
        default=None,
        description="When the session ended (None if ongoing)"
    )
    duration_seconds: Optional[float] = Field(
        default=None,
        description="Total session duration in seconds"
    )

    # Aggregates
    frame_count: Optional[int] = Field(
        default=None,
        description="Number of frames captured (for video)"
    )
    sample_count: Optional[int] = Field(
        default=None,
        description="Number of samples captured (for audio)"
    )
    total_bytes: Optional[int] = Field(
        default=None,
        description="Total bytes of data observed"
    )

    # Sample hashes for verification
    sample_hashes: List[str] = Field(
        default_factory=list,
        description="Hashes of sample frames/segments for verification"
    )

    # Resolution/quality
    resolution: Optional[str] = Field(
        default=None,
        description="Resolution or quality: '1920x1080', '44100Hz', etc."
    )
    format: Optional[str] = Field(
        default=None,
        description="Data format: 'png', 'wav', 'mp4', etc."
    )

    # Context
    episode_id: Optional[str] = Field(
        default=None,
        description="Episode this session belongs to"
    )
    triggered_by_action_id: Optional[str] = Field(
        default=None,
        description="Action that initiated this session"
    )

    # Privacy
    contains_pii: bool = Field(
        default=False,
        description="True if session may contain PII"
    )
    contains_phi: bool = Field(
        default=False,
        description="True if session may contain PHI"
    )
    sensitivity: SensitivityLevel = Field(
        default=SensitivityLevel.INTERNAL,
        description="Overall sensitivity of session data"
    )


def create_observation_session_receipt(
    source_type: str,
    started_at: Optional[datetime] = None,
    source_identifier: str = "",
    ended_at: Optional[datetime] = None,
    frame_count: Optional[int] = None,
    sample_count: Optional[int] = None,
    resolution: Optional[str] = None,
    episode_id: Optional[str] = None,
    contains_pii: bool = False,
    contains_phi: bool = False,
    sensitivity: SensitivityLevel = SensitivityLevel.INTERNAL,
    **kwargs,
) -> ObservationSessionReceipt:
    """
    Create an ObservationSessionReceipt for a continuous observation.

    Args:
        source_type: Type of source (screen_capture, camera, microphone)
        started_at: Session start time (defaults to now)
        source_identifier: Specific device or target
        ended_at: Session end time (None if ongoing)
        frame_count: Number of frames (video)
        sample_count: Number of samples (audio)
        resolution: Resolution or quality string
        episode_id: Episode context
        contains_pii: Whether PII may be present
        contains_phi: Whether PHI may be present
        sensitivity: Data sensitivity level

    Returns:
        ObservationSessionReceipt
    """
    session_id = f"obs_session_{uuid.uuid4().hex[:16]}"
    receipt_id = f"ossr_{uuid.uuid4().hex[:16]}"

    if started_at is None:
        started_at = datetime.now(timezone.utc)

    duration_seconds = None
    if ended_at is not None:
        duration_seconds = (ended_at - started_at).total_seconds()

    return ObservationSessionReceipt(
        receipt_id=receipt_id,
        session_id=session_id,
        source_type=source_type,
        source_identifier=source_identifier,
        started_at=started_at,
        ended_at=ended_at,
        duration_seconds=duration_seconds,
        frame_count=frame_count,
        sample_count=sample_count,
        resolution=resolution,
        episode_id=episode_id,
        contains_pii=contains_pii,
        contains_phi=contains_phi,
        sensitivity=sensitivity,
        **kwargs,
    )


# ---------------------------------------------------------------------------
# Invariant Check
# ---------------------------------------------------------------------------


class MembraneOperation(BaseModel):
    """Represents an operation crossing the membrane."""

    operation_id: str = Field(description="Unique ID for this operation")
    effect_type: EffectType = Field(description="Type of effect")
    target: str = Field(description="Target of the operation (path, URL, etc.)")
    timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    metadata: Dict[str, Any] = Field(default_factory=dict)


class InvariantCheckResult(BaseModel):
    """Result of checking the 'No Action Without Receipt' invariant."""

    passed: bool = Field(description="Whether the invariant check passed")
    reason: str = Field(description="Human-readable explanation")
    violation_type: Optional[str] = Field(
        default=None,
        description="Type of violation if failed"
    )
    criticality: Optional[str] = Field(
        default=None,
        description="Criticality level if failed"
    )
    receipt_ids: List[str] = Field(
        default_factory=list,
        description="Receipt IDs that cover this operation"
    )


def check_no_action_without_receipt(
    operation: MembraneOperation,
    domain: DomainProfile,
    receipts: List[BaseReceipt],
) -> InvariantCheckResult:
    """
    Check the constitutional invariant: No external side effect without a receipt.

    Args:
        operation: The operation that crossed the membrane
        domain: Current domain profile (dev, clinical, etc.)
        receipts: Receipts that may cover this operation

    Returns:
        InvariantCheckResult with pass/fail and details
    """
    required_effects = get_required_effects(domain)

    # Check if this operation's effect type requires a receipt
    if operation.effect_type not in required_effects:
        return InvariantCheckResult(
            passed=True,
            reason=f"Effect {operation.effect_type.value} not required in {domain.value}"
        )

    # Check that at least one valid receipt exists
    matching_receipts = []
    for r in receipts:
        # Check if receipt covers this operation (simplified matching)
        if hasattr(r, 'operation_id') and r.operation_id == operation.operation_id:
            matching_receipts.append(r)
        elif hasattr(r, 'target') and r.target == operation.target:
            matching_receipts.append(r)
        elif hasattr(r, 'source_identifier') and r.source_identifier == operation.target:
            matching_receipts.append(r)

    if not matching_receipts:
        return InvariantCheckResult(
            passed=False,
            reason=f"No receipt found for {operation.effect_type.value} operation on {operation.target}",
            violation_type="missing_receipt",
            criticality="constitutional",
        )

    return InvariantCheckResult(
        passed=True,
        reason=f"Receipt {matching_receipts[0].receipt_id} covers operation",
        receipt_ids=[r.receipt_id for r in matching_receipts],
    )


# ---------------------------------------------------------------------------
# Exports
# ---------------------------------------------------------------------------

__all__ = [
    # Enums
    "EffectType",
    "DomainProfile",
    "SensitivityLevel",
    # Configuration
    "DOMAIN_REQUIRED_EFFECTS",
    "get_required_effects",
    "effect_requires_receipt",
    # Receipts
    "ObservationReceipt",
    "create_observation_receipt",
    "ObservationSessionReceipt",
    "create_observation_session_receipt",
    # Invariant checking
    "MembraneOperation",
    "InvariantCheckResult",
    "check_no_action_without_receipt",
]
