"""Tests for the Escrow Engine."""
import time
import pytest
from swarmpay.escrow import EscrowEngine, EscrowConfig, EscrowRecord, EscrowState

BUYER = "0x1111111111111111111111111111111111111111"
PROVIDER = "0x2222222222222222222222222222222222222222"
OTHER = "0x3333333333333333333333333333333333333333"


@pytest.fixture
def engine(tmp_path):
    return EscrowEngine(
        config=EscrowConfig(default_timeout_seconds=3600),
        storage_path=str(tmp_path / "escrow.json"),
    )


@pytest.fixture
def escrow(engine):
    """Create a standard test escrow."""
    return engine.create_escrow(
        buyer_wallet=BUYER,
        provider_wallet=PROVIDER,
        amount_usd=10.0,
        fee_usd=0.1,
        service_amount_usd=9.9,
        service_type="translation",
    )


# ── Create Escrow ───────────────────────────────────────────

def test_create_escrow(escrow):
    assert escrow.state == EscrowState.PENDING.value
    assert escrow.buyer_wallet == BUYER
    assert escrow.provider_wallet == PROVIDER
    assert escrow.amount_usd == 10.0
    assert escrow.fee_usd == 0.1
    assert escrow.service_amount_usd == 9.9
    assert escrow.service_type == "translation"
    assert escrow.escrow_id.startswith("escrow_")


def test_create_escrow_custom_timeout(engine):
    escrow = engine.create_escrow(
        buyer_wallet=BUYER, provider_wallet=PROVIDER,
        amount_usd=5.0, fee_usd=0.05, service_amount_usd=4.95,
        service_type="analysis", timeout_seconds=60,
    )
    assert escrow.timeout_at == pytest.approx(escrow.created_at + 60, abs=1)


# ── Confirm Delivery ───────────────────────────────────────

def test_confirm_delivery(engine, escrow):
    result = engine.confirm_delivery(escrow.escrow_id, PROVIDER)
    assert result.state == EscrowState.DELIVERED.value
    assert result.delivered_at > 0


def test_confirm_delivery_wrong_provider(engine, escrow):
    with pytest.raises(PermissionError):
        engine.confirm_delivery(escrow.escrow_id, OTHER)


def test_confirm_delivery_wrong_state(engine, escrow):
    engine.release(escrow.escrow_id)  # Move to RELEASED
    with pytest.raises(ValueError):
        engine.confirm_delivery(escrow.escrow_id, PROVIDER)


# ── Release ─────────────────────────────────────────────────

def test_release_from_delivered(engine, escrow):
    engine.confirm_delivery(escrow.escrow_id, PROVIDER)
    result = engine.release(escrow.escrow_id)
    assert result.state == EscrowState.RELEASED.value
    assert result.released_at > 0


def test_release_from_pending(engine, escrow):
    result = engine.release(escrow.escrow_id)
    assert result.state == EscrowState.RELEASED.value


def test_release_wrong_state(engine, escrow):
    engine.release(escrow.escrow_id)  # RELEASED
    with pytest.raises(ValueError):
        engine.release(escrow.escrow_id)  # Already released


# ── Dispute ─────────────────────────────────────────────────

def test_dispute_from_pending(engine, escrow):
    result = engine.dispute(escrow.escrow_id, BUYER, "Service not received")
    assert result.state == EscrowState.DISPUTED.value
    assert result.dispute_reason == "Service not received"


def test_dispute_from_delivered(engine, escrow):
    engine.confirm_delivery(escrow.escrow_id, PROVIDER)
    result = engine.dispute(escrow.escrow_id, BUYER, "Bad quality")
    assert result.state == EscrowState.DISPUTED.value


def test_dispute_wrong_buyer(engine, escrow):
    with pytest.raises(PermissionError):
        engine.dispute(escrow.escrow_id, OTHER, "Not my escrow")


# ── Refund ──────────────────────────────────────────────────

def test_refund_from_disputed(engine, escrow):
    engine.dispute(escrow.escrow_id, BUYER)
    result = engine.refund(escrow.escrow_id)
    assert result.state == EscrowState.REFUNDED.value
    assert result.refunded_at > 0


def test_refund_from_pending(engine, escrow):
    result = engine.refund(escrow.escrow_id)
    assert result.state == EscrowState.REFUNDED.value


