"""
Integration tests for Test Catalog, Categories, Parameters, and Reference Ranges.
"""

import pytest
import pytest_asyncio
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.security import create_access_token, hash_password
from app.models.lab import Lab
from app.models.user import User, UserRole
from app.repositories.permission_repository import PermissionRepository


@pytest_asyncio.fixture(autouse=True)
async def seed_perms(db_session: AsyncSession) -> None:
    """Seed permissions for RBAC checks."""
    repo = PermissionRepository()
    await repo.seed_permissions(db_session)


@pytest_asyncio.fixture
async def catalog_lab(db_session: AsyncSession) -> Lab:
    """Create test laboratory."""
    lab = Lab(name="Catalog Test Lab", address="Kathmandu")
    db_session.add(lab)
    await db_session.commit()
    await db_session.refresh(lab)
    return lab


@pytest_asyncio.fixture
async def admin_token(db_session: AsyncSession, catalog_lab: Lab) -> str:
    """Create admin user token."""
    admin = User(
        lab_id=catalog_lab.id,
        name="Catalog Admin",
        phone="9814000001",
        role=UserRole.SUPERADMIN,
        hashed_password=hash_password("AdminPass123!"),
        is_active=True,
    )
    db_session.add(admin)
    await db_session.commit()
    await db_session.refresh(admin)

    return create_access_token(
        subject=str(admin.id),
        extra_claims={
            "phone": admin.phone,
            "role": admin.role.value,
            "lab_id": str(catalog_lab.id),
        },
    )


@pytest_asyncio.fixture
async def general_user_token(db_session: AsyncSession, catalog_lab: Lab) -> str:
    """Create general staff token lacking test:write."""
    user = User(
        lab_id=catalog_lab.id,
        name="General Staff",
        phone="9814000002",
        role=UserRole.GENERAL_USER,
        hashed_password=hash_password("StaffPass123!"),
        is_active=True,
    )
    db_session.add(user)
    await db_session.commit()
    await db_session.refresh(user)

    return create_access_token(
        subject=str(user.id),
        extra_claims={
            "phone": user.phone,
            "role": user.role.value,
            "lab_id": str(catalog_lab.id),
        },
    )


