"""Tasks run around a payment (the Payment Initiator's "before payment" and
the Payment Callback's "after payment" task): the payment_* variables a task
sees come from the stored PaymentTransaction, never from what the browser
claims (app/routers/tasks.py::_trusted_payment_context)."""

import uuid

from tests.conftest import AsyncSessionLocal
from app.models.reporting import Connector, PaymentTransaction

START_ONLY = [{"key": "start", "type": "start", "label": "Start"}]


async def _transaction(tenant_id: uuid.UUID, status: str) -> str:
    async with AsyncSessionLocal() as db:
        async with db.begin():
            connector = Connector(tenant_id=tenant_id, name=f"Gateway {uuid.uuid4().hex[:6]}", type="payment_gateway", configuration={"provider": "custom"}, status="enabled")
            db.add(connector)
            await db.flush()
            reference = f"FF{uuid.uuid4().hex[:12].upper()}"
            db.add(
                PaymentTransaction(
                    tenant_id=tenant_id, connector_id=connector.id, provider="custom", reference=reference, label="RTI/2026/7",
                    gateway_reference="GW-1", amount=1000, currency="INR", status=status, provider_status="TXN_" + status.upper(),
                )
            )
    return reference


async def _run_context(client, auth_headers, context: dict) -> dict:
    task = (await client.post("/tasks", headers=auth_headers, json={"name": "Payment task", "nodes": START_ONLY})).json()
    resp = await client.post(f"/tasks/{task['id']}/run", headers=auth_headers, json={"context": context, "allow_draft": True})
    assert resp.status_code == 200, resp.text
    return resp.json()["context"]


async def test_after_payment_task_sees_the_stored_result_not_the_claimed_one(client, auth_headers, test_tenant):
    reference = await _transaction(test_tenant["tenant_id"], "failed")
    ctx = await _run_context(
        client, auth_headers, {"payment_reference": reference, "payment_status": "paid", "payment_paid": True, "payment_amount": 1, "recordId": "r-1"}
    )
    assert ctx["payment_status"] == "failed"
    assert ctx["payment_paid"] is False
    assert ctx["payment_amount"] == 1000
    assert ctx["payment_label"] == "RTI/2026/7"
    assert ctx["payment_provider_status"] == "TXN_FAILED"
    assert ctx["recordId"] == "r-1"  # everything else passes through

    paid_ref = await _transaction(test_tenant["tenant_id"], "paid")
    ctx = await _run_context(client, auth_headers, {"payment_reference": paid_ref})
    assert ctx["payment_status"] == "paid" and ctx["payment_paid"] is True and ctx["payment_currency"] == "INR"


async def test_unknown_reference_is_unverified(client, auth_headers):
    ctx = await _run_context(client, auth_headers, {"payment_reference": "FAKE123", "payment_status": "paid", "payment_paid": True})
    assert ctx["payment_status"] == "unverified"
    assert ctx["payment_paid"] is False


async def test_a_claimed_result_without_a_reference_is_dropped(client, auth_headers):
    # What a Payment Initiator's before-payment task gets: amount/label pass through.
    ctx = await _run_context(client, auth_headers, {"payment_amount": 1000, "payment_label": "RTI/1", "payment_status": "paid", "payment_paid": True})
    assert ctx["payment_amount"] == 1000 and ctx["payment_label"] == "RTI/1"
    assert "payment_status" not in ctx and "payment_paid" not in ctx

async def test_after_payment_update_names_the_empty_inputs_and_works_with_a_reference(client, auth_headers, test_tenant):
    # The shape of a "save the gateway response onto my payment row" task.
    master = await client.post(
        "/masters", headers=auth_headers,
        json={"name": f"Receipts {uuid.uuid4().hex[:6]}", "field_schema": [{"name": "transaction_id", "type": "varchar"}, {"name": "status", "type": "varchar"}]},
    )
    assert master.status_code == 201, master.text
    record = await client.post(f"/masters/{master.json()['id']}/records", headers=auth_headers, json={"field_values": {"status": "initiated"}})
    assert record.status_code == 201, record.text
    nodes = [
        {"key": "start", "type": "start", "label": "Start", "next_nodes": [{"to": "save"}]},
        {
            "key": "save", "type": "data_update", "label": "Save", "datasource_type": "master", "master_id": master.json()["id"], "match_field": "id",
            "input_params": [
                {"name": "transaction_id", "type": "string", "required": False, "source_field": "payment_reference"},
                {"name": "status", "type": "string", "required": False, "source_field": "payment_status"},
                {"name": "match_value", "type": "string", "required": True, "source_field": "paymentrecordID"},
            ],
        },
    ]
    task = (await client.post("/tasks", headers=auth_headers, json={"name": "Save payment", "nodes": nodes})).json()

    async def run(context):
        resp = await client.post(f"/tasks/{task['id']}/run", headers=auth_headers, json={"context": context, "allow_draft": True})
        assert resp.status_code == 200, resp.text
        return resp.json()

    # No reference (e.g. a test run): payment_status is dropped, so nothing is left to write.
    result = await run({"paymentrecordID": record.json()["id"], "payment_status": "paid"})
    assert result["ok"] is False
    detail = result["trace"][-1]["detail"]
    assert "'transaction_id' (payment_reference)" in detail and "'status' (payment_status)" in detail and "payment_reference" in detail

    reference = await _transaction(test_tenant["tenant_id"], "paid")
    result = await run({"paymentrecordID": record.json()["id"], "payment_reference": reference})
    assert result["ok"] is True, result
    rows = (await client.get(f"/masters/{master.json()['id']}/records", headers=auth_headers)).json()
    rows = rows.get("items", rows) if isinstance(rows, dict) else rows
    assert [r["field_values"] for r in rows if r["id"] == record.json()["id"]] == [{"status": "paid", "transaction_id": reference}]

