"""
Metabolic Ledger - RIT (Resource Intelligence Token) Budget Tracking

Manages RIT balances for TBR gate compliance. Each actor/tenant has a
RIT budget that is debited when decisions are made.

The metabolic ledger enforces:
1. Budget limits - actors cannot exceed their RIT balance
2. Cost estimation - decisions have RIT costs based on complexity
3. Debit tracking - successful decisions debit RIT from balance

Usage:
    from receipts.metabolic_ledger import get_metabolic_ledger

    ledger = get_metabolic_ledger()

    # Get current balance
    balance = ledger.get_rit_balance(actor="PA-2024-001")

    # Estimate cost for a decision
    cost = ledger.estimate_rit_cost(
        suggestion={"assessment": "...", "plan": [...]},
        proof_tier="regulated",
    )

    # Debit after successful decision
    ledger.debit_rit(actor="PA-2024-001", cost=cost, receipt_id="...")
"""

from __future__ import annotations

import logging
import os
import threading
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

logger = logging.getLogger(__name__)

# Default configuration
DEFAULT_RIT_BALANCE = 100  # Default starting balance
DEFAULT_RIT_COST = 10  # Default decision cost

# Proof tier cost multipliers
PROOF_TIER_MULTIPLIERS = {
    "core": 1.0,      # Basic decisions
    "regulated": 1.5,  # PA decisions (default for clinical)
    "court": 2.0,      # Court-grade evidence (highest)
}

# Decision kind modifiers (shadow/test don't cost RIT)
DECISION_KIND_MODIFIERS = {
    "production": 1.0,
    "shadow": 0.0,      # No cost for shadow mode
    "dry_run": 0.0,     # No cost for dry run
    "simulation": 0.0,  # No cost for simulation
    "test": 0.0,        # No cost for test
}

# Complexity factor thresholds
COMPLEXITY_THRESHOLDS = {
    "simple": 5,    # < 5 plan items = simple
    "moderate": 10, # 5-10 plan items = moderate
    "complex": 20,  # 10-20 plan items = complex
    # > 20 = highly complex
}


@dataclass
class RITTransaction:
    """Record of a RIT debit/credit."""
    transaction_id: str
    actor: str
    amount: float  # Negative for debit, positive for credit
    balance_after: float
    receipt_id: Optional[str]
    reason: str
    metadata: Dict[str, Any]
    ts: datetime = field(default_factory=lambda: datetime.now(timezone.utc))


@dataclass
class MetabolicSnapshot:
    """Point-in-time snapshot of metabolic state."""
    snapshot_id: str
    ts: datetime
    temperature: float  # System temperature (0.0 - 1.0)
    total_rit_issued: float
    total_rit_consumed: float


