"""Tests for the FastAPI referral router."""

from __future__ import annotations

import pytest
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient

from referral.config import init_referral
from referral.models import ReferralConfig, RewardType
from referral.rewards import RewardEngine
from referral.router import create_referral_router
from referral.store import InMemoryReferralStore


@pytest.fixture()
def app() -> FastAPI:
    """Create a FastAPI app with the referral router."""
    config = ReferralConfig(
        reward_type=RewardType.CREDIT,
        reward_amount=10.0,
        max_referrals_per_user=50,
        default_code_max_uses=10,
        expiry_days=90,
        double_sided=True,
    )
    init_referral(config)
    store = InMemoryReferralStore()
    engine = RewardEngine(store)
    router = create_referral_router(store, reward_engine=engine)
    application = FastAPI()
    application.include_router(router)
    return application


@pytest.fixture()
async def client(app: FastAPI) -> AsyncClient:
    """Create an async test client."""
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as ac:
        yield ac


# ---------------------------------------------------------------------------
# Create code
# ---------------------------------------------------------------------------


class TestCreateCode:
    async def test_create_code_success(self, client: AsyncClient) -> None:
        resp = await client.post("/referrals/codes", json={"user_id": "user-1"})
        assert resp.status_code == 201
        data = resp.json()
        assert "code" in data
        assert data["code"]["user_id"] == "user-1"
        assert len(data["code"]["code"]) == 8

    async def test_create_code_with_max_uses(self, client: AsyncClient) -> None:
        resp = await client.post(
            "/referrals/codes", json={"user_id": "user-1", "max_uses": 5}
        )
        assert resp.status_code == 201
        assert resp.json()["code"]["max_uses"] == 5


# ---------------------------------------------------------------------------
# Get code
# ---------------------------------------------------------------------------


class TestGetCode:
    async def test_get_code_success(self, client: AsyncClient) -> None:
        create = await client.post("/referrals/codes", json={"user_id": "user-1"})
        code_str = create.json()["code"]["code"]
        resp = await client.get(f"/referrals/codes/{code_str}")
        assert resp.status_code == 200
        assert resp.json()["code"]["code"] == code_str

    async def test_get_code_not_found(self, client: AsyncClient) -> None:
        resp = await client.get("/referrals/codes/BADCODE1")
        assert resp.status_code == 404


# ---------------------------------------------------------------------------
# List user codes
# ---------------------------------------------------------------------------


class TestListUserCodes:
    async def test_list_user_codes(self, client: AsyncClient) -> None:
        await client.post("/referrals/codes", json={"user_id": "user-1"})
        await client.post("/referrals/codes", json={"user_id": "user-1"})
        resp = await client.get("/referrals/codes", params={"user_id": "user-1"})
        assert resp.status_code == 200
        assert len(resp.json()["codes"]) == 2

    async def test_list_user_codes_empty(self, client: AsyncClient) -> None:
        resp = await client.get("/referrals/codes", params={"user_id": "nobody"})
        assert resp.status_code == 200
        assert resp.json()["codes"] == []


# ---------------------------------------------------------------------------
# Apply referral
# ---------------------------------------------------------------------------


class TestApplyReferral:
    async def test_apply_referral_success(self, client: AsyncClient) -> None:
        create = await client.post("/referrals/codes", json={"user_id": "referrer"})
        code_str = create.json()["code"]["code"]
        resp = await client.post(
            "/referrals/apply",
            json={"code": code_str, "referred_user_id": "new-user"},
        )
        assert resp.status_code == 201
        data = resp.json()
        assert data["referral"]["referrer_id"] == "referrer"
        assert data["referral"]["referred_id"] == "new-user"
        assert data["referral"]["status"] == "pending"

    async def test_apply_invalid_code(self, client: AsyncClient) -> None:
        resp = await client.post(
            "/referrals/apply",
            json={"code": "INVALID!", "referred_user_id": "new-user"},
        )
        assert resp.status_code == 400

    async def test_apply_self_referral(self, client: AsyncClient) -> None:
        create = await client.post("/referrals/codes", json={"user_id": "user-1"})
        code_str = create.json()["code"]["code"]
        resp = await client.post(
            "/referrals/apply",
            json={"code": code_str, "referred_user_id": "user-1"},
        )
        assert resp.status_code == 400

    async def test_apply_duplicate_referral(self, client: AsyncClient) -> None:
        create = await client.post("/referrals/codes", json={"user_id": "referrer"})
        code_str = create.json()["code"]["code"]
        await client.post(
            "/referrals/apply",
            json={"code": code_str, "referred_user_id": "new-user"},
        )
        resp = await client.post(
            "/referrals/apply",
            json={"code": code_str, "referred_user_id": "new-user"},
        )
        assert resp.status_code == 400


