"""
Integration tests for authentication endpoints.

Covers:
- POST /api/v1/auth/login (success, bad password, non-existent user, inactive account)
- POST /api/v1/auth/refresh (token rotation and revocation)
- GET /api/v1/auth/me (authenticated user profile and unauthorized access)
- POST /api/v1/auth/logout (revocation in PostgreSQL)
"""

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

from app.core.security import hash_password
from app.core.state_store import store_password_reset_otp
from app.models.lab import Lab
from app.models.user import User, UserRole


async def test_reset_password_changes_credentials_and_revokes_session(
    client: AsyncClient, test_user: User
) -> None:
    login = await client.post(
        "/api/v1/auth/login", json={"phone": test_user.phone, "password": "SuperSecretPass123!"}
    )
    assert login.status_code == 200
    old_refresh = login.json()["data"]["refresh_token"]
    await store_password_reset_otp(test_user.email, "123456", str(test_user.id))
    request = {"email": test_user.email, "otp": "123456", "new_password": "UpdatedPassword456!"}
    assert (await client.post("/api/v1/auth/reset-password", json=request)).status_code == 200
    assert (await client.post("/api/v1/auth/reset-password", json=request)).status_code == 400
    assert (await client.post("/api/v1/auth/refresh", json={"refresh_token": old_refresh})).status_code == 401
    assert (
        await client.post(
            "/api/v1/auth/login", json={"phone": test_user.phone, "password": "SuperSecretPass123!"}
        )
    ).status_code == 401
    assert (
        await client.post(
            "/api/v1/auth/login", json={"phone": test_user.phone, "password": "UpdatedPassword456!"}
        )
    ).status_code == 200


@pytest_asyncio.fixture
async def test_lab(db_session: AsyncSession) -> Lab:
    """Create and commit a test lab."""
    lab = Lab(name="Main Test Branch", address="Kathmandu, Nepal")
    db_session.add(lab)
    await db_session.commit()
    await db_session.refresh(lab)
    return lab


@pytest_asyncio.fixture
async def test_user(db_session: AsyncSession, test_lab: Lab) -> User:
    """Create and commit an active test staff user."""
    user = User(
        lab_id=test_lab.id,
        name="Dr. Ashish Sharma",
        phone="9841234567",
        email="ashish@munallab.com",
        role=UserRole.SUPERADMIN,
        hashed_password=hash_password("SuperSecretPass123!"),
        is_active=True,
    )
    db_session.add(user)
    await db_session.commit()
    await db_session.refresh(user)
    return user


@pytest_asyncio.fixture
async def inactive_user(db_session: AsyncSession, test_lab: Lab) -> User:
    """Create and commit a deactivated user."""
    user = User(
        lab_id=test_lab.id,
        name="Deactivated Staff",
        phone="9800000000",
        email="inactive@munallab.com",
        role=UserRole.RECEPTIONIST,
        hashed_password=hash_password("Password123!"),
        is_active=False,
    )
    db_session.add(user)
    await db_session.commit()
    await db_session.refresh(user)
    return user


