# tests/api/test_auth_error_handlers.py
"""
Tests for auth route exception handlers and error paths.
"""

from unittest.mock import patch

import pytest
from fastapi import status


class TestLoginSessionCreationFailure:
    """Test login when session creation fails."""

    @pytest.mark.asyncio
    async def test_login_succeeds_despite_session_creation_failure(
        self, client, async_test_user
    ):
        """Test that login succeeds even if session creation fails."""
        # Mock session creation to fail
        with patch(
            "app.api.routes.auth.session_crud.create_session",
            side_effect=Exception("Session creation failed"),
        ):
            response = await client.post(
                "/api/v1/auth/login",
                json={"email": "testuser@example.com", "password": "TestPassword123!"},
            )

            # Login should still succeed, just without session record
            assert response.status_code == status.HTTP_200_OK
            data = response.json()
            assert "access_token" in data
            assert "refresh_token" in data


class TestOAuthLoginSessionCreationFailure:
    """Test OAuth login when session creation fails."""

    @pytest.mark.asyncio
    async def test_oauth_login_succeeds_despite_session_failure(
        self, client, async_test_user
    ):
        """Test OAuth login succeeds even if session creation fails."""
        with patch(
            "app.api.routes.auth.session_crud.create_session",
            side_effect=Exception("Session failed"),
        ):
            response = await client.post(
                "/api/v1/auth/login/oauth",
                data={
                    "username": "testuser@example.com",
                    "password": "TestPassword123!",
                },
            )

            assert response.status_code == status.HTTP_200_OK
            data = response.json()
            assert "access_token" in data


class TestRefreshTokenSessionUpdateFailure:
    """Test refresh token when session update fails."""

    @pytest.mark.asyncio
    async def test_refresh_token_succeeds_despite_session_update_failure(
        self, client, async_test_user
    ):
        """Test that token refresh succeeds even if session update fails."""
        # First login to get tokens
        response = await client.post(
            "/api/v1/auth/login",
            json={"email": "testuser@example.com", "password": "TestPassword123!"},
        )
        tokens = response.json()

        # Mock session update to fail
        with patch(
            "app.api.routes.auth.session_crud.update_refresh_token",
            side_effect=Exception("Update failed"),
        ):
            response = await client.post(
                "/api/v1/auth/refresh", json={"refresh_token": tokens["refresh_token"]}
            )

            # Should still succeed - tokens are issued before update
            assert response.status_code == status.HTTP_200_OK
            data = response.json()
            assert "access_token" in data


class TestLogoutWithExpiredToken:
    """Test logout with expired/invalid token."""

    @pytest.mark.asyncio
    async def test_logout_with_invalid_token_still_succeeds(
        self, client, async_test_user
    ):
        """Test logout succeeds even with invalid refresh token."""
        # Login first
        response = await client.post(
            "/api/v1/auth/login",
            json={"email": "testuser@example.com", "password": "TestPassword123!"},
        )
        access_token = response.json()["access_token"]

        # Try logout with invalid refresh token
        response = await client.post(
            "/api/v1/auth/logout",
            headers={"Authorization": f"Bearer {access_token}"},
            json={"refresh_token": "invalid.token.here"},
        )

        # Should succeed (idempotent)
        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert data["success"] is True


class TestLogoutWithNonExistentSession:
    """Test logout when session doesn't exist."""

    @pytest.mark.asyncio
    async def test_logout_with_no_session_succeeds(self, client, async_test_user):
        """Test logout succeeds even if session not found."""
        response = await client.post(
            "/api/v1/auth/login",
            json={"email": "testuser@example.com", "password": "TestPassword123!"},
        )
        tokens = response.json()

        # Mock session lookup to return None
        with patch("app.api.routes.auth.session_crud.get_by_jti", return_value=None):
            response = await client.post(
                "/api/v1/auth/logout",
                headers={"Authorization": f"Bearer {tokens['access_token']}"},
                json={"refresh_token": tokens["refresh_token"]},
            )

            # Should succeed (idempotent)
            assert response.status_code == status.HTTP_200_OK


class TestLogoutUnexpectedError:
    """Test logout with unexpected errors."""

    @pytest.mark.asyncio
    async def test_logout_with_unexpected_error_returns_success(
        self, client, async_test_user
    ):
        """Test logout returns success even on unexpected errors."""
        response = await client.post(
            "/api/v1/auth/login",
            json={"email": "testuser@example.com", "password": "TestPassword123!"},
        )
        tokens = response.json()

        # Mock to raise unexpected error
        with patch(
            "app.api.routes.auth.session_crud.get_by_jti",
            side_effect=Exception("Unexpected error"),
        ):
            response = await client.post(
                "/api/v1/auth/logout",
                headers={"Authorization": f"Bearer {tokens['access_token']}"},
                json={"refresh_token": tokens["refresh_token"]},
            )

            # Should still return success (don't expose errors)
            assert response.status_code == status.HTTP_200_OK
            data = response.json()
            assert data["success"] is True


class TestLogoutAllUnexpectedError:
    """Test logout-all with unexpected errors."""

    @pytest.mark.asyncio
    async def test_logout_all_database_error(self, client, async_test_user):
        """Test logout-all handles database errors."""
        response = await client.post(
            "/api/v1/auth/login",
            json={"email": "testuser@example.com", "password": "TestPassword123!"},
        )
        access_token = response.json()["access_token"]

        # Mock to raise database error
        with patch(
            "app.api.routes.auth.session_crud.deactivate_all_user_sessions",
            side_effect=Exception("DB error"),
        ):
            response = await client.post(
                "/api/v1/auth/logout-all",
                headers={"Authorization": f"Bearer {access_token}"},
            )

            assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR


class TestPasswordResetConfirmSessionInvalidation:
    """Test password reset invalidates sessions."""

    @pytest.mark.asyncio
    async def test_password_reset_continues_despite_session_invalidation_failure(
        self, client, async_test_user
    ):
        """Test password reset succeeds even if session invalidation fails."""
        # Create a valid password reset token
        from app.utils.security import create_password_reset_token

        token = create_password_reset_token(async_test_user.email)

        # Mock session invalidation to fail
        with patch(
            "app.api.routes.auth.session_crud.deactivate_all_user_sessions",
            side_effect=Exception("Invalidation failed"),
        ):
            response = await client.post(
                "/api/v1/auth/password-reset/confirm",
                json={"token": token, "new_password": "NewPassword123!"},
            )

            # Should still succeed - password was reset
            assert response.status_code == status.HTTP_200_OK
            data = response.json()
            assert data["success"] is True
