from __future__ import annotations

from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

import aiohttp
import pytest
from fastapi import FastAPI
from starlette.requests import Request

from app.config import Settings
from app.dependencies import (
    ApplicationContext,
    get_app_context,
    get_db,
    get_http_session,
    get_history_archive_repo,
    get_scheduler,
    get_settings,
    get_session_cache,
    get_settings_repo,
)
from app.main import create_app
from app.models.server import ServerStatus


def _make_test_request(app: FastAPI) -> Request:
    scope = {
        "type": "http",
        "method": "GET",
        "path": "/",
        "headers": [],
        "query_string": b"",
        "client": ("test", 0),
        "server": ("test", 0),
        "scheme": "http",
        "app": app,
    }
    return Request(scope)


@pytest.mark.asyncio
async def test_app_context_dependency_exposes_shared_resources(test_settings: Settings) -> None:
    app = create_app(settings=test_settings)
    session = aiohttp.ClientSession()
    scheduler = MagicMock()
    app.state.http_session = session
    app.state.scheduler = scheduler
    app.state.server_status = ServerStatus(online=False)
    app.state.pending_recovery = None
    app.state.last_activation = None

    request = _make_test_request(app)
    app_context = await get_app_context(request)

    assert isinstance(app_context, ApplicationContext)
    assert app_context.settings is test_settings
    assert app_context.http_session is session
    assert app_context.scheduler is scheduler
    assert app_context.session_cache is app.state.session_cache
    assert app_context.runtime_state is app.state.runtime_state
    assert await get_settings(app_context) is test_settings
    assert await get_http_session(app_context) is session
    assert await get_scheduler(app_context) is scheduler
    assert await get_session_cache(app_context) is app.state.session_cache

    await session.close()


@pytest.mark.asyncio
async def test_settings_and_history_archive_repo_dependencies_return_modules() -> None:
    settings_repo = await get_settings_repo()
    history_archive_repo = await get_history_archive_repo()

    assert hasattr(settings_repo, "get_setting")
    assert hasattr(settings_repo, "set_setting")
    assert hasattr(settings_repo, "delete_setting")
    assert hasattr(settings_repo, "get_all_settings")

    assert hasattr(history_archive_repo, "archive_ban_event")
    assert hasattr(history_archive_repo, "get_max_timeofban")
    assert hasattr(history_archive_repo, "get_archived_history")


@pytest.mark.asyncio
async def test_get_db_uses_effective_runtime_database_path(test_settings: Settings) -> None:
    """Database connections should use effective runtime settings when overridden."""
    runtime_settings = test_settings.model_copy(update={"database_path": "/tmp/runtime.db"})

    mock_connection = MagicMock()
    mock_connection.close = AsyncMock()

    with patch("app.db.open_db", new=AsyncMock(return_value=mock_connection)) as mock_open_db:
        gen = get_db(settings=runtime_settings)
        try:
            connection = await gen.__anext__()
            assert connection is mock_connection
        finally:
            await gen.aclose()

    mock_open_db.assert_awaited_once_with("/tmp/runtime.db")


def test_request_app_state_access_is_only_allowed_in_dependencies() -> None:
    app_root = Path(__file__).resolve().parents[1] / "app"
    bad_modules: list[str] = []

    for path in sorted(app_root.rglob("*.py")):
        if path.name == "dependencies.py":
            continue
        text = path.read_text()
        if "request.app.state" in text:
            bad_modules.append(str(path))

    assert not bad_modules, f"Direct request.app.state access found in: {bad_modules}"