@pytest.mark.asyncio
class TestAuthEndpoints:
    """Test suite for authentication endpoints."""

    async def test_cookie_refresh_and_missing_credentials(self, client: AsyncClient, test_user: User) -> None:
        client.cookies.clear()
        assert (await client.post("/api/v1/auth/refresh", json={})).status_code == 401
        assert (await client.post("/api/v1/auth/refresh", json={"refresh_token": "short"})).status_code == 422
        login = await client.post(
            "/api/v1/auth/login",
            json={"phone": test_user.phone, "password": "SuperSecretPass123!"},
        )
        assert login.status_code == 200
        refreshed = await client.post("/api/v1/auth/refresh", json={})
        assert refreshed.status_code == 200
        assert refreshed.json()["data"]["refresh_token"] != login.json()["data"]["refresh_token"]
        assert (await client.get("/api/v1/auth/me")).status_code == 200
        assert (await client.post("/api/v1/auth/logout", json={})).status_code == 200
        assert (await client.post("/api/v1/auth/refresh", json={})).status_code == 401

    async def test_login_success(self, client: AsyncClient, test_user: User) -> None:
        response = await client.post(
            "/api/v1/auth/login",
            json={"phone": test_user.phone, "password": "SuperSecretPass123!"},
        )
        assert response.status_code == 200
        body = response.json()
        assert body["success"] is True
        assert "data" in body
        data = body["data"]
        assert "access_token" in data
        assert "refresh_token" in data
        assert data["token_type"] == "bearer"
        assert data["expires_in"] > 0

    async def test_login_with_email_success(self, client: AsyncClient, test_user: User) -> None:
        """Staff members can log in using their email address."""
        response = await client.post(
            "/api/v1/auth/login",
            json={"email": test_user.email, "password": "SuperSecretPass123!"},
        )
        assert response.status_code == 200
        body = response.json()
        assert body["success"] is True
        assert "access_token" in body["data"]

    async def test_login_missing_phone_and_email_fails(self, client: AsyncClient) -> None:
        """Attempting to log in without phone or email fails validation."""
        response = await client.post(
            "/api/v1/auth/login",
            json={"password": "SuperSecretPass123!"},
        )
        assert response.status_code == 422

    async def test_login_wrong_password_fails(self, client: AsyncClient, test_user: User) -> None:
        response = await client.post(
            "/api/v1/auth/login",
            json={"phone": test_user.phone, "password": "WrongPassword!"},
        )
        assert response.status_code == 401
        body = response.json()
        assert body["success"] is False
        assert body["error"]["code"] == "UNAUTHORIZED"

    async def test_login_non_existent_phone_fails(self, client: AsyncClient) -> None:
        response = await client.post(
            "/api/v1/auth/login",
            json={"phone": "9899999999", "password": "AnyPassword!"},
        )
        assert response.status_code == 401
        body = response.json()
        assert body["success"] is False
        assert body["error"]["code"] == "UNAUTHORIZED"

    async def test_login_inactive_user_fails(self, client: AsyncClient, inactive_user: User) -> None:
        response = await client.post(
            "/api/v1/auth/login",
            json={"phone": inactive_user.phone, "password": "Password123!"},
        )
        assert response.status_code == 401
        body = response.json()
        assert body["success"] is False
        assert body["error"]["code"] == "UNAUTHORIZED"

    async def test_refresh_token_rotation_success(self, client: AsyncClient, test_user: User) -> None:
        # First log in to get tokens
        login_res = await client.post(
            "/api/v1/auth/login",
            json={"phone": test_user.phone, "password": "SuperSecretPass123!"},
        )
        assert login_res.status_code == 200
        refresh_token = login_res.json()["data"]["refresh_token"]

        # Call refresh endpoint
        refresh_res = await client.post(
            "/api/v1/auth/refresh",
            json={"refresh_token": refresh_token},
        )
        assert refresh_res.status_code == 200
        body = refresh_res.json()
        assert body["success"] is True
        data = body["data"]
        assert "access_token" in data
        assert "refresh_token" in data

    async def test_get_me_success(self, client: AsyncClient, test_user: User) -> None:
        # Log in
        login_res = await client.post(
            "/api/v1/auth/login",
            json={"phone": test_user.phone, "password": "SuperSecretPass123!"},
        )
        access_token = login_res.json()["data"]["access_token"]

        # Request /me
        me_res = await client.get(
            "/api/v1/auth/me",
            headers={"Authorization": f"Bearer {access_token}"},
        )
        assert me_res.status_code == 200
        body = me_res.json()
        assert body["success"] is True
        data = body["data"]
        assert data["id"] == str(test_user.id)
        assert data["name"] == test_user.name
        assert data["phone"] == test_user.phone
        assert data["role"] == test_user.role.value
        assert data["lab_id"] == str(test_user.lab_id)

    async def test_get_me_unauthorized_without_token(self, client: AsyncClient) -> None:
        response = await client.get("/api/v1/auth/me")
        assert response.status_code == 401
        body = response.json()
        assert body["success"] is False
        assert body["error"]["code"] == "UNAUTHORIZED"

    async def test_logout_revokes_refresh_token(self, client: AsyncClient, test_user: User) -> None:
        # Log in
        login_res = await client.post(
            "/api/v1/auth/login",
            json={"phone": test_user.phone, "password": "SuperSecretPass123!"},
        )
        tokens = login_res.json()["data"]
        access_token = tokens["access_token"]
        refresh_token = tokens["refresh_token"]

        # Log out
        logout_res = await client.post(
            "/api/v1/auth/logout",
            headers={"Authorization": f"Bearer {access_token}"},
        )
        assert logout_res.status_code == 200
        assert logout_res.json()["success"] is True

        # Now attempting to refresh using that refresh token must fail
        post_logout_refresh = await client.post(
            "/api/v1/auth/refresh",
            json={"refresh_token": refresh_token},
        )
        assert post_logout_refresh.status_code == 401
        assert post_logout_refresh.json()["error"]["code"] == "UNAUTHORIZED"

        # Now attempting to access /me with the logged-out access token must also be rejected
        post_logout_me = await client.get(
            "/api/v1/auth/me",
            headers={"Authorization": f"Bearer {access_token}"},
        )
        assert post_logout_me.status_code == 401
        assert post_logout_me.json()["error"]["code"] == "UNAUTHORIZED"

    async def test_bootstrap_fails_if_users_already_exist(self, client: AsyncClient, test_user: User) -> None:
        """Bootstrap must return 409 Conflict if system already has users."""
        payload = {
            "lab_name": "New Lab",
            "admin_name": "Super User",
            "admin_phone": "9899999999",
            "admin_email": "superuser@munal.test",
            "password": "SuperPassword123!",
        }
        res = await client.post("/api/v1/auth/bootstrap", json=payload)
        assert res.status_code == 403
        body = res.json()
        assert body["success"] is False
        assert body["error"]["code"] == "FORBIDDEN"

    async def test_bootstrap_success_on_clean_system(
        self, client: AsyncClient, db_session: AsyncSession
    ) -> None:
        """Bootstrap must succeed when no users exist, creating lab and superadmin."""
        from sqlalchemy import delete

        from app.models.user import UserPermissionOverride

        # Clean users for this isolated bootstrap test
        await db_session.execute(delete(UserPermissionOverride))
        await db_session.execute(delete(User))
        await db_session.commit()

        payload = {
            "lab_name": "Bootstrap Main Lab",
            "admin_name": "Root Admin",
            "admin_phone": "9801234567",
            "admin_email": "root@munal.test",
            "password": "RootPassword123!",
        }
        res = await client.post("/api/v1/auth/bootstrap", json=payload)
        assert res.status_code == 201
        body = res.json()
        assert body["success"] is True
        data = body["data"]
        assert data["user"]["name"] == "Root Admin"
        assert data["user"]["phone"] == "9801234567"
        assert data["user"]["role"] == "superadmin"
        assert "access_token" in data["tokens"]
        assert "refresh_token" in data["tokens"]

    async def test_forgot_password_invalid_email_format(self, client: AsyncClient) -> None:
        """Forgot password must reject invalid email formats with 422."""
        res = await client.post("/api/v1/auth/forgot-password", json={"email": "invalid-format"})
        assert res.status_code == 422

    async def test_forgot_password_success_for_registered_user(
        self, client: AsyncClient, test_user: User
    ) -> None:
        """Forgot password sends OTP for a registered active user."""
        res = await client.post("/api/v1/auth/forgot-password", json={"email": test_user.email})
        assert res.status_code == 200
        body = res.json()
        assert body["success"] is True
        assert body["data"]["sent"] is True
        assert "verification code was sent" in body["data"]["message"].lower()

    async def test_forgot_password_unknown_email_returns_not_found(self, client: AsyncClient) -> None:
        """Unknown email returns 404 with a clear message (staff portal explicit feedback)."""
        res = await client.post(
            "/api/v1/auth/forgot-password",
            json={"email": "nobody@nowhere.com"},
        )
        assert res.status_code == 404
        body = res.json()
        assert body["success"] is False
        assert body["error"]["code"] == "NOT_FOUND"
        assert "no staff account" in body["error"]["message"].lower()

    async def test_reset_password_invalid_otp(self, client: AsyncClient, test_user: User) -> None:
        """Reset password rejects invalid or expired OTP with 400 Bad Request."""
        res = await client.post(
            "/api/v1/auth/reset-password",
            json={
                "email": test_user.email,
                "otp": "999999",
                "new_password": "NewValidPassword123!",
            },
        )
        assert res.status_code == 400
        body = res.json()
        assert body["success"] is False
        assert body["error"]["code"] == "BAD_REQUEST"

    async def test_reset_password_weak_password_validation(
        self, client: AsyncClient, test_user: User
    ) -> None:
        """Reset password rejects passwords failing complexity with 422."""
        res = await client.post(
            "/api/v1/auth/reset-password",
            json={
                "email": test_user.email,
                "otp": "123456",
                "new_password": "short",
            },
        )
        assert res.status_code == 422
