"""
Integration tests for Patient management CRUD and search endpoints.
"""

from datetime import UTC, datetime
from uuid import uuid4
from zoneinfo import ZoneInfo

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 patient_test_lab(db_session: AsyncSession) -> Lab:
    """Create a primary test lab."""
    lab = Lab(name="Patient Primary Lab", address="Lalitpur")
    db_session.add(lab)
    await db_session.commit()
    await db_session.refresh(lab)
    return lab


@pytest_asyncio.fixture
async def patient_second_lab(db_session: AsyncSession) -> Lab:
    """Create a second test lab."""
    lab = Lab(name="Patient Secondary Lab", address="Bhaktapur")
    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, patient_test_lab: Lab) -> str:
    """Create an admin user and return their Bearer token."""
    admin = User(
        lab_id=patient_test_lab.id,
        name="Patient Admin",
        phone="9813000001",
        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(patient_test_lab.id),
        },
    )


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


@pytest_asyncio.fixture
async def receptionist_token(db_session: AsyncSession, patient_test_lab: Lab) -> str:
    receptionist = User(
        lab_id=patient_test_lab.id,
        name="Receptionist",
        phone="9813000003",
        role=UserRole.RECEPTIONIST,
        hashed_password=hash_password("ReceptionPass123!"),
        is_active=True,
    )
    db_session.add(receptionist)
    await db_session.commit()
    await db_session.refresh(receptionist)
    return create_access_token(
        subject=str(receptionist.id),
        extra_claims={
            "phone": receptionist.phone,
            "role": receptionist.role.value,
            "lab_id": str(patient_test_lab.id),
        },
    )


