"""
Escrow Engine – Holds funds until service delivery is confirmed.

States: PENDING → DELIVERED → RELEASED
        PENDING → EXPIRED → REFUNDED
        PENDING/DELIVERED → DISPUTED → REFUNDED
"""
from __future__ import annotations
import json
import os
import time
import uuid
from dataclasses import dataclass, asdict
from enum import Enum


class EscrowState(str, Enum):
    PENDING = "pending"
    DELIVERED = "delivered"
    RELEASED = "released"
    DISPUTED = "disputed"
    REFUNDED = "refunded"
    EXPIRED = "expired"


# Terminal states that cannot transition
_TERMINAL_STATES = {EscrowState.RELEASED, EscrowState.REFUNDED, EscrowState.EXPIRED}


@dataclass
class EscrowConfig:
    """Configuration for escrow behavior."""
    default_timeout_seconds: int = 3600     # 1 hour
    auto_release_on_delivery: bool = False  # Skip manual release step


@dataclass
class EscrowRecord:
    """A single escrow transaction."""
    escrow_id: str
    buyer_wallet: str
    provider_wallet: str
    amount_usd: float
    fee_usd: float
    service_amount_usd: float
    service_type: str
    state: str
    created_at: float
    timeout_at: float
    delivered_at: float = 0.0
    released_at: float = 0.0
    disputed_at: float = 0.0
    refunded_at: float = 0.0
    dispute_reason: str = ""
    currency: str = "USDC"
    transaction_id: str = ""


class EscrowEngine:
    """
    Manages escrow lifecycle for agent-to-agent payments.

    Usage:
        engine = EscrowEngine()
        record = engine.create_escrow(buyer, provider, 0.50, 0.005, 0.495, "translation")
        engine.confirm_delivery(escrow_id, provider_wallet)
        engine.release(escrow_id)
    """

    def __init__(self, config: EscrowConfig | None = None, storage_path: str = ""):
        self.config = config or EscrowConfig()
        if not storage_path:
            storage_path = os.path.join(
                os.path.dirname(__file__), "..", "escrow.json"
            )
        self._storage_path = os.path.abspath(storage_path)
        self._escrows: dict[str, dict] = self._load()

    def _load(self) -> dict:
        if os.path.exists(self._storage_path):
            with open(self._storage_path, "r") as f:
                return json.load(f)
        return {}

    def _save(self):
        with open(self._storage_path, "w") as f:
            json.dump(self._escrows, f, indent=2, ensure_ascii=False)

    def create_escrow(
        self,
        buyer_wallet: str,
        provider_wallet: str,
        amount_usd: float,
        fee_usd: float,
        service_amount_usd: float,
        service_type: str,
        timeout_seconds: int | None = None,
        transaction_id: str = "",
    ) -> EscrowRecord:
        """Create a new escrow in PENDING state."""
        now = time.time()
        timeout = timeout_seconds or self.config.default_timeout_seconds
        escrow_id = f"escrow_{uuid.uuid4().hex[:12]}"

        record = EscrowRecord(
            escrow_id=escrow_id,
            buyer_wallet=buyer_wallet,
            provider_wallet=provider_wallet,
            amount_usd=amount_usd,
            fee_usd=fee_usd,
            service_amount_usd=service_amount_usd,
            service_type=service_type,
            state=EscrowState.PENDING.value,
            created_at=now,
            timeout_at=now + timeout,
            transaction_id=transaction_id,
        )
        self._escrows[escrow_id] = asdict(record)
        self._save()
        return record

    def confirm_delivery(self, escrow_id: str, provider_wallet: str) -> EscrowRecord:
        """Provider confirms service delivery. PENDING → DELIVERED."""
        record = self._get_and_validate(escrow_id, [EscrowState.PENDING])
        if record["provider_wallet"] != provider_wallet:
            raise PermissionError("Only the provider can confirm delivery")

        record["state"] = EscrowState.DELIVERED.value
        record["delivered_at"] = time.time()

        if self.config.auto_release_on_delivery:
            record["state"] = EscrowState.RELEASED.value
            record["released_at"] = time.time()

        self._save()
        return self._to_record(record)

    def release(self, escrow_id: str) -> EscrowRecord:
        """Release funds to provider. PENDING/DELIVERED → RELEASED."""
        record = self._get_and_validate(
            escrow_id, [EscrowState.DELIVERED, EscrowState.PENDING]
        )
        record["state"] = EscrowState.RELEASED.value
        record["released_at"] = time.time()
        self._save()
        return self._to_record(record)

    def dispute(self, escrow_id: str, buyer_wallet: str, reason: str = "") -> EscrowRecord:
        """Buyer disputes delivery. PENDING/DELIVERED → DISPUTED."""
        record = self._get_and_validate(
            escrow_id, [EscrowState.PENDING, EscrowState.DELIVERED]
        )
        if record["buyer_wallet"] != buyer_wallet:
            raise PermissionError("Only the buyer can dispute")

        record["state"] = EscrowState.DISPUTED.value
        record["disputed_at"] = time.time()
        record["dispute_reason"] = reason
        self._save()
        return self._to_record(record)

    def refund(self, escrow_id: str) -> EscrowRecord:
        """Refund funds to buyer. DISPUTED/PENDING/EXPIRED → REFUNDED."""
        record = self._get_and_validate(
            escrow_id,
            [EscrowState.DISPUTED, EscrowState.PENDING, EscrowState.EXPIRED],
        )
        record["state"] = EscrowState.REFUNDED.value
        record["refunded_at"] = time.time()
        self._save()
        return self._to_record(record)

    def check_expired(self) -> list[EscrowRecord]:
        """Check for expired escrows. Returns newly expired records."""
        now = time.time()
        expired = []
        for rec in self._escrows.values():
            if rec["state"] == EscrowState.PENDING.value and now > rec["timeout_at"]:
                rec["state"] = EscrowState.EXPIRED.value
                expired.append(self._to_record(rec))
        if expired:
            self._save()
        return expired

    def get_escrow(self, escrow_id: str) -> EscrowRecord | None:
        """Get escrow by ID."""
        if escrow_id in self._escrows:
            return self._to_record(self._escrows[escrow_id])
        return None

    def find_by_buyer(self, buyer_wallet: str) -> list[EscrowRecord]:
        """Find all escrows for a buyer."""
        return [
            self._to_record(rec) for rec in self._escrows.values()
            if rec["buyer_wallet"] == buyer_wallet
        ]

    def find_by_provider(self, provider_wallet: str) -> list[EscrowRecord]:
        """Find all escrows for a provider."""
        return [
            self._to_record(rec) for rec in self._escrows.values()
            if rec["provider_wallet"] == provider_wallet
        ]

    def _get_and_validate(self, escrow_id: str,
                          expected_states: list[EscrowState]) -> dict:
        """Get escrow and validate state."""
        if escrow_id not in self._escrows:
            raise ValueError(f"Escrow not found: {escrow_id}")
        record = self._escrows[escrow_id]
        valid = [s.value for s in expected_states]
        if record["state"] not in valid:
            raise ValueError(
                f"Escrow {escrow_id} is '{record['state']}', expected one of {valid}"
            )
        return record

    @staticmethod
    def _to_record(d: dict) -> EscrowRecord:
        return EscrowRecord(**{
            k: v for k, v in d.items()
            if k in EscrowRecord.__dataclass_fields__
        })
