"""
Tests for app/main.py

Tests FastAPI application initialization, middleware, error handlers,
and lifecycle management.
"""

import os
from unittest.mock import MagicMock, patch

import pytest
from fastapi import HTTPException


@pytest.mark.unit
class TestAppInitialization:
    """Test application initialization and configuration"""

    def test_session_secret_is_set(self):
        """Test that SESSION_SECRET is configured"""
        import app.main

        # SESSION_SECRET should be set (either from settings or default)
        assert app.main.SESSION_SECRET is not None
        assert len(app.main.SESSION_SECRET) > 0

    def test_app_created_successfully(self):
        """Test that FastAPI app is created successfully"""
        from app.main import app

        assert app is not None
        assert app.title == "DocuElevate"


@pytest.mark.unit
class TestLifespanEvents:
    """Test application lifespan events (startup and shutdown)"""

    @pytest.mark.asyncio
    async def test_lifespan_context_manager_executes(self):
        """Test that lifespan context manager can be executed"""
        with (
            patch("app.database.init_db"),
            patch("app.database.SessionLocal") as mock_session_cls,
            patch("app.utils.config_loader.load_settings_from_db"),
            patch("app.utils.config_validator.dump_all_settings"),
            patch("app.utils.config_validator.check_all_configs", return_value={"email": [], "storage": {}}),
            patch("app.utils.notification.init_apprise"),
            patch("app.utils.notification.notify_startup"),
            patch("app.utils.notification.notify_shutdown"),
            patch("app.utils.sentry.init_sentry"),
        ):
            # Mock database session
            mock_db = MagicMock()
            mock_session_cls.return_value = mock_db

            from app.main import app, lifespan

            # Execute the startup and shutdown
            async with lifespan(app):
                pass  # Startup completed

            # Shutdown completed
            mock_db.close.assert_called()

    @pytest.mark.asyncio
    async def test_lifespan_startup_with_config_issues(self):
        """Test that lifespan logs warning when there are config issues"""
        with (
            patch("app.database.init_db"),
            patch("app.database.SessionLocal") as mock_session_cls,
            patch("app.utils.config_loader.load_settings_from_db"),
            patch("app.utils.config_validator.dump_all_settings"),
            patch("app.utils.config_validator.check_all_configs") as mock_check,
            patch("app.utils.notification.init_apprise"),
            patch("app.utils.notification.notify_startup"),
            patch("app.utils.notification.notify_shutdown"),
            patch("app.utils.sentry.init_sentry"),
            patch("logging.warning") as mock_warning,
        ):
            mock_db = MagicMock()
            mock_session_cls.return_value = mock_db
            # Return config with issues
            mock_check.return_value = {"email": ["Invalid email config"], "storage": {"dropbox": ["Missing token"]}}

            from app.main import app, lifespan

            async with lifespan(app):
                pass

            # Should log warning about config issues
            mock_warning.assert_called()

    @pytest.mark.asyncio
    async def test_lifespan_startup_handles_db_settings_load_failure(self):
        """Test that lifespan handles failures when loading settings from DB"""
        with (
            patch("app.database.init_db"),
            patch("app.database.SessionLocal") as mock_session_cls,
            patch("app.utils.config_loader.load_settings_from_db", side_effect=Exception("DB error")),
            patch("app.utils.config_validator.dump_all_settings"),
            patch("app.utils.config_validator.check_all_configs", return_value={"email": [], "storage": {}}),
            patch("app.utils.notification.init_apprise"),
            patch("app.utils.notification.notify_startup"),
            patch("app.utils.notification.notify_shutdown"),
            patch("app.utils.sentry.init_sentry"),
            patch("logging.error") as mock_error,
        ):
            mock_db = MagicMock()
            mock_session_cls.return_value = mock_db

            from app.main import app, lifespan

            # Should not raise exception, just log error
            async with lifespan(app):
                pass

            mock_error.assert_called()

    @pytest.mark.asyncio
    async def test_lifespan_calls_init_sentry_after_db_settings_load(self):
        """Test that init_sentry is called inside lifespan after load_settings_from_db."""

        with (
            patch("app.database.init_db"),
            patch("app.database.SessionLocal") as mock_session_cls,
            patch("app.utils.config_loader.load_settings_from_db") as mock_load_settings,
            patch("app.utils.config_validator.dump_all_settings"),
            patch("app.utils.config_validator.check_all_configs", return_value={"email": [], "storage": {}}),
            patch("app.utils.notification.init_apprise"),
            patch("app.utils.notification.notify_startup"),
            patch("app.utils.notification.notify_shutdown"),
            patch("app.main.init_sentry") as mock_init_sentry,
        ):
            mock_db = MagicMock()
            mock_session_cls.return_value = mock_db

            from app.main import app, lifespan

            async with lifespan(app):
                pass

            # init_sentry must have been called exactly once during startup
            mock_init_sentry.assert_called_once()
            # load_settings_from_db must also have been called
            mock_load_settings.assert_called_once()


