from __future__ import annotations

from datetime import datetime
from typing import List, Optional

from sqlalchemy import (
    Boolean,
    CheckConstraint,
    DateTime,
    ForeignKey,
    Index,
    Integer,
    String,
    UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql import func
from sqlalchemy.ext.mutable import MutableList
from sqlalchemy import JSON

from config.database import Base


class NavigationGroup(Base):
    __tablename__ = "navigation_groups"
    __table_args__ = (
        UniqueConstraint("slug", name="uq_navigation_groups_slug"),
        Index("ix_navigation_groups_sort_order", "sort_order"),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    slug: Mapped[str] = mapped_column(String(64), nullable=False)
    label: Mapped[str] = mapped_column(String(128), nullable=False)
    sort_order: Mapped[int] = mapped_column(
        Integer, nullable=False, default=100)
    icon: Mapped[Optional[str]] = mapped_column(String(64))
    tooltip: Mapped[Optional[str]] = mapped_column(String(255))
    is_enabled: Mapped[bool] = mapped_column(
        Boolean, nullable=False, default=True)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
    )

    links: Mapped[List["NavigationLink"]] = relationship(
        "NavigationLink",
        back_populates="group",
        cascade="all, delete-orphan",
        order_by="NavigationLink.sort_order",
    )

    def __repr__(self) -> str:  # pragma: no cover
        return f"NavigationGroup(id={self.id!r}, slug={self.slug!r})"


class NavigationLink(Base):
    __tablename__ = "navigation_links"
    __table_args__ = (
        UniqueConstraint("group_id", "slug",
                         name="uq_navigation_links_group_slug"),
        Index("ix_navigation_links_group_sort", "group_id", "sort_order"),
        Index("ix_navigation_links_parent_sort",
              "parent_link_id", "sort_order"),
        CheckConstraint(
            "(route_name IS NOT NULL) OR (href_override IS NOT NULL)",
            name="ck_navigation_links_route_or_href",
        ),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    group_id: Mapped[int] = mapped_column(
        ForeignKey("navigation_groups.id", ondelete="CASCADE"), nullable=False
    )
    parent_link_id: Mapped[Optional[int]] = mapped_column(
        ForeignKey("navigation_links.id", ondelete="CASCADE")
    )
    slug: Mapped[str] = mapped_column(String(64), nullable=False)
    label: Mapped[str] = mapped_column(String(128), nullable=False)
    route_name: Mapped[Optional[str]] = mapped_column(String(128))
    href_override: Mapped[Optional[str]] = mapped_column(String(512))
    match_prefix: Mapped[Optional[str]] = mapped_column(String(512))
    sort_order: Mapped[int] = mapped_column(
        Integer, nullable=False, default=100)
    icon: Mapped[Optional[str]] = mapped_column(String(64))
    tooltip: Mapped[Optional[str]] = mapped_column(String(255))
    required_roles: Mapped[list[str]] = mapped_column(
        MutableList.as_mutable(JSON), nullable=False, default=list
    )
    is_enabled: Mapped[bool] = mapped_column(
        Boolean, nullable=False, default=True)
    is_external: Mapped[bool] = mapped_column(
        Boolean, nullable=False, default=False)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
    )

    group: Mapped[NavigationGroup] = relationship(
        NavigationGroup,
        back_populates="links",
    )
    parent: Mapped[Optional["NavigationLink"]] = relationship(
        "NavigationLink",
        remote_side="NavigationLink.id",
        back_populates="children",
    )
    children: Mapped[List["NavigationLink"]] = relationship(
        "NavigationLink",
        back_populates="parent",
        cascade="all, delete-orphan",
        order_by="NavigationLink.sort_order",
    )

    def is_visible_for_roles(self, roles: list[str]) -> bool:
        if not self.required_roles:
            return True
        role_set = set(roles)
        return any(role in role_set for role in self.required_roles)

    def __repr__(self) -> str:  # pragma: no cover
        return f"NavigationLink(id={self.id!r}, slug={self.slug!r})"
