"""Users, groups and login sessions."""

from __future__ import annotations

from datetime import datetime
from typing import TYPE_CHECKING, Any

from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Index, String, Table, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship

from lembas.db.base import Base, Timestamps, UUIDPrimaryKey
from lembas.db.types import JSONDict

if TYPE_CHECKING:
    # Annotation only; SQLAlchemy resolves the real class from its registry.
    from lembas.db.models.connection import Model
    from lembas.db.models.tool import CustomTool, McpServer

# Roles are a simple ordered ladder rather than a permission matrix. Groups
# (below) carry finer-grained permissions once the users/groups UI lands.
ROLE_ADMIN = "admin"
ROLE_USER = "user"
ROLE_PENDING = "pending"  # registered but awaiting admin approval

user_groups = Table(
    "user_groups",
    Base.metadata,
    Column("user_id", String(32), ForeignKey("users.id", ondelete="CASCADE"), primary_key=True),
    Column("group_id", String(32), ForeignKey("groups.id", ondelete="CASCADE"), primary_key=True),
)


class User(UUIDPrimaryKey, Timestamps, Base):
    __tablename__ = "users"

    email: Mapped[str] = mapped_column(String(320), unique=True, nullable=False, index=True)
    name: Mapped[str] = mapped_column(String(120), nullable=False)
    password_hash: Mapped[str] = mapped_column(Text, nullable=False)
    role: Mapped[str] = mapped_column(String(16), default=ROLE_USER, nullable=False)
    active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
    last_login_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))

    # Per-user preferences: theme, default model, composer behaviour, etc.
    settings_json: Mapped[dict[str, Any]] = mapped_column(JSONDict, default=dict)

    groups: Mapped[list[Group]] = relationship(secondary=user_groups, back_populates="users")
    sessions: Mapped[list[Session]] = relationship(
        back_populates="user", cascade="all, delete-orphan"
    )

    @property
    def is_admin(self) -> bool:
        return self.role == ROLE_ADMIN

    def __repr__(self) -> str:
        return f"<User {self.email} role={self.role}>"


class Group(UUIDPrimaryKey, Timestamps, Base):
    """A named set of users. Permissions are enforced once the RBAC pass lands."""

    __tablename__ = "groups"

    name: Mapped[str] = mapped_column(String(120), unique=True, nullable=False)
    description: Mapped[str] = mapped_column(Text, default="")

    # Only the granted keys need be present. Absent means "no opinion", not
    # "deny" -- permissions union across a user's groups. See
    # lembas.security.permissions.
    permissions_json: Mapped[dict[str, Any]] = mapped_column(JSONDict, default=dict)

    users: Mapped[list[User]] = relationship(secondary=user_groups, back_populates="groups")
    models: Mapped[list[Model]] = relationship(
        "Model", secondary="model_groups", back_populates="groups"
    )
    custom_tools: Mapped[list[CustomTool]] = relationship(
        "CustomTool", secondary="custom_tool_groups", back_populates="groups"
    )
    mcp_servers: Mapped[list[McpServer]] = relationship(
        "McpServer", secondary="mcp_server_groups", back_populates="groups"
    )


class Session(UUIDPrimaryKey, Timestamps, Base):
    """Server-side login session.

    Sessions live in the database rather than in a signed JWT so that logging
    out, banning a user, or rotating a device actually revokes access
    immediately instead of waiting for a token to expire.
    """

    __tablename__ = "sessions"

    user_id: Mapped[str] = mapped_column(
        String(32), ForeignKey("users.id", ondelete="CASCADE"), nullable=False
    )
    # SHA-256 of the cookie value. The raw token is shown to the browser once
    # and never stored, so a database leak does not hand over live sessions.
    token_hash: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
    expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
    user_agent: Mapped[str] = mapped_column(Text, default="")
    ip_address: Mapped[str] = mapped_column(String(45), default="")

    user: Mapped[User] = relationship(back_populates="sessions")


Index("ix_sessions_user_id", Session.user_id)
