"""Integration tests for app.api.v1.webhooks — Stripe webhook processing."""

import uuid
import datetime

import pytest
import pytest_asyncio
from sqlalchemy import select

from app.models.user import Tenant
from app.models.operations import TenantSubscription, SubscriptionEvent

CUSTOMER_ID = "cus_test123"
SUBSCRIPTION_ID = "sub_test456"
STRIPE_EVENT_ID = "evt_test789"


@pytest_asyncio.fixture
async def seed_tenant(db):
    """Seed a tenant for webhook tests. Cleans up first."""
    from sqlalchemy import delete as sql_delete
    await db.execute(sql_delete(SubscriptionEvent))
    await db.execute(sql_delete(TenantSubscription))
    await db.execute(sql_delete(Tenant).where(Tenant.name == "WebhookTestTenant"))

    t = Tenant(id=uuid.uuid4(), name="WebhookTestTenant", slug="webhook-test-tenant", plan_type="free", is_personal=True)
    db.add(t)
    await db.commit()
    return t


# ── Basic Error Handling ──


@pytest.mark.asyncio
async def test_webhook_invalid_json(client):
    """Invalid JSON body returns 400."""
    r = await client.post("/api/v1/webhooks/stripe",
                          content=b"not-valid-json",
                          headers={"Content-Type": "application/json"})
    assert r.status_code == 400
    assert r.json()["error"]["message"] == "Invalid payload"


@pytest.mark.asyncio
async def test_webhook_unhandled_event(client):
    """Unhandled event type returns received."""
    payload = {"type": "unhandled.event.type", "data": {"object": {}}}
    r = await client.post("/api/v1/webhooks/stripe", json=payload)
    assert r.status_code == 200
    assert r.json()["status"] == "received"


# ── Checkout Session Completed (new subscription) ──


@pytest.mark.asyncio
async def test_webhook_checkout_new(client, db, seed_tenant):
    """checkout.session.completed creates a new subscription."""
    tid = str(seed_tenant.id)
    payload = {
        "id": STRIPE_EVENT_ID,
        "type": "checkout.session.completed",
        "data": {
            "object": {
                "customer": CUSTOMER_ID,
                "subscription": SUBSCRIPTION_ID,
                "metadata": {"plan_id": "pro", "tenant_id": tid},
            }
        },
    }
    r = await client.post("/api/v1/webhooks/stripe", json=payload)
    assert r.status_code == 200
    assert r.json()["action"] == "subscription_created"

    # Verify in DB
    sub = (await db.execute(select(TenantSubscription).where(
        TenantSubscription.stripe_customer_id == CUSTOMER_ID
    ))).scalar()
    assert sub is not None
    assert sub.plan_id == "pro"
    assert sub.status == "active"

    ev = (await db.execute(select(SubscriptionEvent).where(
        SubscriptionEvent.stripe_event_id == STRIPE_EVENT_ID
    ))).scalar()
    assert ev is not None
    assert ev.event_type == "upgrade"
    assert ev.to_plan == "pro"

    # Verify tenant upgraded
    await db.refresh(seed_tenant)
    assert seed_tenant.plan_type == "pro"
    assert not seed_tenant.is_personal


@pytest.mark.asyncio
async def test_webhook_checkout_missing_tenant_id(client, seed_tenant):
    """checkout without tenant_id metadata returns skipped."""
    payload = {
        "type": "checkout.session.completed",
        "data": {
            "object": {
                "customer": "cus_other",
                "subscription": "sub_other",
                "metadata": {"plan_id": "pro"},
            }
        },
    }
    r = await client.post("/api/v1/webhooks/stripe", json=payload)
    assert r.status_code == 200
    assert r.json()["status"] == "skipped"
    assert r.json()["reason"] == "missing_tenant_id"


@pytest.mark.asyncio
async def test_webhook_checkout_invalid_tenant_id(client, seed_tenant):
    """checkout with invalid tenant_id UUID returns skipped."""
    payload = {
        "type": "checkout.session.completed",
        "data": {
            "object": {
                "customer": "cus_bad",
                "subscription": "sub_bad",
                "metadata": {"plan_id": "pro", "tenant_id": "not-a-uuid"},
            }
        },
    }
    r = await client.post("/api/v1/webhooks/stripe", json=payload)
    assert r.status_code == 200
    assert r.json()["status"] == "skipped"
    assert r.json()["reason"] == "invalid_tenant_id"


