"""
Integration tests for Visit Orders, pricing snapshots, and order lifecycle.
"""

from datetime import UTC, datetime

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.appointment import Appointment, AppointmentStatus, BookingChannel
from app.models.lab import Lab
from app.models.package import TestPackage as LabPackage
from app.models.patient import Gender, Patient
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 order_lab(db_session: AsyncSession) -> Lab:
    """Create test laboratory."""
    lab = Lab(name="Orders Test Lab", address="Kathmandu")
    db_session.add(lab)
    await db_session.commit()
    await db_session.refresh(lab)
    return lab


@pytest_asyncio.fixture
async def order_patient(db_session: AsyncSession, order_lab: Lab) -> Patient:
    """Create test patient."""
    p = Patient(
        lab_id=order_lab.id,
        patient_code="P/26/0000000088",
        name="Order Patient",
        gender=Gender.FEMALE,
        phone="9817000088",
    )
    db_session.add(p)
    await db_session.commit()
    await db_session.refresh(p)
    return p


@pytest_asyncio.fixture
async def catalog_fixtures(db_session: AsyncSession, order_lab: Lab) -> tuple[LabTest, LabPackage]:
    """Create test and package with defined prices."""
    cat = LabCategory(lab_id=order_lab.id, name="Order Category")
    db_session.add(cat)
    await db_session.flush()

    test = LabTest(
        lab_id=order_lab.id,
        category_id=cat.id,
        name="Thyroid Stimulating Hormone",
        code="TSH",
        price=450.0,
        result_type=LabResultType.NUMERIC,
    )
    pkg = LabPackage(
        lab_id=order_lab.id,
        name="Thyroid Profile",
        code="TP",
        price=1100.0,
    )
    db_session.add_all([test, pkg])
    await db_session.commit()
    await db_session.refresh(test)
    await db_session.refresh(pkg)
    return test, pkg


@pytest_asyncio.fixture
async def admin_token(db_session: AsyncSession, order_lab: Lab) -> str:
    """Create admin user token."""
    admin = User(
        lab_id=order_lab.id,
        name="Order Admin",
        phone="9817000001",
        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(order_lab.id),
        },
    )


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


@pytest_asyncio.fixture
async def receptionist_token(db_session: AsyncSession, order_lab: Lab) -> str:
    receptionist = User(
        lab_id=order_lab.id,
        name="Orders Receptionist",
        phone="9817000003",
        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(order_lab.id),
        },
    )


