"""Load trusted authorization snapshots from canonical Core state (design §3, §4.1 step 11).

Everything the engine decides on comes from these rows: membership status and owner
flag, the permissions and assignment tiers of the membership's roles, and the role
definitions themselves. Nothing is accepted from a request or a token.
"""

from __future__ import annotations

from dataclasses import dataclass
from uuid import UUID

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.domain.project_auth.authorization import (
    Actor,
    AssignmentTier,
    MembershipState,
    RoleDescriptor,
    Target,
    permissions_from_roles,
)
from src.domain.project_auth.models import (
    ProjectAuthRole,
    ProjectMembership,
    ProjectMembershipRole,
    ProjectMembershipSuspension,
)


@dataclass(frozen=True)
class MembershipView:
    project_id: UUID
    membership_id: UUID
    state: MembershipState
    is_owner: bool
    permissions: frozenset[str]
    holds_admin: bool

    def as_actor(self) -> Actor:
        return Actor(self.project_id, self.membership_id, self.state, self.permissions)

    def as_target(self) -> Target:
        return Target(
            self.project_id, self.membership_id, self.state, self.is_owner, self.holds_admin
        )


def _text(value) -> str | None:
    """Enum columns load as plain strings or enum members depending on the mapping."""
    return None if value is None else getattr(value, "value", value)


async def load_membership(
    db: AsyncSession, project_id: UUID, membership_id: UUID
) -> MembershipView | None:
    membership = (
        await db.execute(
            select(ProjectMembership).where(
                ProjectMembership.id == membership_id,
                ProjectMembership.project_id == project_id,
            )
        )
    ).scalar_one_or_none()
    if membership is None:
        return None

    roles = (
        await db.execute(
            select(ProjectAuthRole)
            .join(ProjectMembershipRole, ProjectMembershipRole.role_id == ProjectAuthRole.id)
            .where(
                ProjectMembershipRole.membership_id == membership_id,
                ProjectAuthRole.project_id == project_id,
            )
        )
    ).scalars().all()

    held = (
        await db.execute(
            select(ProjectMembershipSuspension.membership_id).where(
                ProjectMembershipSuspension.membership_id == membership_id
            )
        )
    ).first()

    granted = [list(r.permissions_json or []) for r in roles]
    if membership.is_owner:
        # Ownership is a relation, not a binding (D14): the owner holds the protected role's bundle.
        protected = (
            await db.execute(
                select(ProjectAuthRole.permissions_json).where(
                    ProjectAuthRole.project_id == project_id,
                    ProjectAuthRole.protected.is_(True),
                )
            )
        ).scalars().all()
        granted.extend(list(p or []) for p in protected)

    return MembershipView(
        project_id=project_id,
        membership_id=membership.id,
        state=MembershipState.suspended if held else MembershipState.active,
        is_owner=bool(membership.is_owner),
        permissions=permissions_from_roles(granted),
        holds_admin=any(_text(r.assignment_tier) == AssignmentTier.admin.value for r in roles),
    )


async def load_role(db: AsyncSession, project_id: UUID, role_id: UUID) -> RoleDescriptor | None:
    row = (
        await db.execute(
            select(ProjectAuthRole).where(
                ProjectAuthRole.id == role_id,
                ProjectAuthRole.project_id == project_id,
            )
        )
    ).scalar_one_or_none()
    if row is None:
        return None
    return RoleDescriptor(
        role_id=row.id,
        assignment_tier=(
            AssignmentTier(_text(row.assignment_tier)) if row.assignment_tier is not None else None
        ),
        protected=bool(row.protected),
        permissions=frozenset(row.permissions_json or []),
    )
