"""Payment gateway connectors (app/crud/payment_providers.py, app/crud/
payments.py, app/routers/payments.py): provider metadata, Paytm/Razorpay
signature checks, and the initiate -> launch -> callback -> verify cycle
with the gateway itself stubbed out (no network)."""

import hashlib
import hmac

import pytest

from app.crud import payment_providers as pp
from app.crud.payment_providers import Checkout, CallbackInfo, paytm_signature, paytm_verify


def test_paytm_signature_round_trip_and_tamper():
    key = "abcdEFGH12345678"
    body = '{"mid":"M1","orderId":"FF1"}'
    sig = paytm_signature(body, key)
    assert paytm_verify(body, key, sig)
    assert not paytm_verify(body.replace("FF1", "FF2"), key, sig)
    assert not paytm_verify(body, "zzzzEFGH12345678", sig)
    # Callback-style dict params: sorted by key, "|"-joined, "null" -> "".
    params = {"STATUS": "TXN_SUCCESS", "ORDERID": "FF1", "BANKNAME": "null"}
    assert paytm_verify(params, key, paytm_signature("|".join(["", "FF1", "TXN_SUCCESS"]), key))


def test_razorpay_callback_signature():
    config = {"key_id": "rzp_test_x", "key_secret": "shh"}
    params = {
        "razorpay_payment_link_id": "plink_1",
        "razorpay_payment_link_reference_id": "FF1",
        "razorpay_payment_link_status": "paid",
        "razorpay_payment_id": "pay_1",
    }
    good = hmac.new(b"shh", b"plink_1|FF1|paid|pay_1", hashlib.sha256).hexdigest()
    assert pp.Razorpay().parse_callback(config, None, {**params, "razorpay_signature": good}).signature_valid is True
    assert pp.Razorpay().parse_callback(config, None, {**params, "razorpay_signature": "bad"}).signature_valid is False


async def test_providers_are_listed_with_their_fields(client, auth_headers):
    resp = await client.get("/payments/providers", headers=auth_headers)
    assert resp.status_code == 200
    by_id = {p["id"]: p for p in resp.json()}
    assert set(by_id) == {"paytm", "razorpay", "stripe", "custom"}
    paytm_fields = {f["key"]: f for f in by_id["paytm"]["fields"]}
    assert paytm_fields["merchant_key"]["secret"] is True and by_id["paytm"]["environments"] == ["sandbox", "production"]


class FakeGateway:
    """Stands in for a provider adapter: form-POST checkout, then whatever
    status the test sets."""

    def __init__(self):
        self.status = ("pending", "PENDING")
        self.callback_url = None

    async def create_checkout(self, config, txn, callback_url):
        self.callback_url = callback_url
        return Checkout(kind="form_post", url="https://gateway.test/pay", fields={"orderId": txn.reference, "token": "t0k"}, gateway_reference="G-1")

    def parse_callback(self, config, txn, params):
        return CallbackInfo(signature_valid=params.get("sig") == "ok", provider_status=params.get("STATUS"), raw=params)

    async def fetch_status(self, config, txn):
        return self.status[0], self.status[1], {"checked": True}

    async def test_credentials(self, config):
        return True, "fine"


@pytest.fixture
def fake_gateway(monkeypatch):
    fake = FakeGateway()
    monkeypatch.setitem(pp.ADAPTERS, "paytm", fake)
    return fake


async def _connector(client, auth_headers):
    resp = await client.post(
        "/connectors",
        headers=auth_headers,
        json={"name": "Test Paytm", "type": "payment_gateway", "configuration": {"provider": "paytm", "environment": "sandbox", "merchant_id": "M", "merchant_key": "k" * 16}},
    )
    assert resp.status_code == 201, resp.text
    connector = resp.json()
    enable = await client.patch(f"/connectors/{connector['id']}", headers=auth_headers, json={"status": "enabled"})
    assert enable.status_code == 200, enable.text
    return connector["id"]


async def test_full_payment_cycle_never_trusts_the_callback(client, auth_headers, test_tenant, fake_gateway):
    connector_id = await _connector(client, auth_headers)
    start = await client.post(
        "/payments/initiate",
        headers=auth_headers,
        json={"connector_id": connector_id, "amount": 1050, "reference": "KSRTC/RTI/00001", "return_url": "https://app.test/pages/p1?recordId=r1"},
    )
    assert start.status_code == 200, start.text
    body = start.json()
    ref = body["payment_reference"]
    assert ref.startswith("FF") and ref.isalnum()  # safe for every gateway's order-id rules
    tid = str(test_tenant["tenant_id"])
    assert body["checkout_url"].endswith(f"/payments/launch/{tid}/{body['transaction_id']}")
    assert fake_gateway.callback_url.endswith(f"/payments/callback/{tid}/{body['transaction_id']}")

    launch = await client.get(f"/payments/launch/{tid}/{body['transaction_id']}")
    assert launch.status_code == 200 and "https://gateway.test/pay" in launch.text and "t0k" in launch.text

    # A "success" callback while the gateway still says pending stays pending.
    cb = f"/payments/callback/{tid}/{body['transaction_id']}"
    forged = await client.post(cb, data={"STATUS": "TXN_SUCCESS", "sig": "nope"}, follow_redirects=False)
    assert forged.status_code == 303
    assert "payment_status=pending" in forged.headers["location"] and "recordId=r1" in forged.headers["location"]

    fake_gateway.status = ("paid", "TXN_SUCCESS")
    real = await client.post(cb, data={"STATUS": "TXN_SUCCESS", "sig": "ok"}, follow_redirects=False)
    assert f"payment_reference={ref}" in real.headers["location"] and "payment_status=paid" in real.headers["location"]

    verify = await client.post("/payments/verify", headers=auth_headers, json={"connector_id": connector_id, "reference": ref})
    assert verify.status_code == 200
    expected = {"status": "paid", "provider_status": "TXN_SUCCESS", "label": "KSRTC/RTI/00001", "amount": 1050}
    assert {k: verify.json()[k] for k in expected} == expected

    txns = await client.get("/payments/transactions", headers=auth_headers, params={"connector_id": connector_id})
    assert [t["status"] for t in txns.json()] == ["paid"]

    # Wrong tenant in the URL finds nothing.
    other = await client.get(f"/payments/launch/00000000-0000-0000-0000-000000000000/{body['transaction_id']}")
    assert "no longer valid" in other.text


async def test_test_button_uses_the_provider_check(client, auth_headers, fake_gateway):
    connector_id = await _connector(client, auth_headers)
    resp = await client.post(f"/connectors/{connector_id}/test", headers=auth_headers)
    assert resp.status_code == 200 and resp.json() == {"success": True, "detail": "fine"}


async def test_bad_amount_is_rejected(client, auth_headers, fake_gateway):
    connector_id = await _connector(client, auth_headers)
    resp = await client.post("/payments/initiate", headers=auth_headers, json={"connector_id": connector_id, "amount": 0})
    assert resp.status_code == 422
