"""Phase 7.6 — Wallet Engine tests."""
from __future__ import annotations

import pytest

from app.events.publisher import get_event_publisher
from app.events.types import LoyaltyEventType
from app.tests.conftest import TENANT_A, tenant_headers


async def _open_wallet(client, *, suffix: str, currency: str = "IRR") -> dict:
    program = await client.post(
        "/api/v1/programs",
        json={"code": f"wal{suffix}", "name": f"Wallet {suffix}"},
        headers=tenant_headers(TENANT_A),
    )
    assert program.status_code == 201, program.text
    program_id = program.json()["id"]

    member = await client.post(
        "/api/v1/members",
        json={"program_id": program_id, "display_name": f"Member {suffix}", "enroll": True},
        headers=tenant_headers(TENANT_A),
    )
    assert member.status_code == 201, member.text
    member_id = member.json()["id"]

    wallet = await client.post(
        "/api/v1/wallets",
        json={
            "program_id": program_id,
            "member_id": member_id,
            "currency_code": currency,
        },
        headers=tenant_headers(TENANT_A),
    )
    assert wallet.status_code == 201, wallet.text
    return {
        "program_id": program_id,
        "member_id": member_id,
        "wallet_id": wallet.json()["id"],
        "account_number": wallet.json()["account_number"],
    }


@pytest.mark.asyncio
async def test_open_credit_and_balance(client):
    ctx = await _open_wallet(client, suffix="oc1")

    credit = await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/credit",
        json={"amount": 1000, "reason": "top-up", "idempotency_key": "c1"},
        headers=tenant_headers(TENANT_A),
    )
    assert credit.status_code == 200, credit.text
    assert credit.json()["balance_after"] == 1000
    assert credit.json()["entry_type"] == "credit"

    again = await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/credit",
        json={"amount": 1000, "reason": "top-up", "idempotency_key": "c1"},
        headers=tenant_headers(TENANT_A),
    )
    assert again.status_code == 200
    assert again.json()["id"] == credit.json()["id"]

    balance = await client.get(
        f"/api/v1/wallets/{ctx['wallet_id']}/balance",
        headers=tenant_headers(TENANT_A),
    )
    assert balance.status_code == 200
    assert balance.json()["balance"] == 1000
    assert balance.json()["currency_code"] == "IRR"

    events = {e.event_type for e in get_event_publisher().published}
    assert LoyaltyEventType.WALLET_OPENED.value in events
    assert LoyaltyEventType.WALLET_CREDITED.value in events


@pytest.mark.asyncio
async def test_debit_insufficient_funds_rejected(client):
    ctx = await _open_wallet(client, suffix="db1")
    await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/credit",
        json={"amount": 200},
        headers=tenant_headers(TENANT_A),
    )

    debit = await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/debit",
        json={"amount": 50, "reason": "purchase"},
        headers=tenant_headers(TENANT_A),
    )
    assert debit.status_code == 200, debit.text
    assert debit.json()["balance_after"] == 150

    over = await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/debit",
        json={"amount": 1000},
        headers=tenant_headers(TENANT_A),
    )
    assert over.status_code == 409
    assert over.json()["error"]["code"] == "wallet_insufficient_funds"

    events = {e.event_type for e in get_event_publisher().published}
    assert LoyaltyEventType.WALLET_DEBITED.value in events


@pytest.mark.asyncio
async def test_adjust_and_ledger_list(client):
    ctx = await _open_wallet(client, suffix="adj1")
    await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/credit",
        json={"amount": 500},
        headers=tenant_headers(TENANT_A),
    )
    adjust = await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/adjust",
        json={"amount": -50, "reason": "correction"},
        headers=tenant_headers(TENANT_A),
    )
    assert adjust.status_code == 200, adjust.text
    assert adjust.json()["balance_after"] == 450

    zero_adjust = await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/adjust",
        json={"amount": 0, "reason": "noop"},
        headers=tenant_headers(TENANT_A),
    )
    assert zero_adjust.status_code == 422
    assert zero_adjust.json()["error"]["code"] == "invalid_wallet_adjust_amount"

    page = await client.get(
        f"/api/v1/wallets/{ctx['wallet_id']}/ledger",
        params={"limit": 10},
        headers=tenant_headers(TENANT_A),
    )
    assert page.status_code == 200
    body = page.json()
    assert body["total"] >= 2
    assert len(body["items"]) >= 2

    events = {e.event_type for e in get_event_publisher().published}
    assert LoyaltyEventType.WALLET_ADJUSTED.value in events


@pytest.mark.asyncio
async def test_transfer_between_wallets_same_currency(client):
    source = await _open_wallet(client, suffix="tr1a")
    target = await _open_wallet(client, suffix="tr1b")

    await client.post(
        f"/api/v1/wallets/{source['wallet_id']}/credit",
        json={"amount": 300},
        headers=tenant_headers(TENANT_A),
    )

    transfer = await client.post(
        f"/api/v1/wallets/{source['wallet_id']}/transfer",
        json={"target_wallet_id": target["wallet_id"], "amount": 120, "reason": "p2p"},
        headers=tenant_headers(TENANT_A),
    )
    assert transfer.status_code == 200, transfer.text
    body = transfer.json()
    assert body["source_entry"]["entry_type"] == "transfer_out"
    assert body["source_entry"]["amount"] == -120
    assert body["source_entry"]["balance_after"] == 180
    assert body["target_entry"]["entry_type"] == "transfer_in"
    assert body["target_entry"]["amount"] == 120
    assert body["target_entry"]["balance_after"] == 120

    source_balance = await client.get(
        f"/api/v1/wallets/{source['wallet_id']}/balance",
        headers=tenant_headers(TENANT_A),
    )
    assert source_balance.json()["balance"] == 180

    target_balance = await client.get(
        f"/api/v1/wallets/{target['wallet_id']}/balance",
        headers=tenant_headers(TENANT_A),
    )
    assert target_balance.json()["balance"] == 120

    events = {e.event_type for e in get_event_publisher().published}
    assert LoyaltyEventType.WALLET_TRANSFERRED.value in events


