"""
Patient portal lane: bill-printed PIN login, visits/appointments overview,
own-document PDF, and staff/patient lane separation.
"""

from datetime import UTC, date, datetime
from decimal import Decimal

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
from app.models.lab import Lab
from app.models.order import OrderStatus, OrderTest, OrderTestStatus, VisitOrder
from app.models.patient import Gender, Patient
from app.models.report import Report, ReportStatus
from app.models.test import CategoryModality
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:
    await PermissionRepository().seed_permissions(db_session)


@pytest_asyncio.fixture
async def test_lab(db_session: AsyncSession) -> Lab:
    lab = Lab(name="Portal Lab", address="Kathmandu")
    db_session.add(lab)
    await db_session.commit()
    await db_session.refresh(lab)
    return lab


@pytest_asyncio.fixture
async def portal_patient(db_session: AsyncSession, test_lab: Lab) -> Patient:
    patient = Patient(
        lab_id=test_lab.id,
        patient_code="P26-900",
        name="Portal Patient",
        gender=Gender.FEMALE,
        date_of_birth=date(1994, 5, 5),
        phone="9849000001",
        email="portal@example.com",
        pin_hash=hash_password("KQ9X2Z"),
    )
    db_session.add(patient)
    await db_session.commit()
    await db_session.refresh(patient)
    return patient


@pytest_asyncio.fixture
async def staffer(db_session: AsyncSession, test_lab: Lab) -> tuple[User, str]:
    user = User(
        lab_id=test_lab.id,
        name="Reception Rina",
        phone="9849000002",
        email="rina@munal.com",
        role=UserRole.RECEPTIONIST,
        hashed_password=hash_password("Pass123!"),
        is_active=True,
    )
    db_session.add(user)
    await db_session.commit()
    await db_session.refresh(user)
    token = create_access_token(
        subject=str(user.id),
        extra_claims={"email": user.email, "role": user.role.value, "lab_id": str(user.lab_id)},
    )
    return user, token


@pytest_asyncio.fixture
async def released_visit(
    db_session: AsyncSession, test_lab: Lab, portal_patient: Patient
) -> tuple[VisitOrder, Report]:
    cat = LabCategory(lab_id=test_lab.id, name="Hematology")
    db_session.add(cat)
    await db_session.flush()
    test = LabTest(
        lab_id=test_lab.id,
        category_id=cat.id,
        name="Complete Blood Count",
        code="CBC",
        result_type=LabResultType.COMPOSITE,
        price=Decimal("450.00"),
    )
    db_session.add(test)
    await db_session.flush()
    order = VisitOrder(
        lab_id=test_lab.id,
        patient_id=portal_patient.id,
        order_number="ORD-PORT-001",
        status=OrderStatus.COMPLETED,
    )
    db_session.add(order)
    await db_session.flush()
    db_session.add(
        OrderTest(
            order_id=order.id,
            test_id=test.id,
            price_at_order=test.price,
            status=OrderTestStatus.RELEASED,
        )
    )
    await db_session.flush()
    report = Report(
        order_id=order.id,
        modality=CategoryModality.PATHOLOGY,
        version=1,
        status=ReportStatus.RELEASED,
        released_at=datetime.now(UTC),
        verification_reason="All normal.",
    )
    db_session.add(report)
    await db_session.flush()
    appt = Appointment(
        lab_id=test_lab.id,
        patient_id=portal_patient.id,
        status=AppointmentStatus.CONFIRMED,
        notes="Follow-up",
    )
    db_session.add(appt)
    await db_session.commit()
    await db_session.refresh(order)
    await db_session.refresh(report)
    return order, report


def _patient_headers(token: str) -> dict[str, str]:
    return {"Authorization": f"Bearer {token}"}


@pytest.mark.asyncio
async def test_patient_login_success_and_failures(client: AsyncClient, portal_patient: Patient) -> None:
    ok = await client.post("/api/v1/patient-auth/login", json={"phone": "9849000001", "pin": "KQ9X2Z"})
    assert ok.status_code == 200, ok.text
    body = ok.json()["data"]
    assert body["patient"]["id"] == str(portal_patient.id)
    assert body["token_type"] == "bearer"

    wrong = await client.post("/api/v1/patient-auth/login", json={"phone": "9849000001", "pin": "WRONG1"})
    assert wrong.status_code == 401
    unknown = await client.post("/api/v1/patient-auth/login", json={"phone": "9800000000", "pin": "KQ9X2Z"})
    assert unknown.status_code == 401