@pytest.mark.asyncio
class TestPatientEndpoints:
    """Tests covering Patient registration, search, and CRUD."""

    async def test_list_patients_empty(self, client: AsyncClient, admin_token: str) -> None:
        response = await client.get(
            "/api/v1/patients",
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert response.status_code == 200
        payload = response.json()
        assert payload["success"] is True
        assert payload["data"] == []
        assert payload["meta"]["total"] == 0

    async def test_activity_date_includes_patients_registered_on_clinic_day(
        self, client: AsyncClient, admin_token: str, patient_test_lab: Lab
    ) -> None:
        created = await client.post(
            "/api/v1/patients",
            json={
                "lab_id": str(patient_test_lab.id),
                "name": "Today Patient",
                "gender": "female",
                "phone": "9800000099",
            },
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert created.status_code == 201
        created_at = datetime.fromisoformat(created.json()["data"]["created_at"])
        if created_at.tzinfo is None:
            created_at = created_at.replace(tzinfo=UTC)
        clinic_today = created_at.astimezone(ZoneInfo("Asia/Kathmandu")).date().isoformat()

        response = await client.get(
            "/api/v1/patients",
            params={"activity_date": clinic_today, "limit": 1},
            headers={"Authorization": f"Bearer {admin_token}"},
        )

        assert response.status_code == 200
        payload = response.json()
        assert payload["meta"]["total"] == 1
        assert payload["data"][0]["id"] == created.json()["data"]["id"]

    async def test_reception_patient_access_is_scoped_to_assigned_lab(
        self,
        client: AsyncClient,
        admin_token: str,
        receptionist_token: str,
        patient_second_lab: Lab,
    ) -> None:
        created = await client.post(
            "/api/v1/patients",
            json={
                "lab_id": str(patient_second_lab.id),
                "name": "Other Branch Patient",
                "gender": "male",
                "phone": "9800000088",
            },
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert created.status_code == 201

        headers = {"Authorization": f"Bearer {receptionist_token}"}
        list_response = await client.get(
            "/api/v1/patients",
            params={"lab_id": str(patient_second_lab.id)},
            headers=headers,
        )
        detail_response = await client.get(
            f"/api/v1/patients/{created.json()['data']['id']}",
            headers=headers,
        )

        assert list_response.status_code == 403
        assert detail_response.status_code == 404

    async def test_create_patient_success(
        self, client: AsyncClient, admin_token: str, patient_test_lab: Lab
    ) -> None:
        payload = {
            "lab_id": str(patient_test_lab.id),
            "name": "Ram Bahadur Shrestha",
            "gender": "male",
            "phone": "9841234567",
            "age": 35,
            "address": "Patan Dhoka, Lalitpur",
            "country": "Nepal",
        }
        response = await client.post(
            "/api/v1/patients",
            json=payload,
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert response.status_code == 201
        res = response.json()
        assert res["success"] is True
        data = res["data"]
        assert data["name"] == "Ram Bahadur Shrestha"
        assert data["phone"] == "9841234567"
        assert data["gender"] == "male"
        assert data["age"] == 35

        # Verify sequential code structure P{YY}-0001
        year_short = datetime.now(UTC).strftime("%y")
        assert data["patient_code"] == f"P{year_short}-0001"

    async def test_create_sequential_codes(
        self, client: AsyncClient, admin_token: str, patient_test_lab: Lab
    ) -> None:
        # Create first patient
        res1 = await client.post(
            "/api/v1/patients",
            json={
                "lab_id": str(patient_test_lab.id),
                "name": "First Patient",
                "gender": "male",
                "phone": "9800000001",
            },
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert res1.status_code == 201
        code1 = res1.json()["data"]["patient_code"]

        # Create second patient
        res2 = await client.post(
            "/api/v1/patients",
            json={
                "lab_id": str(patient_test_lab.id),
                "name": "Second Patient",
                "gender": "female",
                "phone": "9800000002",
            },
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert res2.status_code == 201
        code2 = res2.json()["data"]["patient_code"]

        year_short = datetime.now(UTC).strftime("%y")
        assert code1 == f"P{year_short}-0001"
        assert code2 == f"P{year_short}-0002"

    async def test_create_patient_duplicate_phone_in_same_lab_409(
        self, client: AsyncClient, admin_token: str, patient_test_lab: Lab
    ) -> None:
        payload = {
            "lab_id": str(patient_test_lab.id),
            "name": "Original Patient",
            "gender": "female",
            "phone": "9841999999",
        }
        res1 = await client.post(
            "/api/v1/patients",
            json=payload,
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert res1.status_code == 201

        # Attempt with duplicate phone in same lab
        res2 = await client.post(
            "/api/v1/patients",
            json={
                "lab_id": str(patient_test_lab.id),
                "name": "Duplicate Phone Patient",
                "gender": "female",
                "phone": "9841999999",
            },
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert res2.status_code == 409
        assert "already registered" in res2.json()["error"]["message"]

    async def test_create_patient_same_phone_different_lab_success(
        self,
        client: AsyncClient,
        admin_token: str,
        patient_test_lab: Lab,
        patient_second_lab: Lab,
    ) -> None:
        phone = "9841888888"
        # Register in Lab 1
        res1 = await client.post(
            "/api/v1/patients",
            json={
                "lab_id": str(patient_test_lab.id),
                "name": "Patient in Lab 1",
                "gender": "male",
                "phone": phone,
            },
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert res1.status_code == 201

        # Register same phone in Lab 2
        res2 = await client.post(
            "/api/v1/patients",
            json={
                "lab_id": str(patient_second_lab.id),
                "name": "Patient in Lab 2",
                "gender": "male",
                "phone": phone,
            },
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert res2.status_code == 201

    async def test_get_patient_by_id(
        self, client: AsyncClient, admin_token: str, patient_test_lab: Lab
    ) -> None:
        res = await client.post(
            "/api/v1/patients",
            json={
                "lab_id": str(patient_test_lab.id),
                "name": "Sita Devi",
                "gender": "female",
                "phone": "9841777777",
            },
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        patient_id = res.json()["data"]["id"]

        get_res = await client.get(
            f"/api/v1/patients/{patient_id}",
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert get_res.status_code == 200
        assert get_res.json()["data"]["name"] == "Sita Devi"

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

    async def test_search_patients_by_name_phone_and_code(
        self, client: AsyncClient, admin_token: str, patient_test_lab: Lab
    ) -> None:
        # Register distinct patients
        res1 = await client.post(
            "/api/v1/patients",
            json={
                "lab_id": str(patient_test_lab.id),
                "name": "Hari Prasad KC",
                "gender": "male",
                "phone": "9841111222",
            },
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        p1 = res1.json()["data"]

        res2 = await client.post(
            "/api/v1/patients",
            json={
                "lab_id": str(patient_test_lab.id),
                "name": "Gita Kumari Thapa",
                "gender": "female",
                "phone": "9841333444",
            },
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        p2 = res2.json()["data"]

        # Search by partial name
        s_name = await client.get(
            "/api/v1/patients?q=Hari",
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert s_name.status_code == 200
        data = s_name.json()["data"]
        assert len(data) == 1
        assert data[0]["id"] == p1["id"]

        # Search by partial phone
        s_phone = await client.get(
            "/api/v1/patients?q=333444",
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert s_phone.status_code == 200
        data = s_phone.json()["data"]
        assert len(data) == 1
        assert data[0]["id"] == p2["id"]

        # Search by patient code
        s_code = await client.get(
            f"/api/v1/patients?q={p1['patient_code']}",
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert s_code.status_code == 200
        data = s_code.json()["data"]
        assert len(data) == 1
        assert data[0]["id"] == p1["id"]

    async def test_update_patient(self, client: AsyncClient, admin_token: str, patient_test_lab: Lab) -> None:
        res = await client.post(
            "/api/v1/patients",
            json={
                "lab_id": str(patient_test_lab.id),
                "name": "Old Patient Name",
                "gender": "male",
                "phone": "9841555555",
            },
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        patient_id = res.json()["data"]["id"]

        update_res = await client.patch(
            f"/api/v1/patients/{patient_id}",
            json={"name": "New Patient Name", "address": "Kathmandu, Thamel"},
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert update_res.status_code == 200
        updated_data = update_res.json()["data"]
        assert updated_data["name"] == "New Patient Name"
        assert updated_data["address"] == "Kathmandu, Thamel"

    async def test_delete_patient_soft_delete(
        self, client: AsyncClient, admin_token: str, patient_test_lab: Lab
    ) -> None:
        res = await client.post(
            "/api/v1/patients",
            json={
                "lab_id": str(patient_test_lab.id),
                "name": "Delete Me",
                "gender": "male",
                "phone": "9841666666",
            },
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        patient_id = res.json()["data"]["id"]

        # Delete
        del_res = await client.delete(
            f"/api/v1/patients/{patient_id}",
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert del_res.status_code == 200

        # Should now return 404 on get
        get_res = await client.get(
            f"/api/v1/patients/{patient_id}",
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert get_res.status_code == 404

        # Listing should not include deleted patient
        list_res = await client.get(
            "/api/v1/patients",
            headers={"Authorization": f"Bearer {admin_token}"},
        )
        assert list_res.json()["meta"]["total"] == 0

    async def test_forbidden_without_permission(
        self, client: AsyncClient, general_user_token: str, patient_test_lab: Lab
    ) -> None:
        # General user doesn't have patient:write permission
        response = await client.post(
            "/api/v1/patients",
            json={
                "lab_id": str(patient_test_lab.id),
                "name": "Unauthorized Patient",
                "gender": "female",
                "phone": "9841999111",
            },
            headers={"Authorization": f"Bearer {general_user_token}"},
        )
        assert response.status_code == 403