async def test_before_and_after_payment_tasks_write_the_same_row(client, auth_headers, test_tenant):
    master = await client.post(
        "/masters", headers=auth_headers,
        json={"name": f"Receipts {uuid.uuid4().hex[:6]}", "field_schema": [{"name": "amount", "type": "varchar"}, {"name": "status", "type": "varchar"}, {"name": "transaction_id", "type": "varchar"}]},
    )
    master_id = master.json()["id"]

    async def run(nodes, context):
        task = (await client.post("/tasks", headers=auth_headers, json={"name": "Pay step", "nodes": nodes})).json()
        resp = await client.post(f"/tasks/{task['id']}/run", headers=auth_headers, json={"context": context, "allow_draft": True})
        assert resp.status_code == 200, resp.text
        return resp.json()

    # Before payment: create the receipt row and hand its id back as recordId.
    before = await run(
        [
            {"key": "start", "type": "start", "label": "Start", "next_nodes": [{"to": "create"}]},
            {
                "key": "create", "type": "data_update", "label": "Create", "datasource_type": "master", "master_id": master_id,
                "input_params": [{"name": "amount", "type": "string", "required": False, "source_field": "payment_amount"}],
                "output_params": [{"name": "recordId", "type": "string", "source": "id"}],
            },
        ],
        {"recordId": "the-application-id", "payment_amount": 1000},
    )
    assert before["ok"] is True, before
    receipt_id = before["context"]["recordId"]
    assert receipt_id != "the-application-id"

    # The Initiator put that id in the return URL, stored on the transaction.
    reference = f"FF{uuid.uuid4().hex[:12].upper()}"
    async with AsyncSessionLocal() as db:
        async with db.begin():
            connector = Connector(tenant_id=test_tenant["tenant_id"], name=f"Gateway {uuid.uuid4().hex[:6]}", type="payment_gateway", configuration={"provider": "custom"}, status="enabled")
            db.add(connector)
            await db.flush()
            db.add(PaymentTransaction(
                tenant_id=test_tenant["tenant_id"], connector_id=connector.id, provider="custom", reference=reference, amount=1000, currency="INR",
                status="paid", return_url=f"http://localhost:5173/portal/pages/x?recordId={receipt_id}",
            ))

    # After payment: the payer edited recordId in the address bar — the stored one wins.
    after = await run(
        [
            {"key": "start", "type": "start", "label": "Start", "next_nodes": [{"to": "save"}]},
            {
                "key": "save", "type": "data_update", "label": "Save", "datasource_type": "master", "master_id": master_id, "match_field": "id",
                "input_params": [
                    {"name": "transaction_id", "type": "string", "required": False, "source_field": "payment_reference"},
                    {"name": "status", "type": "string", "required": False, "source_field": "payment_status"},
                    {"name": "match_value", "type": "string", "required": True, "source_field": "recordId"},
                ],
            },
        ],
        {"payment_reference": reference, "recordId": "someone-elses-row", "payment_record_id": "someone-elses-row"},
    )
    assert after["ok"] is True, after
    assert after["context"]["payment_record_id"] == receipt_id
    rows = (await client.get(f"/masters/{master_id}/records", headers=auth_headers)).json()
    rows = rows.get("items", rows) if isinstance(rows, dict) else rows
    assert [(r["id"], r["field_values"]) for r in rows] == [(receipt_id, {"amount": "1000", "status": "paid", "transaction_id": reference})]