@pytest.mark.asyncio
async def test_patient_overview_and_pdf(
    client: AsyncClient,
    portal_patient: Patient,
    released_visit: tuple[VisitOrder, Report],
) -> None:
    login = await client.post("/api/v1/patient-auth/login", json={"phone": "9849000001", "pin": "KQ9X2Z"})
    headers = _patient_headers(login.json()["data"]["access_token"])

    me = await client.get("/api/v1/patient-auth/me", headers=headers)
    assert me.status_code == 200
    assert me.json()["data"]["phone"] == "9849000001"

    overview = await client.get("/api/v1/patient-auth/overview", headers=headers)
    assert overview.status_code == 200, overview.text
    data = overview.json()["data"]
    assert data["patient_name"] == "Portal Patient"
    assert [v["order_number"] for v in data["visits"]] == ["ORD-PORT-001"]
    assert data["visits"][0]["reports"][0]["modality"] == "pathology"
    assert len(data["appointments"]) == 1

    _, report = released_visit
    pdf = await client.get(f"/api/v1/patient-auth/reports/{report.id}/pdf", headers=headers)
    assert pdf.status_code == 200, pdf.text
    assert pdf.headers["content-type"].startswith("application/pdf")
    assert pdf.content.startswith(b"%PDF-")


@pytest.mark.asyncio
async def test_patient_cannot_reach_others_documents(
    client: AsyncClient, db_session: AsyncSession, test_lab: Lab, portal_patient: Patient
) -> None:
    other = Patient(
        lab_id=test_lab.id,
        patient_code="P26-901",
        name="Other Patient",
        gender=Gender.MALE,
        date_of_birth=date(1990, 1, 1),
        phone="9849000003",
        pin_hash=hash_password("AAAAAA"),
    )
    db_session.add(other)
    await db_session.flush()
    other_order = VisitOrder(
        lab_id=test_lab.id,
        patient_id=other.id,
        order_number="ORD-PORT-002",
        status=OrderStatus.COMPLETED,
    )
    db_session.add(other_order)
    await db_session.flush()
    other_report = Report(
        order_id=other_order.id,
        modality=CategoryModality.PATHOLOGY,
        version=1,
        status=ReportStatus.RELEASED,
        released_at=datetime.now(UTC),
    )
    db_session.add(other_report)
    await db_session.commit()

    login = await client.post("/api/v1/patient-auth/login", json={"phone": "9849000001", "pin": "KQ9X2Z"})
    headers = _patient_headers(login.json()["data"]["access_token"])
    overview = await client.get("/api/v1/patient-auth/overview", headers=headers)
    assert [v["order_number"] for v in overview.json()["data"]["visits"]] == []
    pdf = await client.get(f"/api/v1/patient-auth/reports/{other_report.id}/pdf", headers=headers)
    assert pdf.status_code == 404


@pytest.mark.asyncio
async def test_lane_separation(
    client: AsyncClient, portal_patient: Patient, staffer: tuple[User, str]
) -> None:
    login = await client.post("/api/v1/patient-auth/login", json={"phone": "9849000001", "pin": "KQ9X2Z"})
    patient_headers = _patient_headers(login.json()["data"]["access_token"])
    _, staff_token = staffer
    staff_headers = _patient_headers(staff_token)

    # Patient tokens never pass staff guards.
    assert (await client.get("/api/v1/auth/me", headers=patient_headers)).status_code == 401
    assert (await client.get("/api/v1/reports", headers=patient_headers)).status_code in (401, 403)
    # Staff tokens never pass patient guards.
    assert (await client.get("/api/v1/patient-auth/me", headers=staff_headers)).status_code == 401
    assert (await client.get("/api/v1/patient-auth/overview", headers=staff_headers)).status_code == 401
