import asyncio
from dataclasses import replace

import httpx
from cryptography.fernet import Fernet
from fastapi import Query

from backend.bootstrap.application import create_application
from backend.bootstrap.settings import Settings
from backend.http.errors import AppError
from tests.support import request


def test_health_reports_runtime_environment(tmp_path) -> None:
    application = create_application(Settings.for_test(tmp_path))
    response = request(application, "/api/health")

    assert response.status_code == 200
    assert response.json() == {
        "status": "ok",
        "environment": "test",
        "components": {"process": "ok", "database": "ok"},
    }
    assert len(response.headers["X-Request-ID"]) == 32
    assert response.headers["X-Content-Type-Options"] == "nosniff"
    assert response.headers["X-Frame-Options"] == "DENY"


def test_unknown_failure_uses_safe_error_contract(tmp_path) -> None:
    application = create_application(Settings.for_test(tmp_path))

    @application.get("/api/test/failure")
    def fail() -> None:
        raise RuntimeError("secret provider details")

    response = request(application, "/api/test/failure")

    payload = response.json()["error"]
    assert response.status_code == 500
    assert payload["code"] == "internal_error"
    assert payload["message"] == "服务暂时不可用，请稍后重试。"
    assert "secret provider details" not in response.text
    assert len(payload["request_id"]) == 32
    assert response.headers["X-Request-ID"] == payload["request_id"]


def test_application_error_uses_business_message(tmp_path) -> None:
    application = create_application(Settings.for_test(tmp_path))

    @application.get("/api/test/locked")
    def locked() -> None:
        raise AppError("membership_required", "该功能仅对会员开放。", 403)

    response = request(application, "/api/test/locked")

    assert response.status_code == 403
    assert response.json()["error"]["code"] == "membership_required"
    assert response.json()["error"]["message"] == "该功能仅对会员开放。"


def test_framework_404_uses_the_same_safe_contract(tmp_path) -> None:
    application = create_application(Settings.for_test(tmp_path))

    response = request(application, "/api/does-not-exist")

    assert response.status_code == 404
    assert response.json()["error"]["code"] == "not_found"
    assert response.json()["error"]["message"] == "请求的内容不存在。"


def test_validation_error_does_not_expose_framework_details(tmp_path) -> None:
    application = create_application(Settings.for_test(tmp_path))

    @application.get("/api/test/validated")
    def validated(value: int = Query(ge=1)) -> dict[str, int]:
        return {"value": value}

    response = request(application, "/api/test/validated?value=wrong")

    assert response.status_code == 422
    assert response.json()["error"]["code"] == "invalid_request"
    assert "integer" not in response.text.lower()


def test_production_frontend_serves_spa_without_capturing_api_404(tmp_path) -> None:
    settings = Settings.for_test(tmp_path)
    settings.frontend_dist_directory.mkdir(parents=True)
    settings.frontend_dist_directory.joinpath("index.html").write_text(
        "<main>application</main>", encoding="utf-8"
    )
    settings.frontend_dist_directory.joinpath("assets").mkdir()
    settings.frontend_dist_directory.joinpath("assets", "asset.js").write_text(
        "window.ready=true", encoding="utf-8"
    )
    application = create_application(settings)

    assert request(application, "/review/history").text == "<main>application</main>"
    asset_response = request(application, "/assets/asset.js")
    assert asset_response.text == "window.ready=true"
    assert asset_response.headers["Cache-Control"] == "public,max-age=31536000,immutable"
    api_response = request(application, "/api/unknown")
    assert api_response.status_code == 404
    assert api_response.json()["error"]["code"] == "not_found"


def test_production_security_headers_are_strict_on_https(tmp_path) -> None:
    settings = Settings.for_test(tmp_path)
    settings = replace(
        settings,
        environment="production",
        encryption_key=Fernet.generate_key().decode("ascii"),
    )
    application = create_application(settings)

    async def get():
        transport = httpx.ASGITransport(app=application)
        async with application.router.lifespan_context(application):
            async with httpx.AsyncClient(
                transport=transport, base_url="https://testserver"
            ) as client:
                return await client.get("/api/health")

    response = asyncio.run(get())
    assert "default-src 'self'" in response.headers["Content-Security-Policy"]
    assert response.headers["Strict-Transport-Security"].startswith("max-age=31536000")
