"""
Integration tests for Health Packages and Package Items.
"""

from uuid import uuid4

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.test import (
    Test as LabTest,
)
from app.models.test import (
    TestCategory as LabCategory,
)
from app.models.test import (
    TestResultType as LabResultType,
)
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 package_lab(db_session: AsyncSession) -> Lab:
    """Create test laboratory."""
    lab = Lab(name="Package 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, package_lab: Lab) -> str:
    """Create admin user token."""
    admin = User(
        lab_id=package_lab.id,
        name="Package Admin",
        phone="9815000001",
        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(package_lab.id),
        },
    )


@pytest_asyncio.fixture
async def general_user_token(db_session: AsyncSession, package_lab: Lab) -> str:
    """Create general user token without package:write."""
    user = User(
        lab_id=package_lab.id,
        name="General Staff",
        phone="9815000002",
        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(package_lab.id),
        },
    )


@pytest_asyncio.fixture
async def test_item_fixtures(db_session: AsyncSession, package_lab: Lab) -> tuple[LabTest, LabTest]:
    """Create test category and 2 tests to include in packages."""
    cat = LabCategory(lab_id=package_lab.id, name="General Category")
    db_session.add(cat)
    await db_session.flush()

    t1 = LabTest(
        lab_id=package_lab.id,
        category_id=cat.id,
        name="Blood Glucose Fasting",
        code="BGF",
        price=150.0,
        result_type=LabResultType.NUMERIC,
    )
    t2 = LabTest(
        lab_id=package_lab.id,
        category_id=cat.id,
        name="Lipid Profile",
        code="LIPID",
        price=750.0,
        result_type=LabResultType.COMPOSITE,
    )
    db_session.add_all([t1, t2])
    await db_session.commit()
    await db_session.refresh(t1)
    await db_session.refresh(t2)
    return t1, t2