def test_refund_wrong_state(engine, escrow):
    engine.release(escrow.escrow_id)  # RELEASED
    with pytest.raises(ValueError):
        engine.refund(escrow.escrow_id)


# ── Timeout / Expiration ────────────────────────────────────

def test_check_expired(tmp_path):
    engine = EscrowEngine(
        config=EscrowConfig(default_timeout_seconds=0),  # immediate timeout
        storage_path=str(tmp_path / "escrow.json"),
    )
    escrow = engine.create_escrow(
        buyer_wallet=BUYER, provider_wallet=PROVIDER,
        amount_usd=5.0, fee_usd=0.05, service_amount_usd=4.95,
        service_type="test", timeout_seconds=0,
    )
    time.sleep(0.01)  # Ensure time passes
    expired = engine.check_expired()
    assert len(expired) == 1
    assert expired[0].state == EscrowState.EXPIRED.value


def test_check_expired_ignores_released(engine, escrow):
    engine.release(escrow.escrow_id)
    expired = engine.check_expired()
    assert len(expired) == 0


def test_auto_release_on_delivery(tmp_path):
    engine = EscrowEngine(
        config=EscrowConfig(auto_release_on_delivery=True),
        storage_path=str(tmp_path / "escrow.json"),
    )
    escrow = engine.create_escrow(
        buyer_wallet=BUYER, provider_wallet=PROVIDER,
        amount_usd=5.0, fee_usd=0.05, service_amount_usd=4.95,
        service_type="test",
    )
    result = engine.confirm_delivery(escrow.escrow_id, PROVIDER)
    assert result.state == EscrowState.RELEASED.value


# ── Query Methods ───────────────────────────────────────────

def test_get_escrow_by_id(engine, escrow):
    found = engine.get_escrow(escrow.escrow_id)
    assert found is not None
    assert found.escrow_id == escrow.escrow_id


def test_get_escrow_not_found(engine):
    assert engine.get_escrow("nonexistent") is None


def test_find_by_buyer(engine, escrow):
    results = engine.find_by_buyer(BUYER)
    assert len(results) == 1
    assert results[0].buyer_wallet == BUYER


def test_find_by_provider(engine, escrow):
    results = engine.find_by_provider(PROVIDER)
    assert len(results) == 1
    assert results[0].provider_wallet == PROVIDER


# ── Persistence ─────────────────────────────────────────────

def test_escrow_persistence(tmp_path):
    path = str(tmp_path / "escrow.json")
    engine1 = EscrowEngine(storage_path=path)
    escrow = engine1.create_escrow(
        buyer_wallet=BUYER, provider_wallet=PROVIDER,
        amount_usd=5.0, fee_usd=0.05, service_amount_usd=4.95,
        service_type="test",
    )
    # Reload
    engine2 = EscrowEngine(storage_path=path)
    found = engine2.get_escrow(escrow.escrow_id)
    assert found is not None
    assert found.amount_usd == 5.0


def test_multiple_escrows(engine):
    e1 = engine.create_escrow(
        buyer_wallet=BUYER, provider_wallet=PROVIDER,
        amount_usd=5.0, fee_usd=0.05, service_amount_usd=4.95,
        service_type="test1",
    )
    e2 = engine.create_escrow(
        buyer_wallet=BUYER, provider_wallet=OTHER,
        amount_usd=10.0, fee_usd=0.1, service_amount_usd=9.9,
        service_type="test2",
    )
    assert e1.escrow_id != e2.escrow_id
    assert len(engine.find_by_buyer(BUYER)) == 2


# ── Terminal States ─────────────────────────────────────────

def test_terminal_states_immutable(engine, escrow):
    engine.release(escrow.escrow_id)
    # Cannot transition from RELEASED
    with pytest.raises(ValueError):
        engine.dispute(escrow.escrow_id, BUYER)
    with pytest.raises(ValueError):
        engine.refund(escrow.escrow_id)


def test_escrow_state_values():
    assert EscrowState.PENDING.value == "pending"
    assert EscrowState.DELIVERED.value == "delivered"
    assert EscrowState.RELEASED.value == "released"
    assert EscrowState.DISPUTED.value == "disputed"
    assert EscrowState.REFUNDED.value == "refunded"
    assert EscrowState.EXPIRED.value == "expired"