@pytest.mark.asyncio
class TestOrderEndpoints:
    """Tests covering visit order creation, pricing snapshots, and lifecycle."""

    async def test_reception_order_access_is_scoped_to_assigned_lab(
        self,
        client: AsyncClient,
        db_session: AsyncSession,
        admin_token: str,
        receptionist_token: str,
    ) -> None:
        other_lab = Lab(name="Other Orders Lab", address="Bhaktapur")
        db_session.add(other_lab)
        await db_session.flush()
        category = LabCategory(lab_id=other_lab.id, name="Other Branch Category")
        patient = Patient(
            lab_id=other_lab.id,
            patient_code="P260099",
            name="Other Branch Patient",
            gender=Gender.FEMALE,
            phone="9817000099",
        )
        db_session.add_all([category, patient])
        await db_session.flush()
        test = LabTest(
            lab_id=other_lab.id,
            category_id=category.id,
            name="Other Branch Test",
            code="OBT",
            price=100,
            result_type=LabResultType.NUMERIC,
        )
        db_session.add(test)
        await db_session.commit()
        await db_session.refresh(other_lab)
        await db_session.refresh(patient)
        await db_session.refresh(test)

        admin_headers = {"Authorization": f"Bearer {admin_token}"}
        created = await client.post(
            "/api/v1/orders",
            json={"lab_id": str(other_lab.id), "patient_id": str(patient.id), "test_ids": [str(test.id)]},
            headers=admin_headers,
        )
        assert created.status_code == 201
        order_id = created.json()["data"]["id"]

        receptionist_headers = {"Authorization": f"Bearer {receptionist_token}"}
        list_response = await client.get(
            "/api/v1/orders", params={"lab_id": str(other_lab.id)}, headers=receptionist_headers
        )
        detail_response = await client.get(f"/api/v1/orders/{order_id}", headers=receptionist_headers)
        cancel_response = await client.post(f"/api/v1/orders/{order_id}/cancel", headers=receptionist_headers)

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

    async def test_order_creation_and_pricing_snapshot(
        self,
        client: AsyncClient,
        admin_token: str,
        order_lab: Lab,
        order_patient: Patient,
        catalog_fixtures: tuple[LabTest, LabPackage],
        db_session: AsyncSession,
    ) -> None:
        headers = {"Authorization": f"Bearer {admin_token}"}
        test, pkg = catalog_fixtures

        # 1. Create Order with 1 test (450) and 1 package (1100) -> total = 1550
        create_res = await client.post(
            "/api/v1/orders",
            json={
                "lab_id": str(order_lab.id),
                "patient_id": str(order_patient.id),
                "referred_by_custom": "Dr. Sharma",
                "test_ids": [str(test.id)],
                "package_ids": [str(pkg.id)],
                "notes": "Patient fasting overnight",
            },
            headers=headers,
        )
        assert create_res.status_code == 201
        order = create_res.json()["data"]
        order_id = order["id"]

        # Verify sequential order number format: ORD{YY}000001 (no separators)
        year_short = datetime.now(UTC).strftime("%y")
        assert order["order_number"].startswith(f"ORD{year_short}")
        assert "/" not in order["order_number"]
        assert order["order_number"].endswith("000001")
        assert float(order["total_amount"]) == 1550.0
        assert len(order["tests"]) == 1
        assert len(order["packages"]) == 1
        assert float(order["tests"][0]["price_at_order"]) == 450.0
        assert float(order["packages"][0]["price_at_order"]) == 1100.0

        # 2. Modify Test & Package catalog prices in DB
        test.price = 600.0
        pkg.price = 1500.0
        await db_session.commit()

        # 3. Retrieve order: pricing snapshot must be preserved!
        get_res = await client.get(f"/api/v1/orders/{order_id}", headers=headers)
        assert get_res.status_code == 200
        get_order = get_res.json()["data"]
        assert float(get_order["total_amount"]) == 1550.0
        assert float(get_order["tests"][0]["price_at_order"]) == 450.0
        assert float(get_order["packages"][0]["price_at_order"]) == 1100.0

    async def test_order_with_appointment_handoff(
        self,
        client: AsyncClient,
        admin_token: str,
        order_lab: Lab,
        order_patient: Patient,
        catalog_fixtures: tuple[LabTest, LabPackage],
        db_session: AsyncSession,
    ) -> None:
        headers = {"Authorization": f"Bearer {admin_token}"}
        test, _ = catalog_fixtures

        # Create an appointment
        apt = Appointment(
            lab_id=order_lab.id,
            patient_id=order_patient.id,
            status=AppointmentStatus.CONFIRMED,
            booked_via=BookingChannel.PHONE,
        )
        db_session.add(apt)
        await db_session.commit()
        await db_session.refresh(apt)

        # Create order referencing this appointment
        res = await client.post(
            "/api/v1/orders",
            json={
                "lab_id": str(order_lab.id),
                "patient_id": str(order_patient.id),
                "appointment_id": str(apt.id),
                "test_ids": [str(test.id)],
            },
            headers=headers,
        )
        assert res.status_code == 201
        order_id = res.json()["data"]["id"]

        # Refresh appointment from DB: should be completed and have visit_order_id set
        await db_session.refresh(apt)
        assert str(apt.visit_order_id) == order_id
        assert apt.status == AppointmentStatus.COMPLETED

    async def test_order_cancellation(
        self,
        client: AsyncClient,
        admin_token: str,
        order_lab: Lab,
        order_patient: Patient,
        catalog_fixtures: tuple[LabTest, LabPackage],
    ) -> None:
        headers = {"Authorization": f"Bearer {admin_token}"}
        test, _ = catalog_fixtures

        res = await client.post(
            "/api/v1/orders",
            json={
                "lab_id": str(order_lab.id),
                "patient_id": str(order_patient.id),
                "test_ids": [str(test.id)],
            },
            headers=headers,
        )
        order_id = res.json()["data"]["id"]

        cancel_res = await client.post(f"/api/v1/orders/{order_id}/cancel", headers=headers)
        assert cancel_res.status_code == 200
        assert cancel_res.json()["data"]["status"] == "cancelled"

        # Attempt to cancel again -> 422
        cancel_again = await client.post(f"/api/v1/orders/{order_id}/cancel", headers=headers)
        assert cancel_again.status_code == 422

    async def test_order_empty_items_validation(
        self,
        client: AsyncClient,
        admin_token: str,
        order_lab: Lab,
        order_patient: Patient,
    ) -> None:
        headers = {"Authorization": f"Bearer {admin_token}"}
        res = await client.post(
            "/api/v1/orders",
            json={
                "lab_id": str(order_lab.id),
                "patient_id": str(order_patient.id),
                "test_ids": [],
                "package_ids": [],
            },
            headers=headers,
        )
        assert res.status_code == 422
        assert "at least one test or health package" in res.json()["error"]["message"]

    async def test_forbidden_without_permission(
        self,
        client: AsyncClient,
        general_user_token: str,
        order_lab: Lab,
        order_patient: Patient,
        catalog_fixtures: tuple[LabTest, LabPackage],
    ) -> None:
        headers = {"Authorization": f"Bearer {general_user_token}"}
        test, _ = catalog_fixtures
        res = await client.post(
            "/api/v1/orders",
            json={
                "lab_id": str(order_lab.id),
                "patient_id": str(order_patient.id),
                "test_ids": [str(test.id)],
            },
            headers=headers,
        )
        assert res.status_code == 403