@pytest.mark.unit
class TestExceptionHandlers:
    """Test custom exception handlers"""

    def test_http_exception_handler_frontend_route_404(self):
        """Test that HTTPException returns HTML for frontend 404 errors"""
        from fastapi import Request

        from app.main import http_exception_handler

        # Create a mock request for a frontend route
        mock_request = MagicMock(spec=Request)
        mock_request.url.path = "/nonexistent"

        exc = HTTPException(status_code=404, detail="Not found")

        # Call the handler directly
        import asyncio

        response = asyncio.run(http_exception_handler(mock_request, exc))

        assert response.status_code == 404

    def test_http_exception_handler_frontend_route_other_error(self):
        """Test that HTTPException returns HTML for other frontend errors"""
        from fastapi import Request

        from app.main import http_exception_handler

        # Create a mock request for a frontend route
        mock_request = MagicMock(spec=Request)
        mock_request.url.path = "/some-page"

        exc = HTTPException(status_code=403, detail="Forbidden")

        # Call the handler directly
        import asyncio

        response = asyncio.run(http_exception_handler(mock_request, exc))

        assert response.status_code == 403

    def test_custom_500_handler_api_route(self):
        """Test that 500 error returns JSON for API routes"""
        from fastapi import Request

        from app.main import custom_500_handler

        # Create a mock request for an API route
        mock_request = MagicMock(spec=Request)
        mock_request.url.path = "/api/something"

        exc = Exception("Internal error")

        # Call the handler directly
        import asyncio

        response = asyncio.run(custom_500_handler(mock_request, exc))

        assert response.status_code == 500
        # Parse JSON response
        import json

        content = json.loads(response.body.decode())
        assert content["detail"] == "Internal server error"

    def test_custom_500_handler_frontend_route(self):
        """Test that 500 error returns HTML for frontend routes"""
        from fastapi import Request

        from app.main import custom_500_handler

        # Create a mock request for a frontend route
        mock_request = MagicMock(spec=Request)
        mock_request.url.path = "/dashboard"

        exc = Exception("Internal error")

        # Call the handler directly
        import asyncio

        response = asyncio.run(custom_500_handler(mock_request, exc))

        assert response.status_code == 500


@pytest.mark.unit
class TestTestEndpoint:
    """Test the /test-500 debugging endpoint"""

    def test_test_500_endpoint_raises_error(self):
        """Test that /test-500 endpoint raises RuntimeError"""
        from app.main import test_500

        # The function should raise RuntimeError
        with pytest.raises(RuntimeError, match="Testing forced 500 error"):
            test_500()


@pytest.mark.unit
class TestStaticFileMount:
    """Test static file mounting logic"""

    def test_static_files_mounted_when_directory_exists(self):
        """Test that static files are served when directory exists"""
        import pathlib

        from app.main import app

        # Check if static directory exists
        static_dir = pathlib.Path(__file__).parents[1] / "frontend" / "static"

        if os.path.exists(static_dir):
            # Check if static route is mounted
            assert any("/static" in str(route.path) for route in app.routes)


@pytest.mark.unit
class TestMiddlewareConfiguration:
    """Test middleware configuration"""

    def test_app_has_limiter_state(self):
        """Test that app.state.limiter is configured"""
        from app.main import app

        assert hasattr(app.state, "limiter")
        assert app.state.limiter is not None

    def test_app_has_correct_title(self):
        """Test that FastAPI app has correct title"""
        from app.main import app

        assert app.title == "DocuElevate"