@pytest.mark.asyncio
class TestCatalogEndpoints:
    """Tests covering test categories, tests, parameters, and ranges."""

    async def test_category_lifecycle(self, client: AsyncClient, admin_token: str, catalog_lab: Lab) -> None:
        headers = {"Authorization": f"Bearer {admin_token}"}

        # 1. Create Category
        create_res = await client.post(
            "/api/v1/test-categories",
            json={
                "lab_id": str(catalog_lab.id),
                "name": "Hematology",
                "description": "Blood and blood-forming tissues",
                "is_active": True,
            },
            headers=headers,
        )
        assert create_res.status_code == 201
        cat = create_res.json()["data"]
        cat_id = cat["id"]
        assert cat["name"] == "Hematology"

        # 2. Duplicate Category Name 409
        dup_res = await client.post(
            "/api/v1/test-categories",
            json={
                "lab_id": str(catalog_lab.id),
                "name": "Hematology",
            },
            headers=headers,
        )
        assert dup_res.status_code == 409

        # 3. Get Category
        get_res = await client.get(f"/api/v1/test-categories/{cat_id}", headers=headers)
        assert get_res.status_code == 200
        assert get_res.json()["data"]["name"] == "Hematology"

        # 4. Update Category
        update_res = await client.patch(
            f"/api/v1/test-categories/{cat_id}",
            json={"description": "Updated description"},
            headers=headers,
        )
        assert update_res.status_code == 200
        assert update_res.json()["data"]["description"] == "Updated description"

        # 5. List Categories
        list_res = await client.get("/api/v1/test-categories", headers=headers)
        assert list_res.status_code == 200
        assert list_res.json()["meta"]["total"] >= 1

        # 6. Delete Category
        del_res = await client.delete(f"/api/v1/test-categories/{cat_id}", headers=headers)
        assert del_res.status_code == 200

        # Verify 404 after soft delete
        get_deleted = await client.get(f"/api/v1/test-categories/{cat_id}", headers=headers)
        assert get_deleted.status_code == 404

    async def test_test_lifecycle(self, client: AsyncClient, admin_token: str, catalog_lab: Lab) -> None:
        headers = {"Authorization": f"Bearer {admin_token}"}

        # Create category first
        cat_res = await client.post(
            "/api/v1/test-categories",
            json={
                "lab_id": str(catalog_lab.id),
                "name": "Biochemistry",
            },
            headers=headers,
        )
        cat_id = cat_res.json()["data"]["id"]

        # 1. Create Test
        create_res = await client.post(
            "/api/v1/tests",
            json={
                "lab_id": str(catalog_lab.id),
                "category_id": cat_id,
                "name": "Liver Function Test",
                "code": "LFT",
                "description": "Comprehensive LFT profile",
                "price": 1200.0,
                "result_type": "composite",
                "is_active": True,
            },
            headers=headers,
        )
        assert create_res.status_code == 201
        test_data = create_res.json()["data"]
        test_id = test_data["id"]
        assert test_data["code"] == "LFT"

        # 2. Duplicate Test Code 409
        dup_res = await client.post(
            "/api/v1/tests",
            json={
                "lab_id": str(catalog_lab.id),
                "category_id": cat_id,
                "name": "Another Test",
                "code": "LFT",
                "price": 500.0,
                "result_type": "numeric",
            },
            headers=headers,
        )
        assert dup_res.status_code == 409

        # 3. Get Test Detail
        get_res = await client.get(f"/api/v1/tests/{test_id}", headers=headers)
        assert get_res.status_code == 200
        assert get_res.json()["data"]["name"] == "Liver Function Test"
        assert get_res.json()["data"]["parameters"] == []

        # 4. Search Tests
        search_res = await client.get("/api/v1/tests?q=Liver", headers=headers)
        assert search_res.status_code == 200
        assert len(search_res.json()["data"]) == 1

        # 5. Update Test
        update_res = await client.patch(
            f"/api/v1/tests/{test_id}",
            json={"price": 1350.0},
            headers=headers,
        )
        assert update_res.status_code == 200
        assert float(update_res.json()["data"]["price"]) == 1350.0

        # 6. Delete Test
        del_res = await client.delete(f"/api/v1/tests/{test_id}", headers=headers)
        assert del_res.status_code == 200

        # Verify 404 after soft delete
        assert (await client.get(f"/api/v1/tests/{test_id}", headers=headers)).status_code == 404

        # 7. Re-create same code restores soft-deleted test
        revive_res = await client.post(
            "/api/v1/tests",
            json={
                "lab_id": str(catalog_lab.id),
                "category_id": cat_id,
                "name": "Liver Function Test (restored)",
                "code": "LFT",
                "price": 1400.0,
                "result_type": "composite",
            },
            headers=headers,
        )
        assert revive_res.status_code == 201
        revived = revive_res.json()["data"]
        assert revived["id"] == test_id
        assert revived["name"] == "Liver Function Test (restored)"
        assert float(revived["price"]) == 1400.0

    async def test_parameters_and_ranges_lifecycle(
        self, client: AsyncClient, admin_token: str, catalog_lab: Lab
    ) -> None:
        headers = {"Authorization": f"Bearer {admin_token}"}

        # Setup Category and Test
        cat_res = await client.post(
            "/api/v1/test-categories",
            json={"lab_id": str(catalog_lab.id), "name": "General Tests"},
            headers=headers,
        )
        cat_id = cat_res.json()["data"]["id"]

        test_res = await client.post(
            "/api/v1/tests",
            json={
                "lab_id": str(catalog_lab.id),
                "category_id": cat_id,
                "name": "Complete Blood Count",
                "code": "CBC",
                "price": 800.0,
                "result_type": "composite",
            },
            headers=headers,
        )
        test_id = test_res.json()["data"]["id"]

        # 1. Add Parameter to Test
        param_res = await client.post(
            f"/api/v1/tests/{test_id}/parameters",
            json={
                "name": "Haemoglobin",
                "unit": "g/dL",
                "decimal_places": 1,
                "sort_order": 1,
            },
            headers=headers,
        )
        assert param_res.status_code == 201
        param = param_res.json()["data"]
        param_id = param["id"]
        assert param["name"] == "Haemoglobin"
        assert param["unit"] == "g/dL"

        # 2. List Parameters
        plist_res = await client.get(f"/api/v1/tests/{test_id}/parameters", headers=headers)
        assert plist_res.status_code == 200
        assert len(plist_res.json()["data"]) == 1

        # 3. Add Reference Range to Parameter
        range_res = await client.post(
            f"/api/v1/tests/{test_id}/parameters/{param_id}/ranges",
            json={
                "gender": "male",
                "age_min": 18,
                "age_max": 65,
                "numeric_low": 13.5,
                "numeric_high": 17.5,
            },
            headers=headers,
        )
        assert range_res.status_code == 201
        rng = range_res.json()["data"]
        range_id = rng["id"]
        assert rng["gender"] == "male"
        assert float(rng["numeric_low"]) == 13.5

        # 4. List Ranges
        rlist_res = await client.get(
            f"/api/v1/tests/{test_id}/parameters/{param_id}/ranges",
            headers=headers,
        )
        assert rlist_res.status_code == 200
        assert len(rlist_res.json()["data"]) == 1

        # 5. Update Range
        update_range = await client.patch(
            f"/api/v1/tests/{test_id}/parameters/{param_id}/ranges/{range_id}",
            json={"numeric_high": 18.0},
            headers=headers,
        )
        assert update_range.status_code == 200
        assert float(update_range.json()["data"]["numeric_high"]) == 18.0

        # 6. Delete Range
        del_range = await client.delete(
            f"/api/v1/tests/{test_id}/parameters/{param_id}/ranges/{range_id}",
            headers=headers,
        )
        assert del_range.status_code == 200

        # 7. Delete Parameter
        del_param = await client.delete(
            f"/api/v1/tests/{test_id}/parameters/{param_id}",
            headers=headers,
        )
        assert del_param.status_code == 200

    async def test_forbidden_without_permission(
        self, client: AsyncClient, general_user_token: str, catalog_lab: Lab
    ) -> None:
        headers = {"Authorization": f"Bearer {general_user_token}"}
        res = await client.post(
            "/api/v1/test-categories",
            json={
                "lab_id": str(catalog_lab.id),
                "name": "Unauthorized Category",
            },
            headers=headers,
        )
        assert res.status_code == 403
