from typing import Any

import httpx
import pytest
from fastapi import APIRouter, FastAPI
from fastapi.routing import APIRoute

from app.api.deps import get_current_active_user_async
from app.runtime.config import settings


def _v1_routes(app: FastAPI) -> list[Any]:
    """返回最终应用中可执行的 v1 API 路由上下文。"""
    routes: list[Any] = []
    for route in app.routes:
        effective_route_contexts = getattr(route, "effective_route_contexts", None)
        if callable(effective_route_contexts):
            routes.extend(effective_route_contexts())
        elif isinstance(route, APIRoute):
            routes.append(route)
    return [
        route
        for route in routes
        if route.path.startswith(f"{settings.API_V1_STR}/")
    ]


def _route_contract(route: APIRoute) -> tuple[Any, ...]:
    """提取直接聚合前后必须保持一致的公开路由合同。"""
    return (
        type(route),
        route.path,
        tuple(sorted(route.methods or ())),
        route.name,
        route.endpoint,
        tuple(route.tags),
        route.status_code,
        route.response_model,
        route.response_class,
        route.responses,
        tuple(
            (
                dependency.dependency,
                dependency.use_cache,
                tuple(dependency.scopes or ()),
            )
            for dependency in route.dependencies
        ),
        route.operation_id,
        route.unique_id,
        route.include_in_schema,
        route.deprecated,
    )


def test_init_routers_directly_includes_endpoint_router_specs(monkeypatch):
    """启动聚合应直接 include 原始端点路由器并一次性附加完整 v1 前缀。"""
    from app.api.router_specs import API_V1_ROUTER_SPECS
    from app.startup.initializers.routers import init_routers

    app = FastAPI()
    include_calls = []
    original_include_router = app.include_router

    def record_include_router(router, **kwargs):
        """记录启动聚合参数后继续执行 FastAPI 的公开 include 接口。"""
        include_calls.append((router, kwargs.get("prefix"), kwargs.get("tags")))
        return original_include_router(router, **kwargs)

    monkeypatch.setattr(app, "include_router", record_include_router)

    init_routers(app)

    v1_calls = include_calls[: len(API_V1_ROUTER_SPECS)]
    assert [router for router, _, _ in v1_calls] == [
        spec.router for spec in API_V1_ROUTER_SPECS
    ]
    assert [prefix for _, prefix, _ in v1_calls] == [
        f"{settings.API_V1_STR}{spec.prefix}" for spec in API_V1_ROUTER_SPECS
    ]
    assert [tuple(tags or ()) for _, _, tags in v1_calls] == [
        spec.tags for spec in API_V1_ROUTER_SPECS
    ]
    assert [prefix for _, prefix, _ in include_calls[-2:]] == [
        "/api/v3",
        "/cookiecloud",
    ]


def test_direct_v1_routes_and_openapi_match_compatibility_router():
    """最终应用的 v1 路由合同与 OpenAPI 应和兼容聚合结果完全一致。"""
    from app.api.apiv1 import api_router
    from app.startup.initializers.routers import init_routers

    compatibility_app = FastAPI()
    compatibility_app.include_router(api_router, prefix=settings.API_V1_STR)
    direct_app = FastAPI()
    init_routers(direct_app)

    compatibility_routes = _v1_routes(compatibility_app)
    direct_routes = _v1_routes(direct_app)

    assert compatibility_routes
    assert direct_routes
    assert [_route_contract(route) for route in direct_routes] == [
        _route_contract(route) for route in compatibility_routes
    ]
    assert all(
        route.dependency_overrides_provider is direct_app for route in direct_routes
    )
    assert all(
        route.dependency_overrides_provider is compatibility_app
        for route in compatibility_routes
    )
    direct_v1_paths = {
        path: item
        for path, item in direct_app.openapi()["paths"].items()
        if path.startswith(f"{settings.API_V1_STR}/")
    }
    assert direct_v1_paths == compatibility_app.openapi()["paths"]


@pytest.mark.anyio
async def test_direct_routes_honor_application_dependency_overrides():
    """直接聚合后的路由仍应由最终 FastAPI 应用解析依赖覆盖。"""
    from app.startup.initializers.routers import init_routers

    app = FastAPI()
    init_routers(app)
    app.dependency_overrides[get_current_active_user_async] = lambda: object()

    async with httpx.AsyncClient(
        transport=httpx.ASGITransport(app=app),
        base_url="http://testserver",
    ) as client:
        response = await client.get(f"{settings.API_V1_STR}/system/ping")

    assert response.status_code == 200
    assert response.json() == {"success": True, "message": "", "data": None}


def test_compatibility_api_router_keeps_public_contract():
    """历史导出应继续提供无 v1 根前缀的标准 APIRouter 与固定路由集合。"""
    from app.api.apiv1 import api_router
    from app.api.router_specs import API_V1_ROUTER_SPECS

    assert type(api_router) is APIRouter
    app = FastAPI()
    app.include_router(api_router, prefix=settings.API_V1_STR)
    paths = set(app.openapi()["paths"])
    expected_paths = {
        f"{settings.API_V1_STR}{spec.prefix}{route.path}"
        for spec in API_V1_ROUTER_SPECS
        for route in spec.router.routes
        if (
            isinstance(route, APIRoute)
            and route.include_in_schema
            and ":path}" not in route.path
        )
    }
    assert expected_paths <= paths


def test_init_routers_accepts_composition_root_api_prefix():
    """路由初始化应使用组合根传入的 API 前缀。"""
    from app.startup.initializers.routers import init_routers

    app = FastAPI()
    init_routers(app, "/custom/v1")

    assert "/custom/v1/system/ping" in app.openapi()["paths"]