@pytest.mark.asyncio
class TestPackageEndpoints:
    """Tests covering package creation, listing, updating, items and deletion."""

    async def test_package_lifecycle(
        self,
        client: AsyncClient,
        admin_token: str,
        package_lab: Lab,
        test_item_fixtures: tuple[LabTest, LabTest],
    ) -> None:
        headers = {"Authorization": f"Bearer {admin_token}"}
        t1, t2 = test_item_fixtures

        # 1. Create Package
        create_res = await client.post(
            "/api/v1/packages",
            json={
                "lab_id": str(package_lab.id),
                "name": "Diabetes Screening Package",
                "code": "DSP",
                "description": "Fasting blood sugar and lipid checks",
                "price": 800.0,
                "is_active": True,
            },
            headers=headers,
        )
        assert create_res.status_code == 201
        pkg_data = create_res.json()["data"]
        pkg_id = pkg_data["id"]
        assert pkg_data["name"] == "Diabetes Screening Package"
        assert pkg_data["code"] == "DSP"

        audit_res = await client.get(
            "/api/v1/audit-logs?category=BILLING&search=health package", headers=headers
        )
        assert audit_res.status_code == 200
        assert any(entry["action"] == "HEALTH_PACKAGE_CREATED" for entry in audit_res.json()["data"])

        # 2. Duplicate Code 409
        dup_res = await client.post(
            "/api/v1/packages",
            json={
                "lab_id": str(package_lab.id),
                "name": "Another Package",
                "code": "DSP",
                "price": 1000.0,
            },
            headers=headers,
        )
        assert dup_res.status_code == 409

        # 3. Add Item to Package
        add_item_res1 = await client.post(
            f"/api/v1/packages/{pkg_id}/items",
            json={"test_id": str(t1.id), "sort_order": 1},
            headers=headers,
        )
        assert add_item_res1.status_code == 201
        item1 = add_item_res1.json()["data"]
        assert item1["test_code"] == "BGF"

        # 4. Duplicate Test Item in Package 409
        dup_item_res = await client.post(
            f"/api/v1/packages/{pkg_id}/items",
            json={"test_id": str(t1.id)},
            headers=headers,
        )
        assert dup_item_res.status_code == 409

        # Add second item
        add_item_res2 = await client.post(
            f"/api/v1/packages/{pkg_id}/items",
            json={"test_id": str(t2.id), "sort_order": 2},
            headers=headers,
        )
        assert add_item_res2.status_code == 201
        item2 = add_item_res2.json()["data"]

        # 5. Get Package Details with Items
        detail_res = await client.get(f"/api/v1/packages/{pkg_id}", headers=headers)
        assert detail_res.status_code == 200
        detail = detail_res.json()["data"]
        assert len(detail["items"]) == 2

        # 6. Search Packages
        search_res = await client.get("/api/v1/packages?q=Diabetes", headers=headers)
        assert search_res.status_code == 200
        assert len(search_res.json()["data"]) == 1

        # 7. Update Package
        update_res = await client.patch(
            f"/api/v1/packages/{pkg_id}",
            json={"price": 850.0},
            headers=headers,
        )
        assert update_res.status_code == 200
        assert float(update_res.json()["data"]["price"]) == 850.0

        # 8. Remove Item from Package
        remove_item_res = await client.delete(
            f"/api/v1/packages/{pkg_id}/items/{item2['id']}",
            headers=headers,
        )
        assert remove_item_res.status_code == 200

        # Verify only 1 item remaining
        detail_after = await client.get(f"/api/v1/packages/{pkg_id}", headers=headers)
        assert len(detail_after.json()["data"]["items"]) == 1

        # 9. Delete Package (Soft Delete)
        del_res = await client.delete(f"/api/v1/packages/{pkg_id}", headers=headers)
        assert del_res.status_code == 200

        # Verify 404 after deletion
        assert (await client.get(f"/api/v1/packages/{pkg_id}", headers=headers)).status_code == 404

    async def test_get_package_not_found(self, client: AsyncClient, admin_token: str) -> None:
        headers = {"Authorization": f"Bearer {admin_token}"}
        res = await client.get(f"/api/v1/packages/{uuid4()}", headers=headers)
        assert res.status_code == 404

    async def test_package_routes_are_scoped_to_selected_branch(
        self, client: AsyncClient, admin_token: str, package_lab: Lab, db_session: AsyncSession
    ) -> None:
        headers = {"Authorization": f"Bearer {admin_token}"}
        other_lab = Lab(name="Other Package Branch", address="Pokhara")
        db_session.add(other_lab)
        await db_session.commit()

        created = await client.post(
            f"/api/v1/packages?lab_id={other_lab.id}",
            json={"name": "Other Branch Package", "code": "OTHER", "price": 400},
            headers=headers,
        )
        assert created.status_code == 201
        package_id = created.json()["data"]["id"]

        # An ID from another branch is invisible unless the superadmin selects that branch.
        assert (await client.get(f"/api/v1/packages/{package_id}", headers=headers)).status_code == 404
        scoped = await client.get(f"/api/v1/packages/{package_id}?lab_id={other_lab.id}", headers=headers)
        assert scoped.status_code == 200
        assert scoped.json()["data"]["lab_id"] == str(other_lab.id)

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

    async def test_recreate_package_with_deleted_code(
        self, client: AsyncClient, admin_token: str, package_lab: Lab
    ) -> None:
        """Regression: re-creating a soft-deleted package code must not 500."""
        headers = {"Authorization": f"Bearer {admin_token}"}
        payload = {
            "lab_id": str(package_lab.id),
            "name": "Recreated Panel",
            "code": "PKG-RECREATE",
            "price": 999.0,
        }
        create_res = await client.post("/api/v1/packages", json=payload, headers=headers)
        assert create_res.status_code == 201

        # Active duplicate is still a clean 409.
        dup_res = await client.post("/api/v1/packages", json=payload, headers=headers)
        assert dup_res.status_code == 409

        pkg_id = create_res.json()["data"]["id"]
        assert (await client.delete(f"/api/v1/packages/{pkg_id}", headers=headers)).status_code == 200

        # Re-create after soft delete: 201 (was 500 via unique violation).
        recreate_res = await client.post("/api/v1/packages", json=payload, headers=headers)
        assert recreate_res.status_code == 201
        assert recreate_res.json()["data"]["code"] == "PKG-RECREATE"
