from __future__ import annotations

import unittest

from fastapi import FastAPI
from fastapi.testclient import TestClient

from govoplan_core.auth import get_api_principal
from govoplan_core.core.modules import (
    FrontendModule,
    FrontendRoute,
    ModuleManifest,
    NavItem,
)
from govoplan_core.core.registry import (
    PlatformRegistry,
    RegistryError,
    manifest_view_surfaces,
)
from govoplan_core.core.views import ViewSurface
from govoplan_core.server.platform import create_platform_router
from govoplan_core.server.registry import available_module_manifests


def example_manifest(
    *,
    custom_surfaces: tuple[ViewSurface, ...] = (),
) -> ModuleManifest:
    return ModuleManifest(
        id="example",
        name="Example",
        version="test",
        frontend=FrontendModule(
            module_id="example",
            nav_items=(
                NavItem(
                    path="/example",
                    label="Example",
                    order=20,
                ),
            ),
            routes=(
                FrontendRoute(
                    path="/example",
                    component="ExamplePage",
                    order=20,
                ),
            ),
            view_surfaces=custom_surfaces,
        ),
    )


class ViewSurfaceContractTests(unittest.TestCase):
    def test_manifest_derives_module_navigation_and_route_surfaces(self) -> None:
        surfaces = manifest_view_surfaces(example_manifest())

        self.assertEqual(
            [
                "example.module",
                "example.nav.example",
                "example.route.example",
            ],
            [surface.id for surface in surfaces],
        )
        self.assertEqual(
            "example.module",
            next(
                surface.parent_id
                for surface in surfaces
                if surface.id == "example.route.example"
            ),
        )

    def test_registry_includes_valid_custom_surface(self) -> None:
        registry = PlatformRegistry()
        registry.register(
            example_manifest(
                custom_surfaces=(
                    ViewSurface(
                        id="example.settings.connection",
                        module_id="example",
                        kind="section",
                        label="Connection settings",
                    ),
                )
            )
        )

        registry.validate()

        custom_surface = next(
            surface
            for surface in registry.view_surfaces()
            if surface.id == "example.settings.connection"
        )
        self.assertEqual("example.module", custom_surface.parent_id)

    def test_registry_rejects_surface_owned_by_another_module(self) -> None:
        registry = PlatformRegistry()
        registry.register(
            example_manifest(
                custom_surfaces=(
                    ViewSurface(
                        id="other.settings.connection",
                        module_id="other",
                        kind="section",
                        label="Connection settings",
                    ),
                )
            )
        )

        with self.assertRaisesRegex(RegistryError, "belongs to"):
            registry.validate()

    def test_registry_rejects_unknown_surface_parent(self) -> None:
        registry = PlatformRegistry()
        registry.register(
            example_manifest(
                custom_surfaces=(
                    ViewSurface(
                        id="example.settings.connection",
                        module_id="example",
                        kind="section",
                        label="Connection settings",
                        parent_id="example.missing",
                    ),
                )
            )
        )

        with self.assertRaisesRegex(RegistryError, "unknown parent"):
            registry.validate()

    def test_registry_rejects_cyclic_surface_hierarchy(self) -> None:
        registry = PlatformRegistry()
        registry.register(
            example_manifest(
                custom_surfaces=(
                    ViewSurface(
                        id="example.section.one",
                        module_id="example",
                        kind="section",
                        label="One",
                        parent_id="example.section.two",
                    ),
                    ViewSurface(
                        id="example.section.two",
                        module_id="example",
                        kind="section",
                        label="Two",
                        parent_id="example.section.one",
                    ),
                )
            )
        )

        with self.assertRaisesRegex(RegistryError, "contains a cycle"):
            registry.validate()

    def test_required_surface_must_be_visible_by_default(self) -> None:
        registry = PlatformRegistry()
        registry.register(
            example_manifest(
                custom_surfaces=(
                    ViewSurface(
                        id="example.recovery",
                        module_id="example",
                        kind="action",
                        label="Recovery",
                        required=True,
                        default_visible=False,
                    ),
                )
            )
        )

        with self.assertRaisesRegex(RegistryError, "visible by default"):
            registry.validate()

    def test_registry_validates_automatically_derived_surface_ids(self) -> None:
        registry = PlatformRegistry()
        registry.register(
            ModuleManifest(
                id="example",
                name="Example",
                version="test",
                frontend=FrontendModule(
                    module_id="example",
                    routes=(
                        FrontendRoute(
                            path=f"/{'segment-' * 30}",
                            component="LongRoutePage",
                        ),
                    ),
                ),
            )
        )

        with self.assertRaisesRegex(RegistryError, "View surface id"):
            registry.validate()

    def test_platform_module_payload_exposes_normalized_surface_contract(self) -> None:
        registry = PlatformRegistry()
        registry.register(
            example_manifest(
                custom_surfaces=(
                    ViewSurface(
                        id="example.section.details",
                        module_id="example",
                        kind="section",
                        label="Details",
                    ),
                )
            )
        )
        registry.validate()
        app = FastAPI()
        app.state.govoplan_registry = registry
        app.include_router(create_platform_router(), prefix="/api/v1")
        app.dependency_overrides[get_api_principal] = lambda: object()

        with TestClient(app) as client:
            response = client.get("/api/v1/platform/modules")

        self.assertEqual(200, response.status_code)
        frontend = response.json()["modules"][0]["frontend"]
        self.assertEqual("1", frontend["view_surface_contract_version"])
        self.assertEqual(
            "example.route.example",
            frontend["routes"][0]["surface_id"],
        )
        surfaces = {
            surface["id"]: surface
            for surface in frontend["view_surfaces"]
        }
        self.assertEqual(
            "example.module",
            surfaces["example.section.details"]["parent_id"],
        )

    def test_access_admin_route_accepts_view_manager_permissions(self) -> None:
        access = available_module_manifests()["access"]
        admin_route = next(
            route
            for route in access.frontend.routes
            if route.path == "/admin"
        )

        self.assertTrue(
            {
                "views:definition:read",
                "views:assignment:read",
                "views:system_definition:read",
                "views:system_assignment:read",
            }.issubset(admin_route.required_any)
        )


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