# ---------------------------------------------------------------------------
# Complete referral
# ---------------------------------------------------------------------------


class TestCompleteReferral:
    async def test_complete_referral_success(self, client: AsyncClient) -> None:
        create = await client.post("/referrals/codes", json={"user_id": "referrer"})
        code_str = create.json()["code"]["code"]
        apply_resp = await client.post(
            "/referrals/apply",
            json={"code": code_str, "referred_user_id": "new-user"},
        )
        referral_id = apply_resp.json()["referral"]["id"]
        resp = await client.post(f"/referrals/{referral_id}/complete")
        assert resp.status_code == 200
        # After completion + reward processing, status becomes "rewarded"
        assert resp.json()["referral"]["status"] in ("completed", "rewarded")

    async def test_complete_nonexistent(self, client: AsyncClient) -> None:
        import uuid

        fake_id = str(uuid.uuid4())
        resp = await client.post(f"/referrals/{fake_id}/complete")
        assert resp.status_code == 400


# ---------------------------------------------------------------------------
# Stats
# ---------------------------------------------------------------------------


class TestStats:
    async def test_get_stats_empty(self, client: AsyncClient) -> None:
        resp = await client.get("/referrals/stats/user-1")
        assert resp.status_code == 200
        stats = resp.json()["stats"]
        assert stats["total_referrals"] == 0
        assert stats["completed"] == 0

    async def test_get_stats_with_data(self, client: AsyncClient) -> None:
        create = await client.post("/referrals/codes", json={"user_id": "referrer"})
        code_str = create.json()["code"]["code"]
        apply_resp = await client.post(
            "/referrals/apply",
            json={"code": code_str, "referred_user_id": "new-user"},
        )
        referral_id = apply_resp.json()["referral"]["id"]
        await client.post(f"/referrals/{referral_id}/complete")

        resp = await client.get("/referrals/stats/referrer")
        assert resp.status_code == 200
        stats = resp.json()["stats"]
        assert stats["total_referrals"] == 1


# ---------------------------------------------------------------------------
# Leaderboard
# ---------------------------------------------------------------------------


class TestLeaderboard:
    async def test_leaderboard_empty(self, client: AsyncClient) -> None:
        resp = await client.get("/referrals/leaderboard")
        assert resp.status_code == 200
        assert resp.json()["entries"] == []

    async def test_leaderboard_with_data(self, client: AsyncClient) -> None:
        # Create referrals for user-1
        create = await client.post("/referrals/codes", json={"user_id": "user-1"})
        code_str = create.json()["code"]["code"]
        apply_resp = await client.post(
            "/referrals/apply",
            json={"code": code_str, "referred_user_id": "r1"},
        )
        ref_id = apply_resp.json()["referral"]["id"]
        await client.post(f"/referrals/{ref_id}/complete")

        resp = await client.get("/referrals/leaderboard", params={"limit": 5})
        assert resp.status_code == 200
        entries = resp.json()["entries"]
        assert len(entries) >= 1

    async def test_leaderboard_limit(self, client: AsyncClient) -> None:
        resp = await client.get("/referrals/leaderboard", params={"limit": 1})
        assert resp.status_code == 200


# ---------------------------------------------------------------------------
# Deactivate code
# ---------------------------------------------------------------------------


class TestDeactivateCode:
    async def test_deactivate_success(self, client: AsyncClient) -> None:
        create = await client.post("/referrals/codes", json={"user_id": "user-1"})
        code_str = create.json()["code"]["code"]
        resp = await client.delete(f"/referrals/codes/{code_str}")
        assert resp.status_code == 204

    async def test_deactivate_not_found(self, client: AsyncClient) -> None:
        resp = await client.delete("/referrals/codes/BADCODE1")
        assert resp.status_code == 404

    async def test_deactivated_code_cannot_be_used(self, client: AsyncClient) -> None:
        create = await client.post("/referrals/codes", json={"user_id": "referrer"})
        code_str = create.json()["code"]["code"]
        await client.delete(f"/referrals/codes/{code_str}")
        resp = await client.post(
            "/referrals/apply",
            json={"code": code_str, "referred_user_id": "new-user"},
        )
        assert resp.status_code == 400
