"""
Integration tests for User management CRUD and password change endpoints.
"""

from uuid import uuid4

import pytest
import pytest_asyncio
from httpx import AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.security import create_access_token, hash_password
from app.models.audit import AuditLog
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 user_test_lab(db_session: AsyncSession) -> Lab:
    """Create and commit a test lab."""
    lab = Lab(name="User Management 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_client_token(db_session: AsyncSession, user_test_lab: Lab) -> tuple[User, str]:
    """Create an admin user and return (User, Bearer token)."""
    admin = User(
        lab_id=user_test_lab.id,
        name="Admin Supervisor",
        phone="9812000001",
        role=UserRole.SUPERADMIN,
        hashed_password=hash_password("AdminPass123!"),
        is_active=True,
    )
    db_session.add(admin)
    await db_session.commit()
    await db_session.refresh(admin)

    token = create_access_token(
        subject=str(admin.id),
        extra_claims={"role": admin.role.value, "lab_id": str(admin.lab_id)},
    )
    return admin, token


@pytest.mark.asyncio
class TestUserEndpoints:
    """Test suite for User management endpoints."""

    async def test_create_user_success(
        self,
        client: AsyncClient,
        admin_client_token: tuple[User, str],
        user_test_lab: Lab,
        db_session: AsyncSession,
    ) -> None:
        _, token = admin_client_token
        payload = {
            "lab_id": str(user_test_lab.id),
            "name": "Binod Phlebotomist",
            "phone": "9812000099",
            "email": "binod@munal.test",
            "role": UserRole.PHLEBOTOMIST.value,
            "password": "InitialPass123!",
        }
        response = await client.post(
            "/api/v1/users",
            json=payload,
            headers={"Authorization": f"Bearer {token}"},
        )
        assert response.status_code == 201
        body = response.json()
        assert body["success"] is True
        data = body["data"]
        assert data["name"] == payload["name"]
        assert data["phone"] == payload["phone"]
        assert data["role"] == payload["role"]
        assert "password" not in data
        assert "hashed_password" not in data
        audit = await db_session.scalar(
            select(AuditLog).where(
                AuditLog.action == "STAFF_CREATED",
                AuditLog.target_id == data["id"],
            )
        )
        assert audit is not None
        assert audit.details == {"role": payload["role"], "rehire": False}
        assert "password" not in str(audit.details).lower()

    async def test_create_duplicate_phone_fails(
        self,
        client: AsyncClient,
        admin_client_token: tuple[User, str],
        user_test_lab: Lab,
    ) -> None:
        admin, token = admin_client_token
        payload = {
            "lab_id": str(user_test_lab.id),
            "name": "Duplicate Staff",
            "phone": admin.phone,  # duplicate
            "license_no": "NMC-TEST-123",
            "role": UserRole.DOCTOR.value,
            "password": "Password123!",
        }
        response = await client.post(
            "/api/v1/users",
            json=payload,
            headers={"Authorization": f"Bearer {token}"},
        )
        assert response.status_code == 409
        body = response.json()
        assert body["success"] is False
        assert body["error"]["code"] == "ALREADY_EXISTS"

    async def test_list_users_paginated_and_filtered(
        self,
        client: AsyncClient,
        admin_client_token: tuple[User, str],
        user_test_lab: Lab,
        db_session: AsyncSession,
    ) -> None:
        _, token = admin_client_token
        db_session.add(
            User(
                lab_id=user_test_lab.id,
                name="Doctor A",
                phone="9812000022",
                role=UserRole.DOCTOR,
                hashed_password=hash_password("Pass123!"),
                is_active=True,
            )
        )
        await db_session.commit()

        # Filter by role
        response = await client.get(
            f"/api/v1/users?role={UserRole.DOCTOR.value}",
            headers={"Authorization": f"Bearer {token}"},
        )
        assert response.status_code == 200
        body = response.json()
        assert body["success"] is True
        assert len(body["data"]) >= 1
        for u in body["data"]:
            assert u["role"] == UserRole.DOCTOR.value

    async def test_get_user_by_id(
        self,
        client: AsyncClient,
        admin_client_token: tuple[User, str],
        user_test_lab: Lab,
        db_session: AsyncSession,
    ) -> None:
        _, token = admin_client_token
        staff = User(
            lab_id=user_test_lab.id,
            name="Target User",
            phone="9812000033",
            role=UserRole.ACCOUNTANT,
            hashed_password=hash_password("Pass123!"),
            is_active=True,
        )
        db_session.add(staff)
        await db_session.commit()

        response = await client.get(
            f"/api/v1/users/{staff.id}",
            headers={"Authorization": f"Bearer {token}"},
        )
        assert response.status_code == 200
        assert response.json()["data"]["name"] == "Target User"

    async def test_get_user_not_found(
        self,
        client: AsyncClient,
        admin_client_token: tuple[User, str],
    ) -> None:
        _, token = admin_client_token
        response = await client.get(
            f"/api/v1/users/{uuid4()}",
            headers={"Authorization": f"Bearer {token}"},
        )
        assert response.status_code == 404
        assert response.json()["error"]["code"] == "NOT_FOUND"

    async def test_update_user_success(
        self,
        client: AsyncClient,
        admin_client_token: tuple[User, str],
        user_test_lab: Lab,
        db_session: AsyncSession,
    ) -> None:
        _, token = admin_client_token
        staff = User(
            lab_id=user_test_lab.id,
            name="Original Name",
            phone="9812000044",
            role=UserRole.RECEPTIONIST,
            hashed_password=hash_password("Pass123!"),
            is_active=True,
        )
        db_session.add(staff)
        await db_session.commit()

        response = await client.patch(
            f"/api/v1/users/{staff.id}",
            json={"name": "Updated Name", "role": UserRole.ACCOUNTANT.value},
            headers={"Authorization": f"Bearer {token}"},
        )
        assert response.status_code == 200
        body = response.json()["data"]
        assert body["name"] == "Updated Name"
        assert body["role"] == UserRole.ACCOUNTANT.value

    async def test_rehire_deleted_phone_restores_account(
        self,
        client: AsyncClient,
        admin_client_token: tuple[User, str],
        user_test_lab: Lab,
    ) -> None:
        """Regression: re-creating a soft-deleted phone restores (201), not 500."""
        _, token = admin_client_token
        headers = {"Authorization": f"Bearer {token}"}
        payload = {
            "lab_id": str(user_test_lab.id),
            "name": "Rehire Ram",
            "phone": "9812000077",
            "role": UserRole.RECEPTIONIST.value,
            "password": "InitialPass123!",
        }
        create_res = await client.post("/api/v1/users", json=payload, headers=headers)
        assert create_res.status_code == 201
        user_id = create_res.json()["data"]["id"]

        assert (await client.delete(f"/api/v1/users/{user_id}", headers=headers)).status_code == 200
        # Deleted staff vanish from the directory (soft delete, not hard).
        assert (await client.get(f"/api/v1/users/{user_id}", headers=headers)).status_code == 404

        rehire = await client.post("/api/v1/users", json=payload, headers=headers)
        assert rehire.status_code == 201
        assert rehire.json()["data"]["phone"] == "9812000077"

    async def test_license_mandatory_for_clinical_roles(
        self,
        client: AsyncClient,
        admin_client_token: tuple[User, str],
        user_test_lab: Lab,
    ) -> None:
        """Doctor/pathologist/radiologist require license_no; others don't."""
        _, token = admin_client_token
        headers = {"Authorization": f"Bearer {token}"}
        base = {
            "lab_id": str(user_test_lab.id),
            "name": "Dr License",
            "phone": "9812000088",
            "role": UserRole.PATHOLOGIST.value,
            "password": "InitialPass123!",
        }
        missing = await client.post("/api/v1/users", json=base, headers=headers)
        assert missing.status_code == 422

        ok = await client.post("/api/v1/users", json={**base, "license_no": "NMC 12345"}, headers=headers)
        assert ok.status_code == 201
        assert ok.json()["data"]["license_no"] == "NMC 12345"
        user_id = ok.json()["data"]["id"]

        # Receptionist without license is fine.
        reception = await client.post(
            "/api/v1/users",
            json={**base, "phone": "9812000089", "role": UserRole.RECEPTIONIST.value},
            headers=headers,
        )
        assert reception.status_code == 201

        # Clearing the license on a clinical role is rejected.
        blank = await client.patch(f"/api/v1/users/{user_id}", json={"license_no": ""}, headers=headers)
        assert blank.status_code == 422
        cleared = await client.patch(f"/api/v1/users/{user_id}", json={"license_no": None}, headers=headers)
        assert cleared.status_code == 422
        retained = await client.get(f"/api/v1/users/{user_id}", headers=headers)
        assert retained.json()["data"]["license_no"] == "NMC 12345"

    async def test_deactivate_user(
        self,
        client: AsyncClient,
        admin_client_token: tuple[User, str],
        user_test_lab: Lab,
        db_session: AsyncSession,
    ) -> None:
        _, token = admin_client_token
        staff = User(
            lab_id=user_test_lab.id,
            name="Active User",
            phone="9812000055",
            role=UserRole.GENERAL_USER,
            hashed_password=hash_password("Pass123!"),
            is_active=True,
        )
        db_session.add(staff)
        await db_session.commit()

        response = await client.post(
            f"/api/v1/users/{staff.id}/deactivate",
            headers={"Authorization": f"Bearer {token}"},
        )
        assert response.status_code == 200
        assert response.json()["data"]["is_active"] is False

    async def test_activate_user(
        self,
        client: AsyncClient,
        admin_client_token: tuple[User, str],
        user_test_lab: Lab,
        db_session: AsyncSession,
    ) -> None:
        _, token = admin_client_token
        staff = User(
            lab_id=user_test_lab.id,
            name="Inactive User",
            phone="9812000056",
            role=UserRole.GENERAL_USER,
            hashed_password=hash_password("Pass123!"),
            is_active=False,
        )
        db_session.add(staff)
        await db_session.commit()

        response = await client.post(
            f"/api/v1/users/{staff.id}/activate",
            headers={"Authorization": f"Bearer {token}"},
        )
        assert response.status_code == 200
        assert response.json()["data"]["is_active"] is True

    async def test_delete_user_soft_delete(
        self,
        client: AsyncClient,
        admin_client_token: tuple[User, str],
        user_test_lab: Lab,
        db_session: AsyncSession,
    ) -> None:
        _, token = admin_client_token
        staff = User(
            lab_id=user_test_lab.id,
            name="To Delete",
            phone="9812000066",
            role=UserRole.GENERAL_USER,
            hashed_password=hash_password("Pass123!"),
            is_active=True,
        )
        db_session.add(staff)
        await db_session.commit()

        del_res = await client.delete(
            f"/api/v1/users/{staff.id}",
            headers={"Authorization": f"Bearer {token}"},
        )
        assert del_res.status_code == 200

        get_res = await client.get(
            f"/api/v1/users/{staff.id}",
            headers={"Authorization": f"Bearer {token}"},
        )
        assert get_res.status_code == 404

    async def test_change_password_flow(
        self,
        client: AsyncClient,
        user_test_lab: Lab,
        db_session: AsyncSession,
    ) -> None:
        # Create a user with known password
        user = User(
            lab_id=user_test_lab.id,
            name="Password Changer",
            phone="9812000077",
            role=UserRole.DOCTOR,
            hashed_password=hash_password("OriginalPassword123!"),
            is_active=True,
        )
        db_session.add(user)
        await db_session.commit()

        token = create_access_token(
            subject=str(user.id),
            extra_claims={"role": user.role.value, "lab_id": str(user.lab_id)},
        )

        # 1. Wrong old password fails
        bad_change = await client.post(
            "/api/v1/users/me/change-password",
            json={"old_password": "WrongPassword!", "new_password": "NewSecretPassword123!"},
            headers={"Authorization": f"Bearer {token}"},
        )
        assert bad_change.status_code == 422
        assert bad_change.json()["error"]["code"] == "VALIDATION_ERROR"

        # 2. Correct old password succeeds
        good_change = await client.post(
            "/api/v1/users/me/change-password",
            json={"old_password": "OriginalPassword123!", "new_password": "NewSecretPassword123!"},
            headers={"Authorization": f"Bearer {token}"},
        )
        assert good_change.status_code == 200
        assert good_change.json()["success"] is True

        # 3. Can now log in with the new password
        login_res = await client.post(
            "/api/v1/auth/login",
            json={"phone": user.phone, "password": "NewSecretPassword123!"},
        )
        assert login_res.status_code == 200
        assert login_res.json()["success"] is True

    async def test_superadmin_can_create_another_superadmin(
        self, client: AsyncClient, user_test_lab: Lab, db_session: AsyncSession
    ) -> None:
        """An authenticated Superadmin can create another user with SUPERADMIN role."""
        superadmin = User(
            lab_id=user_test_lab.id,
            name="Primary Superadmin",
            phone="9800000001",
            role=UserRole.SUPERADMIN,
            hashed_password=hash_password("Pass123!"),
            is_active=True,
        )
        db_session.add(superadmin)
        await db_session.commit()

        token = create_access_token(
            subject=str(superadmin.id),
            extra_claims={"role": superadmin.role.value, "lab_id": str(superadmin.lab_id)},
        )

        res = await client.post(
            "/api/v1/users",
            headers={"Authorization": f"Bearer {token}"},
            json={
                "name": "Second Superadmin",
                "phone": "9800000002",
                "role": "superadmin",
                "password": "Password123!",
            },
        )
        assert res.status_code == 201
        body = res.json()
        assert body["success"] is True
        assert body["data"]["role"] == "superadmin"

    async def test_non_superadmin_cannot_create_superadmin(
        self, client: AsyncClient, admin_client_token: tuple[User, str], db_session: AsyncSession
    ) -> None:
        """A non-superadmin with user:write still cannot create a superadmin."""
        from app.models.user import Permission, UserPermissionOverride

        user, _ = admin_client_token
        user.role = UserRole.RECEPTIONIST
        permission = await db_session.scalar(select(Permission).where(Permission.code == "user:write"))
        db_session.add(UserPermissionOverride(user_id=user.id, permission_id=permission.id, is_granted=True))
        await db_session.commit()
        token = create_access_token(
            subject=str(user.id), extra_claims={"role": user.role.value, "lab_id": str(user.lab_id)}
        )
        res = await client.post(
            "/api/v1/users",
            headers={"Authorization": f"Bearer {token}"},
            json={
                "name": "Unauthorized Superadmin",
                "phone": "9800000003",
                "role": "superadmin",
                "password": "Password123!",
            },
        )
        assert res.status_code == 403
        body = res.json()
        assert body["success"] is False
        assert body["error"]["code"] == "FORBIDDEN"
