"""
Desk reset of patient portal credentials (reception/superadmin only).
"""

from datetime import date

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.patient import Gender, Patient
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="Credential Lab", address="Kathmandu")
    db_session.add(lab)
    await db_session.commit()
    await db_session.refresh(lab)
    return lab


def _staff_token(user: User) -> str:
    return create_access_token(
        subject=str(user.id),
        extra_claims={"email": user.email, "role": user.role.value, "lab_id": str(user.lab_id)},
    )


@pytest_asyncio.fixture
async def receptionist(db_session: AsyncSession, test_lab: Lab) -> tuple[User, str]:
    user = User(
        lab_id=test_lab.id,
        name="Reception Rina",
        phone="9847000001",
        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)
    return user, _staff_token(user)


@pytest_asyncio.fixture
async def phlebotomist(db_session: AsyncSession, test_lab: Lab) -> tuple[User, str]:
    user = User(
        lab_id=test_lab.id,
        phone="9847000002",
        name="Phlebotomist Binod",
        email="binod@munal.com",
        role=UserRole.PHLEBOTOMIST,
        hashed_password=hash_password("Pass123!"),
        is_active=True,
    )
    db_session.add(user)
    await db_session.commit()
    await db_session.refresh(user)
    return user, _staff_token(user)


@pytest_asyncio.fixture
async def patient(db_session: AsyncSession, test_lab: Lab) -> Patient:
    patient = Patient(
        lab_id=test_lab.id,
        patient_code="P26-700",
        name="Credential Patient",
        gender=Gender.MALE,
        date_of_birth=date(1990, 1, 1),
        phone="9847000100",
        pin_hash=hash_password("OLDPIN"),
    )
    db_session.add(patient)
    await db_session.commit()
    await db_session.refresh(patient)
    return patient


@pytest.mark.asyncio
async def test_reset_phone_and_rotate_pin(
    client: AsyncClient, receptionist: tuple[User, str], patient: Patient
) -> None:
    _, token = receptionist
    headers = {"Authorization": f"Bearer {token}"}

    res = await client.patch(
        f"/api/v1/patients/{patient.id}/portal-credentials",
        json={"phone": "9847000199", "rotate_pin": True},
        headers=headers,
    )
    assert res.status_code == 200, res.text
    body = res.json()["data"]
    assert body["patient"]["phone"] == "9847000199"
    assert body["portal_pin"] is not None and len(body["portal_pin"]) == 6

    # New credentials work; old ones do not.
    ok = await client.post(
        "/api/v1/patient-auth/login",
        json={"phone": "9847000199", "pin": body["portal_pin"]},
    )
    assert ok.status_code == 200, ok.text
    stale = await client.post("/api/v1/patient-auth/login", json={"phone": "9847000100", "pin": "OLDPIN"})
    assert stale.status_code == 401


@pytest.mark.asyncio
async def test_reset_phone_only_keeps_pin(
    client: AsyncClient, receptionist: tuple[User, str], patient: Patient
) -> None:
    _, token = receptionist
    headers = {"Authorization": f"Bearer {token}"}

    res = await client.patch(
        f"/api/v1/patients/{patient.id}/portal-credentials",
        json={"phone": "9847000188"},
        headers=headers,
    )
    assert res.status_code == 200, res.text
    assert res.json()["data"]["portal_pin"] is None

    ok = await client.post("/api/v1/patient-auth/login", json={"phone": "9847000188", "pin": "OLDPIN"})
    assert ok.status_code == 200, ok.text


@pytest.mark.asyncio
async def test_reset_duplicate_phone_rejected(
    client: AsyncClient,
    db_session: AsyncSession,
    test_lab: Lab,
    receptionist: tuple[User, str],
    patient: Patient,
) -> None:
    other = Patient(
        lab_id=test_lab.id,
        patient_code="P26-701",
        name="Other Patient",
        gender=Gender.FEMALE,
        date_of_birth=date(1992, 2, 2),
        phone="9847000177",
    )
    db_session.add(other)
    await db_session.commit()

    _, token = receptionist
    res = await client.patch(
        f"/api/v1/patients/{patient.id}/portal-credentials",
        json={"phone": "9847000177"},
        headers={"Authorization": f"Bearer {token}"},
    )
    assert res.status_code == 409, res.text


@pytest.mark.asyncio
async def test_reset_forbidden_without_patient_write(
    client: AsyncClient, phlebotomist: tuple[User, str], patient: Patient
) -> None:
    _, token = phlebotomist
    res = await client.patch(
        f"/api/v1/patients/{patient.id}/portal-credentials",
        json={"rotate_pin": True},
        headers={"Authorization": f"Bearer {token}"},
    )
    assert res.status_code == 403, res.text


@pytest.mark.asyncio
async def test_reset_unknown_patient_404(client: AsyncClient, receptionist: tuple[User, str]) -> None:
    from uuid import uuid4

    _, token = receptionist
    res = await client.patch(
        f"/api/v1/patients/{uuid4()}/portal-credentials",
        json={"rotate_pin": True},
        headers={"Authorization": f"Bearer {token}"},
    )
    assert res.status_code == 404, res.text