@pytest.mark.asyncio
async def test_transfer_rejects_different_currency(client):
    source = await _open_wallet(client, suffix="cur1a", currency="IRR")
    target = await _open_wallet(client, suffix="cur1b", currency="USD")
    await client.post(
        f"/api/v1/wallets/{source['wallet_id']}/credit",
        json={"amount": 500},
        headers=tenant_headers(TENANT_A),
    )

    resp = await client.post(
        f"/api/v1/wallets/{source['wallet_id']}/transfer",
        json={"target_wallet_id": target["wallet_id"], "amount": 10},
        headers=tenant_headers(TENANT_A),
    )
    assert resp.status_code == 422
    assert resp.json()["error"]["code"] == "wallet_currency_mismatch"


@pytest.mark.asyncio
async def test_transfer_rejects_same_wallet(client):
    ctx = await _open_wallet(client, suffix="same1")
    await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/credit",
        json={"amount": 500},
        headers=tenant_headers(TENANT_A),
    )

    resp = await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/transfer",
        json={"target_wallet_id": ctx["wallet_id"], "amount": 10},
        headers=tenant_headers(TENANT_A),
    )
    assert resp.status_code == 422
    assert resp.json()["error"]["code"] == "wallet_transfer_same_account"


@pytest.mark.asyncio
async def test_transfer_idempotency_returns_same_entries(client):
    source = await _open_wallet(client, suffix="idem1a")
    target = await _open_wallet(client, suffix="idem1b")
    await client.post(
        f"/api/v1/wallets/{source['wallet_id']}/credit",
        json={"amount": 500},
        headers=tenant_headers(TENANT_A),
    )

    first = await client.post(
        f"/api/v1/wallets/{source['wallet_id']}/transfer",
        json={
            "target_wallet_id": target["wallet_id"],
            "amount": 75,
            "idempotency_key": "tr-idem-1",
        },
        headers=tenant_headers(TENANT_A),
    )
    assert first.status_code == 200, first.text

    second = await client.post(
        f"/api/v1/wallets/{source['wallet_id']}/transfer",
        json={
            "target_wallet_id": target["wallet_id"],
            "amount": 75,
            "idempotency_key": "tr-idem-1",
        },
        headers=tenant_headers(TENANT_A),
    )
    assert second.status_code == 200, second.text
    assert second.json()["source_entry"]["id"] == first.json()["source_entry"]["id"]
    assert second.json()["target_entry"]["id"] == first.json()["target_entry"]["id"]

    balance = await client.get(
        f"/api/v1/wallets/{source['wallet_id']}/balance",
        headers=tenant_headers(TENANT_A),
    )
    assert balance.json()["balance"] == 425


@pytest.mark.asyncio
async def test_frozen_wallet_rejects_mutations(client):
    ctx = await _open_wallet(client, suffix="frz1")
    await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/credit",
        json={"amount": 200},
        headers=tenant_headers(TENANT_A),
    )

    frozen = await client.patch(
        f"/api/v1/wallets/{ctx['wallet_id']}",
        json={"status": "frozen"},
        headers=tenant_headers(TENANT_A),
    )
    assert frozen.status_code == 200, frozen.text
    assert frozen.json()["status"] == "frozen"

    resp = await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/credit",
        json={"amount": 10},
        headers=tenant_headers(TENANT_A),
    )
    assert resp.status_code == 409
    assert resp.json()["error"]["code"] == "wallet_account_frozen"


@pytest.mark.asyncio
async def test_closed_wallet_rejects_mutations(client):
    ctx = await _open_wallet(client, suffix="cls1")
    await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/credit",
        json={"amount": 200},
        headers=tenant_headers(TENANT_A),
    )

    closed = await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/delete",
        headers=tenant_headers(TENANT_A),
    )
    assert closed.status_code == 200, closed.text
    assert closed.json()["status"] == "closed"
    assert closed.json()["is_deleted"] is True

    resp = await client.post(
        f"/api/v1/wallets/{ctx['wallet_id']}/debit",
        json={"amount": 10},
        headers=tenant_headers(TENANT_A),
    )
    assert resp.status_code == 404

    events = {e.event_type for e in get_event_publisher().published}
    assert LoyaltyEventType.WALLET_CLOSED.value in events


@pytest.mark.asyncio
async def test_duplicate_wallet_for_member_program_rejected(client):
    ctx = await _open_wallet(client, suffix="dup1")

    dup = await client.post(
        "/api/v1/wallets",
        json={"program_id": ctx["program_id"], "member_id": ctx["member_id"]},
        headers=tenant_headers(TENANT_A),
    )
    assert dup.status_code == 409
    assert dup.json()["error"]["code"] == "wallet_account_exists"


@pytest.mark.asyncio
async def test_list_wallets(client):
    ctx = await _open_wallet(client, suffix="ls1")

    listed = await client.get("/api/v1/wallets", headers=tenant_headers(TENANT_A))
    assert listed.status_code == 200
    assert any(w["id"] == ctx["wallet_id"] for w in listed.json())
