from __future__ import annotations

import unittest
from types import SimpleNamespace

from fastapi import HTTPException
from httpx import ASGITransport, AsyncClient

from app.auth import create_access_token
from app.main import create_app


class FakeUsersCollection:
    def __init__(self, documents: dict[str, dict]) -> None:
        self.documents = dict(documents)

    async def find_one(self, query: dict) -> dict | None:
        return self.documents.get(str(query.get("username")))


class FakeAsyncDatabase(dict):
    def __getitem__(self, key):
        return dict.__getitem__(self, key)


class ErrorPagesTest(unittest.IsolatedAsyncioTestCase):
    async def asyncSetUp(self) -> None:
        self.app = create_app()
        self.app.state.mongo_runtime = SimpleNamespace(
            async_database=FakeAsyncDatabase({"users": FakeUsersCollection({"admin": {"username": "admin", "role": "admin"}})})
        )

        @self.app.get("/boom-html")
        async def boom_html():
            raise RuntimeError("boom")

        @self.app.get("/api/boom")
        async def boom_api():
            raise RuntimeError("boom")

        @self.app.get("/boom-http")
        async def boom_http():
            raise HTTPException(status_code=500, detail="detalle sensible")

        self.transport = ASGITransport(app=self.app, raise_app_exceptions=False)

    async def test_unknown_html_route_renders_404_page(self) -> None:
        async with AsyncClient(transport=self.transport, base_url="http://testserver") as client:
            response = await client.get("/ruta-inexistente")

        self.assertEqual(response.status_code, 404)
        self.assertIn("Página no encontrada", response.text)
        self.assertIn('/static/css/error-pages.css', response.text)

    async def test_unknown_api_route_keeps_json_404(self) -> None:
        async with AsyncClient(transport=self.transport, base_url="http://testserver") as client:
            response = await client.get("/api/ruta-inexistente")

        self.assertEqual(response.status_code, 404)
        self.assertEqual(response.json()["detail"], "Not Found")

    async def test_unhandled_html_exception_renders_generic_500_page(self) -> None:
        async with AsyncClient(transport=self.transport, base_url="http://testserver") as client:
            response = await client.get("/boom-html")

        self.assertEqual(response.status_code, 500)
        self.assertIn("Error interno del servidor", response.text)
        self.assertNotIn("RuntimeError", response.text)

    async def test_http_exception_500_renders_generic_html_page(self) -> None:
        async with AsyncClient(transport=self.transport, base_url="http://testserver") as client:
            response = await client.get("/boom-http")

        self.assertEqual(response.status_code, 500)
        self.assertIn("Error interno del servidor", response.text)
        self.assertNotIn("detalle sensible", response.text)

    async def test_unhandled_api_exception_returns_generic_json(self) -> None:
        async with AsyncClient(transport=self.transport, base_url="http://testserver") as client:
            response = await client.get("/api/boom")

        self.assertEqual(response.status_code, 500)
        self.assertEqual(
            response.json(),
            {"detail": "Error interno del servidor", "code": "internal_error"},
        )

    async def test_request_id_is_rendered_only_for_admin_sessions(self) -> None:
        token = create_access_token({"sub": "admin"})

        async with AsyncClient(transport=self.transport, base_url="http://testserver") as client:
            response = await client.get("/boom-html", cookies={"access_token": f"Bearer {token}"})

        self.assertEqual(response.status_code, 500)
        self.assertIn("Request ID", response.text)
        self.assertIn(response.headers["x-request-id"], response.text)

    async def test_protected_html_route_redirects_to_login_when_auth_cookie_is_missing(self) -> None:
        async with AsyncClient(transport=self.transport, base_url="http://testserver") as client:
            response = await client.get("/dashboard", follow_redirects=False)

        self.assertEqual(response.status_code, 303)
        self.assertTrue(str(response.headers.get("location") or "").startswith("/login?next="))
        self.assertIn("access_token=", response.headers.get_list("set-cookie")[0])

    async def test_protected_fetch_route_returns_auth_expired_json_with_login_url(self) -> None:
        async with AsyncClient(transport=self.transport, base_url="http://testserver") as client:
            response = await client.get(
                "/api/dashboard/overview",
                headers={
                    "X-Requested-With": "fetch",
                    "Referer": "http://testserver/dashboard",
                    "Accept": "application/json",
                },
            )

        self.assertEqual(response.status_code, 401)
        payload = response.json()
        self.assertEqual(payload["detail"], "Not authenticated")
        self.assertEqual(payload["code"], "auth_expired")
        self.assertEqual(payload["login_url"], "/login?next=%2Fdashboard")


if __name__ == "__main__":
    unittest.main()