@pytest.mark.asyncio
async def test_webhook_checkout_existing_subscription(client, db, seed_tenant):
    """checkout updates existing subscription by stripe_customer_id."""
    from datetime import datetime, timedelta
    from app.compat import UTC
    existing = TenantSubscription(
        id=uuid.uuid4(), tenant_id=seed_tenant.id,
        stripe_customer_id="cus_existing",
        stripe_subscription_id="sub_old",
        plan_id="free", status="incomplete",
        current_period_start=datetime.now(UTC),
        current_period_end=datetime.now(UTC) + timedelta(days=30),
    )
    db.add(existing)
    await db.commit()

    payload = {
        "id": "evt_update_existing",
        "type": "checkout.session.completed",
        "data": {
            "object": {
                "customer": "cus_existing",
                "subscription": "sub_new",
                "metadata": {"plan_id": "team"},
            }
        },
    }
    r = await client.post("/api/v1/webhooks/stripe", json=payload)
    assert r.status_code == 200
    assert r.json()["action"] == "subscription_created"

    await db.refresh(existing)
    assert existing.stripe_subscription_id == "sub_new"
    assert existing.plan_id == "team"
    assert existing.status == "active"

    ev = (await db.execute(select(SubscriptionEvent).where(
        SubscriptionEvent.stripe_event_id == "evt_update_existing"
    ))).scalar()
    assert ev is not None


# ── Subscription Updated ──


@pytest.mark.asyncio
async def test_webhook_subscription_updated(client, db, seed_tenant):
    """customer.subscription.updated updates status."""
    from datetime import datetime, timedelta
    from app.compat import UTC
    sub = TenantSubscription(
        id=uuid.uuid4(), tenant_id=seed_tenant.id,
        stripe_customer_id="cus_upd", stripe_subscription_id="sub_upd",
        plan_id="pro", status="active",
        current_period_start=datetime.now(UTC),
        current_period_end=datetime.now(UTC) + timedelta(days=30),
    )
    db.add(sub)
    await db.commit()

    payload = {
        "id": "evt_upd",
        "type": "customer.subscription.updated",
        "data": {
            "object": {"id": "sub_upd", "status": "past_due"}
        },
    }
    r = await client.post("/api/v1/webhooks/stripe", json=payload)
    assert r.status_code == 200
    assert r.json()["status"] == "received"

    await db.refresh(sub)
    assert sub.status == "past_due"

    ev = (await db.execute(select(SubscriptionEvent).where(
        SubscriptionEvent.stripe_event_id == "evt_upd"
    ))).scalar()
    assert ev is not None
    assert ev.event_type == "status_change"


@pytest.mark.asyncio
async def test_webhook_subscription_updated_no_match(client):
    """Updating non-existent subscription still returns received."""
    payload = {
        "id": "evt_no_match",
        "type": "customer.subscription.updated",
        "data": {
            "object": {"id": "sub_nonexistent", "status": "canceled"}
        },
    }
    r = await client.post("/api/v1/webhooks/stripe", json=payload)
    assert r.status_code == 200
    assert r.json()["status"] == "received"


# ── Subscription Deleted ──


@pytest.mark.asyncio
async def test_webhook_subscription_deleted(client, db, seed_tenant):
    """customer.subscription.deleted cancels and downgrades tenant."""
    from datetime import datetime, timedelta
    from app.compat import UTC
    sub = TenantSubscription(
        id=uuid.uuid4(), tenant_id=seed_tenant.id,
        stripe_customer_id="cus_del", stripe_subscription_id="sub_del",
        plan_id="pro", status="active",
        current_period_start=datetime.now(UTC),
        current_period_end=datetime.now(UTC) + timedelta(days=30),
    )
    db.add(sub)
    await db.commit()

    payload = {
        "id": "evt_del",
        "type": "customer.subscription.deleted",
        "data": {
            "object": {"id": "sub_del"}
        },
    }
    r = await client.post("/api/v1/webhooks/stripe", json=payload)
    assert r.status_code == 200
    assert r.json()["status"] == "received"

    await db.refresh(sub)
    assert sub.status == "canceled"

    await db.refresh(seed_tenant)
    assert seed_tenant.plan_type == "free"

    ev = (await db.execute(select(SubscriptionEvent).where(
        SubscriptionEvent.stripe_event_id == "evt_del"
    ))).scalar()
    assert ev is not None
    assert ev.event_type == "cancel"


@pytest.mark.asyncio
async def test_webhook_subscription_deleted_no_match(client, seed_tenant):
    """Deleting non-existent subscription still returns received."""
    payload = {
        "type": "customer.subscription.deleted",
        "data": {
            "object": {"id": "sub_nonexistent"}
        },
    }
    r = await client.post("/api/v1/webhooks/stripe", json=payload)
    assert r.status_code == 200
    assert r.json()["status"] == "received"
