from __future__ import annotations

from dataclasses import dataclass, field
from typing import Dict, Iterable, List, cast

from fastapi import Request

from services.navigation import NavigationService
from services.repositories import NavigationRepository
from services.session import AuthSession, SessionTokens
from models import User


@dataclass
class StubNavigationLink:
    id: int
    slug: str
    label: str
    parent_link_id: int | None = None
    route_name: str | None = None
    href_override: str | None = None
    match_prefix: str | None = None
    sort_order: int = 0
    icon: str | None = None
    tooltip: str | None = None
    required_roles: List[str] = field(default_factory=list)
    is_enabled: bool = True
    is_external: bool = False
    children: List["StubNavigationLink"] = field(default_factory=list)


@dataclass
class StubNavigationGroup:
    id: int
    slug: str
    label: str
    sort_order: int = 0
    icon: str | None = None
    tooltip: str | None = None
    is_enabled: bool = True
    links: List[StubNavigationLink] = field(default_factory=list)


class StubNavigationRepository(NavigationRepository):
    def __init__(self, groups: Iterable[StubNavigationGroup]) -> None:
        super().__init__(session=None)  # type: ignore[arg-type]
        self._groups = list(groups)

    def list_groups_with_links(self, *, include_disabled: bool = False):
        if include_disabled:
            return list(self._groups)
        return [group for group in self._groups if group.is_enabled]


class StubRequest:
    def __init__(
        self,
        *,
        path_params: Dict[str, str] | None = None,
        query_params: Dict[str, str] | None = None,
    ) -> None:
        self.path_params = path_params or {}
        self.query_params = query_params or {}
        self._url_for_calls: List[tuple[str, Dict[str, str]]] = []

    def url_for(self, name: str, **params: str) -> str:
        self._url_for_calls.append((name, params))
        if params:
            suffix = "_".join(f"{key}-{value}" for key,
                              value in sorted(params.items()))
            return f"/{name}/{suffix}"
        return f"/{name}"

    @property
    def url_for_calls(self) -> List[tuple[str, Dict[str, str]]]:
        return list(self._url_for_calls)


def _session(*, roles: Iterable[str], authenticated: bool = True) -> AuthSession:
    tokens = SessionTokens(
        access_token="token" if authenticated else None, refresh_token=None)
    user = cast(User, object()) if authenticated else None
    session = AuthSession(tokens=tokens, user=user, role_slugs=tuple(roles))
    return session


def test_build_sidebar_filters_links_by_role():
    visible_link = StubNavigationLink(
        id=1,
        slug="projects",
        label="Projects",
        href_override="/projects",
        required_roles=["viewer"],
    )
    hidden_link = StubNavigationLink(
        id=2,
        slug="admin",
        label="Admin",
        href_override="/admin",
        required_roles=["admin"],
    )
    group = StubNavigationGroup(id=1, slug="workspace", label="Workspace", links=[
                                visible_link, hidden_link])

    service = NavigationService(StubNavigationRepository([group]))
    dto = service.build_sidebar(
        session=_session(roles=["viewer"]),
        request=cast(Request, StubRequest()),
    )

    assert len(dto.groups) == 1
    assert [link.label for link in dto.groups[0].links] == ["Projects"]
    assert dto.roles == ("viewer",)


def test_build_sidebar_appends_anonymous_role_for_guests():
    link = StubNavigationLink(
        id=1, slug="help", label="Help", href_override="/help")
    group = StubNavigationGroup(
        id=1, slug="account", label="Account", links=[link])

    service = NavigationService(StubNavigationRepository([group]))
    dto = service.build_sidebar(session=AuthSession.anonymous(), request=None)

    assert dto.roles[-1] == "anonymous"
    assert dto.groups[0].links[0].href.startswith("/")


def test_build_sidebar_resolves_profitability_link_with_context():
    link = StubNavigationLink(
        id=1,
        slug="profitability",
        label="Profitability",
        route_name="calculations.profitability_form",
        href_override="/calculations/profitability",
    )
    group = StubNavigationGroup(
        id=99, slug="insights", label="Insights", links=[link])

    request = StubRequest(path_params={"project_id": "7", "scenario_id": "42"})
    service = NavigationService(StubNavigationRepository([group]))

    dto = service.build_sidebar(
        session=_session(roles=["viewer"]),
        request=cast(Request, request),
    )

    assert dto.groups[0].links[0].href == "/calculations.profitability_form/project_id-7_scenario_id-42"
    assert request.url_for_calls[0][0] == "calculations.profitability_form"
    assert request.url_for_calls[0][1] == {
        "project_id": "7", "scenario_id": "42"}
    assert dto.groups[0].links[0].match_prefix == dto.groups[0].links[0].href


def test_build_sidebar_resolves_opex_link_with_context():
    link = StubNavigationLink(
        id=2,
        slug="opex",
        label="Opex",
        route_name="calculations.opex_form",
        href_override="/calculations/opex",
    )
    group = StubNavigationGroup(
        id=5, slug="workspace", label="Workspace", links=[link])

    request = StubRequest(path_params={"project_id": "3", "scenario_id": "9"})
    service = NavigationService(StubNavigationRepository([group]))

    dto = service.build_sidebar(
        session=_session(roles=["analyst"]),
        request=cast(Request, request),
    )

    href = dto.groups[0].links[0].href
    assert href == "/calculations.opex_form/project_id-3_scenario_id-9"
    assert request.url_for_calls[0][0] == "calculations.opex_form"
    assert request.url_for_calls[0][1] == {
        "project_id": "3", "scenario_id": "9"}


def test_build_sidebar_uses_href_override_when_calculator_context_missing():
    class ParamAwareStubRequest(StubRequest):
        # type: ignore[override]
        def url_for(self, name: str, **params: str) -> str:
            if name in {
                "calculations.opex_form",
                "calculations.capex_form",
            } and not params:
                self._url_for_calls.append((name, params))
                raise RuntimeError("missing params")
            return super().url_for(name, **params)

    link = StubNavigationLink(
        id=3,
        slug="capex",
        label="Capex",
        route_name="calculations.capex_form",
        href_override="/calculations/capex",
    )
    group = StubNavigationGroup(
        id=6, slug="workspace", label="Workspace", links=[link])

    request = ParamAwareStubRequest()
    service = NavigationService(StubNavigationRepository([group]))

    dto = service.build_sidebar(
        session=_session(roles=["analyst"]),
        request=cast(Request, request),
    )

    assert dto.groups[0].links[0].href == "/calculations/capex"
    assert request.url_for_calls[-1][0] == "calculations.capex_form"


def test_build_sidebar_skips_disabled_links_unless_included():
    enabled_link = StubNavigationLink(
        id=1,
        slug="projects",
        label="Projects",
        href_override="/projects",
    )
    disabled_link = StubNavigationLink(
        id=2,
        slug="reports",
        label="Reports",
        href_override="/reports",
        is_enabled=False,
    )
    group = StubNavigationGroup(
        id=5,
        slug="workspace",
        label="Workspace",
        links=[enabled_link, disabled_link],
    )

    service = NavigationService(StubNavigationRepository([group]))

    default_sidebar = service.build_sidebar(
        session=_session(roles=["viewer"]),
        request=cast(Request, StubRequest()),
    )
    assert [link.label for link in default_sidebar.groups[0].links] == ["Projects"]

    full_sidebar = service.build_sidebar(
        session=_session(roles=["viewer"]),
        request=cast(Request, StubRequest()),
        include_disabled=True,
    )
    assert [link.label for link in full_sidebar.groups[0].links] == [
        "Projects", "Reports"]