class TestPhaseAReceptionChain:
    """Phase A: draft report at birth, per-type specimens, samples on the bill."""

    async def _make_order(
        self,
        client: AsyncClient,
        headers: dict[str, str],
        order_lab: Lab,
        order_patient: Patient,
        test_id: str,
    ) -> dict:
        create_res = await client.post(
            "/api/v1/orders",
            json={
                "lab_id": str(order_lab.id),
                "patient_id": str(order_patient.id),
                "test_ids": [test_id],
            },
            headers=headers,
        )
        assert create_res.status_code == 201, create_res.text
        return create_res.json()["data"]

    async def test_order_birth_creates_draft_report(
        self,
        client: AsyncClient,
        admin_token: str,
        order_lab: Lab,
        order_patient: Patient,
        catalog_fixtures: tuple[LabTest, LabPackage],
    ) -> None:
        headers = {"Authorization": f"Bearer {admin_token}"}
        test, _ = catalog_fixtures
        order = await self._make_order(client, headers, order_lab, order_patient, str(test.id))

        rep_res = await client.get("/api/v1/reports", params={"order_id": order["id"]}, headers=headers)
        assert rep_res.status_code == 200, rep_res.text
        reports = rep_res.json()["data"]
        assert len(reports) == 1
        assert reports[0]["status"] == "draft"
        assert reports[0]["version"] == 1

    async def test_blood_and_urine_get_distinct_samples(
        self,
        client: AsyncClient,
        admin_token: str,
        order_lab: Lab,
        order_patient: Patient,
        catalog_fixtures: tuple[LabTest, LabPackage],
    ) -> None:
        headers = {"Authorization": f"Bearer {admin_token}"}
        test, _ = catalog_fixtures
        order = await self._make_order(client, headers, order_lab, order_patient, str(test.id))

        blood = await client.post(
            f"/api/v1/orders/{order['id']}/samples",
            json={"sample_type": "blood"},
            headers=headers,
        )
        urine = await client.post(
            f"/api/v1/orders/{order['id']}/samples",
            json={"sample_type": "urine"},
            headers=headers,
        )
        assert blood.status_code == 201, blood.text
        assert urine.status_code == 201, urine.text
        assert blood.json()["data"]["sample_code"] != urine.json()["data"]["sample_code"]

    async def test_bill_detail_lists_sample_codes(
        self,
        client: AsyncClient,
        admin_token: str,
        order_lab: Lab,
        order_patient: Patient,
        catalog_fixtures: tuple[LabTest, LabPackage],
    ) -> None:
        headers = {"Authorization": f"Bearer {admin_token}"}
        test, _ = catalog_fixtures
        order = await self._make_order(client, headers, order_lab, order_patient, str(test.id))
        sample_res = await client.post(
            f"/api/v1/orders/{order['id']}/samples",
            json={"sample_type": "blood"},
            headers=headers,
        )

        assert sample_res.status_code == 201, sample_res.text

        bill_res = await client.post("/api/v1/bills", json={"order_id": order["id"]}, headers=headers)
        assert bill_res.status_code == 201, bill_res.text
        bill = bill_res.json()["data"]
        assert len(bill["samples"]) == 1
        assert bill["samples"][0]["sample_type"] == "blood"