class MetabolicLedger:
    """
    RIT (Resource Intelligence Token) budget ledger.

    Thread-safe ledger tracking RIT balances per actor.
    """

    def __init__(
        self,
        *,
        default_balance: float = DEFAULT_RIT_BALANCE,
        config: Optional[Dict[str, Any]] = None,
    ) -> None:
        """
        Initialize metabolic ledger.

        Args:
            default_balance: Default RIT balance for new actors
            config: Optional configuration overrides
        """
        self._default_balance = default_balance
        self._config = config or {}
        self._balances: Dict[str, float] = {}
        self._transactions: List[RITTransaction] = []
        self._lock = threading.Lock()

        # Load config from environment if available
        self._load_env_config()

    def _load_env_config(self) -> None:
        """Load configuration from environment variables."""
        env_balance = os.getenv("LOOM_DEFAULT_RIT_BALANCE")
        if env_balance:
            try:
                self._default_balance = float(env_balance)
            except ValueError:
                pass

    def get_rit_balance(self, actor: str) -> float:
        """
        Get current RIT balance for actor.

        Args:
            actor: Actor identifier (case_id, tenant, etc.)

        Returns:
            Current RIT balance
        """
        with self._lock:
            if actor not in self._balances:
                # Initialize with default balance
                self._balances[actor] = self._get_initial_balance(actor)
            return self._balances[actor]

    def _get_initial_balance(self, actor: str) -> float:
        """Get initial balance for actor based on config."""
        # Check actor-specific config
        actor_config = (self._config.get("actors") or {}).get(actor, {})
        if "initial_balance" in actor_config:
            return float(actor_config["initial_balance"])

        # Check role-based config
        role = actor_config.get("role", "default")
        role_config = (self._config.get("roles") or {}).get(role, {})
        if "initial_balance" in role_config:
            return float(role_config["initial_balance"])

        return self._default_balance

    def estimate_rit_cost(
        self,
        suggestion: Dict[str, Any],
        proof_tier: str = "regulated",
        decision_kind: str = "production",
        priority: str = "normal",
    ) -> float:
        """
        Estimate RIT cost for a decision.

        Cost is computed as:
        base_cost * proof_tier_multiplier * complexity_factor * decision_kind_modifier

        Args:
            suggestion: Clinical suggestion to evaluate
            proof_tier: Proof tier (core|regulated|court)
            decision_kind: Decision kind (production|shadow|...)
            priority: Priority level (normal|critical_safety)

        Returns:
            Estimated RIT cost (0 if shadow/test mode)
        """
        # Decision kind modifier (shadow/test = 0 cost)
        kind_modifier = DECISION_KIND_MODIFIERS.get(decision_kind, 1.0)
        if kind_modifier == 0.0:
            return 0.0

        # Base cost from config or default
        base_cost = float(self._config.get("base_cost", DEFAULT_RIT_COST))

        # Proof tier multiplier
        tier_mult = PROOF_TIER_MULTIPLIERS.get(proof_tier, 1.0)

        # Complexity factor based on suggestion
        complexity = self._compute_complexity(suggestion)

        # Priority modifier (critical_safety gets discount)
        priority_mult = 0.5 if priority == "critical_safety" else 1.0

        cost = base_cost * tier_mult * complexity * priority_mult
        return round(cost, 2)

    def _compute_complexity(self, suggestion: Dict[str, Any]) -> float:
        """Compute complexity factor from suggestion content."""
        # Count plan items
        plan = suggestion.get("plan", [])
        if isinstance(plan, list):
            plan_count = len(plan)
        else:
            plan_count = 1

        # Determine complexity tier
        if plan_count < COMPLEXITY_THRESHOLDS["simple"]:
            return 1.0  # Simple
        elif plan_count < COMPLEXITY_THRESHOLDS["moderate"]:
            return 1.2  # Moderate
        elif plan_count < COMPLEXITY_THRESHOLDS["complex"]:
            return 1.5  # Complex
        else:
            return 2.0  # Highly complex

    def debit_rit(
        self,
        actor: str,
        cost: float,
        receipt_id: Optional[str] = None,
        reason: str = "decision",
        metadata: Optional[Dict[str, Any]] = None,
    ) -> RITTransaction:
        """
        Debit RIT from actor's balance.

        Args:
            actor: Actor identifier
            cost: Amount to debit
            receipt_id: Associated receipt ID
            reason: Reason for debit
            metadata: Additional metadata

        Returns:
            Transaction record
        """
        with self._lock:
            current = self.get_rit_balance(actor)
            new_balance = max(0.0, current - cost)  # Floor at 0
            self._balances[actor] = new_balance

            txn = RITTransaction(
                transaction_id=f"rit-{uuid.uuid4().hex[:12]}",
                actor=actor,
                amount=-cost,
                balance_after=new_balance,
                receipt_id=receipt_id,
                reason=reason,
                metadata=metadata or {},
            )
            self._transactions.append(txn)

            logger.debug(
                "RIT debit: actor=%s cost=%.2f balance=%.2f receipt=%s",
                actor, cost, new_balance, receipt_id
            )

            return txn

    def credit_rit(
        self,
        actor: str,
        amount: float,
        reason: str = "refill",
        metadata: Optional[Dict[str, Any]] = None,
    ) -> RITTransaction:
        """
        Credit RIT to actor's balance.

        Args:
            actor: Actor identifier
            amount: Amount to credit
            reason: Reason for credit
            metadata: Additional metadata

        Returns:
            Transaction record
        """
        with self._lock:
            current = self.get_rit_balance(actor)
            new_balance = current + amount
            self._balances[actor] = new_balance

            txn = RITTransaction(
                transaction_id=f"rit-{uuid.uuid4().hex[:12]}",
                actor=actor,
                amount=amount,
                balance_after=new_balance,
                receipt_id=None,
                reason=reason,
                metadata=metadata or {},
            )
            self._transactions.append(txn)

            logger.debug(
                "RIT credit: actor=%s amount=%.2f balance=%.2f",
                actor, amount, new_balance
            )

            return txn

    def can_afford(self, actor: str, cost: float, allow_overdraft: bool = False) -> bool:
        """
        Check if actor can afford a cost.

        Args:
            actor: Actor identifier
            cost: Cost to check
            allow_overdraft: Allow overdraft for critical_safety

        Returns:
            True if actor can afford cost
        """
        if allow_overdraft:
            return True
        return self.get_rit_balance(actor) >= cost

    def get_transactions(
        self,
        actor: Optional[str] = None,
        limit: int = 100,
    ) -> List[RITTransaction]:
        """Get recent transactions, optionally filtered by actor."""
        with self._lock:
            if actor:
                txns = [t for t in self._transactions if t.actor == actor]
            else:
                txns = list(self._transactions)
            return sorted(txns, key=lambda t: t.ts, reverse=True)[:limit]

    def get_snapshot(self) -> MetabolicSnapshot:
        """Get current metabolic snapshot."""
        with self._lock:
            total_issued = sum(
                t.amount for t in self._transactions if t.amount > 0
            )
            total_consumed = sum(
                -t.amount for t in self._transactions if t.amount < 0
            )

            # Temperature: ratio of consumed/issued (0 = cold, 1 = hot)
            if total_issued > 0:
                temperature = min(1.0, total_consumed / total_issued)
            else:
                temperature = 0.0

            return MetabolicSnapshot(
                snapshot_id=f"meta-{uuid.uuid4().hex[:8]}",
                ts=datetime.now(timezone.utc),
                temperature=temperature,
                total_rit_issued=total_issued,
                total_rit_consumed=total_consumed,
            )

    def clear(self) -> None:
        """Clear all balances and transactions (for testing)."""
        with self._lock:
            self._balances.clear()
            self._transactions.clear()


