"""Access control service.

Single source of truth for permission resolution on projects and notes.
All human-facing routes that should honour shares call these functions.
LLM tool routes continue to use owner-scoped service functions directly.

Permission rank: owner > admin > editor > viewer
"""

import logging

from sqlalchemy import select

from fabledassistant.models import async_session
from fabledassistant.models.group import GroupMembership
from fabledassistant.models.note import Note
from fabledassistant.models.project import Project
from fabledassistant.models.share import NoteShare, ProjectShare

logger = logging.getLogger(__name__)

PERMISSION_RANK: dict[str, int] = {
    "viewer": 1,
    "editor": 2,
    "admin": 3,
    "owner": 4,
}


def _higher(a: str | None, b: str | None) -> str | None:
    """Return the higher-ranked permission, or None if both are None."""
    if a is None:
        return b
    if b is None:
        return a
    return a if PERMISSION_RANK[a] >= PERMISSION_RANK[b] else b


async def _user_group_ids(session, user_id: int) -> list[int]:
    rows = (
        await session.execute(
            select(GroupMembership.group_id).where(GroupMembership.user_id == user_id)
        )
    ).scalars().all()
    return list(rows)


# ---------------------------------------------------------------------------
# Project permissions
# ---------------------------------------------------------------------------

async def get_project_permission(user_id: int, project_id: int) -> str | None:
    """Return the effective permission string for user on project, or None."""
    async with async_session() as session:
        project = await session.get(Project, project_id)
        if project is None:
            return None
        if project.user_id == user_id:
            return "owner"

        # Direct share
        direct = (
            await session.execute(
                select(ProjectShare).where(
                    ProjectShare.project_id == project_id,
                    ProjectShare.shared_with_user_id == user_id,
                )
            )
        ).scalar_one_or_none()

        # Group shares
        group_ids = await _user_group_ids(session, user_id)
        group_perm: str | None = None
        if group_ids:
            group_shares = (
                await session.execute(
                    select(ProjectShare).where(
                        ProjectShare.project_id == project_id,
                        ProjectShare.shared_with_group_id.in_(group_ids),
                    )
                )
            ).scalars().all()
            for gs in group_shares:
                group_perm = _higher(group_perm, gs.permission)

        return _higher(direct.permission if direct else None, group_perm)


async def can_read_project(user_id: int, project_id: int) -> bool:
    return (await get_project_permission(user_id, project_id)) is not None


async def can_write_project(user_id: int, project_id: int) -> bool:
    perm = await get_project_permission(user_id, project_id)
    return perm in ("editor", "admin", "owner")


async def can_admin_project(user_id: int, project_id: int) -> bool:
    perm = await get_project_permission(user_id, project_id)
    return perm in ("admin", "owner")


# ---------------------------------------------------------------------------
# Note / task permissions
# ---------------------------------------------------------------------------

async def get_note_permission(user_id: int, note_id: int) -> str | None:
    """Return the effective permission for user on a note/task, or None.

    Resolution order:
    1. Ownership
    2. Direct note share
    3. Group-based note share
    4. Inherited from project share (if note belongs to a project)
    Highest rank wins.
    """
    async with async_session() as session:
        note = await session.get(Note, note_id)
        if note is None:
            return None
        if note.user_id == user_id:
            return "owner"

        direct = (
            await session.execute(
                select(NoteShare).where(
                    NoteShare.note_id == note_id,
                    NoteShare.shared_with_user_id == user_id,
                )
            )
        ).scalar_one_or_none()

        group_ids = await _user_group_ids(session, user_id)
        group_perm: str | None = None
        if group_ids:
            group_shares = (
                await session.execute(
                    select(NoteShare).where(
                        NoteShare.note_id == note_id,
                        NoteShare.shared_with_group_id.in_(group_ids),
                    )
                )
            ).scalars().all()
            for gs in group_shares:
                group_perm = _higher(group_perm, gs.permission)

        note_perm = _higher(direct.permission if direct else None, group_perm)

        # Inherit from project if note belongs to one
        if note.project_id is not None:
            project_perm = await get_project_permission(user_id, note.project_id)
            note_perm = _higher(note_perm, project_perm)

        return note_perm


async def can_read_note(user_id: int, note_id: int) -> bool:
    return (await get_note_permission(user_id, note_id)) is not None


async def can_write_note(user_id: int, note_id: int) -> bool:
    perm = await get_note_permission(user_id, note_id)
    return perm in ("editor", "admin", "owner")