# Singleton instance
_default_ledger: Optional[MetabolicLedger] = None
_ledger_lock = threading.Lock()


def get_metabolic_ledger() -> MetabolicLedger:
    """Get or create default metabolic ledger."""
    global _default_ledger
    with _ledger_lock:
        if _default_ledger is None:
            _default_ledger = MetabolicLedger()
        return _default_ledger


def reset_metabolic_ledger() -> None:
    """Reset the default ledger (for testing)."""
    global _default_ledger
    with _ledger_lock:
        if _default_ledger is not None:
            _default_ledger.clear()
        _default_ledger = None


# Convenience functions for Guardian service integration
def get_rit_balance_for_decision(case_id: Optional[str]) -> int:
    """
    Get RIT balance for a decision.

    Args:
        case_id: Case/actor identifier

    Returns:
        Current RIT balance (as int for TBR gate)
    """
    if not case_id:
        return DEFAULT_RIT_BALANCE

    ledger = get_metabolic_ledger()
    return int(ledger.get_rit_balance(case_id))


def estimate_rit_cost_for_decision(
    suggestion: Dict[str, Any],
    proof_tier: str = "regulated",
    decision_kind: str = "production",
    priority: str = "normal",
) -> int:
    """
    Estimate RIT cost for a decision.

    Args:
        suggestion: Clinical suggestion
        proof_tier: Proof tier
        decision_kind: Decision kind
        priority: Priority level

    Returns:
        Estimated RIT cost (as int for TBR gate)
    """
    ledger = get_metabolic_ledger()
    cost = ledger.estimate_rit_cost(
        suggestion=suggestion,
        proof_tier=proof_tier,
        decision_kind=decision_kind,
        priority=priority,
    )
    return int(cost)


def debit_rit_for_decision(
    case_id: Optional[str],
    cost: int,
    receipt_id: Optional[str] = None,
) -> None:
    """
    Debit RIT after a successful decision.

    Args:
        case_id: Case/actor identifier
        cost: Cost to debit
        receipt_id: Associated receipt ID
    """
    if not case_id or cost <= 0:
        return

    ledger = get_metabolic_ledger()
    ledger.debit_rit(
        actor=case_id,
        cost=float(cost),
        receipt_id=receipt_id,
        reason="guardian_decision",
    )
