"""Read-only drift inventory for the Core → Cloud projection (rollout step 1).

Answers one question per project: if the projection were synced now, would any live Cloud
user's legacy `role` change? Nothing here writes to Core or Cloud. Classification:

SAFE     every projected user's Cloud role already equals the projected legacy role
DRIFT    one or more Cloud roles would change; do not sync until the cause is understood
BLOCKED  an invariant fails (owner missing or duplicated, unclassified authority role,
         missing Cloud identity, Cloud user not found); do not sync
"""

from __future__ import annotations

from dataclasses import dataclass, field
from uuid import UUID

import httpx

from src.domain.project_auth.cloud_projection import MembershipProjection

SAFE = "SAFE"
DRIFT = "DRIFT"
BLOCKED = "BLOCKED"


@dataclass(frozen=True)
class CloudUser:
    cloud_app_user_id: str
    email: str
    role: str | None


@dataclass(frozen=True)
class UserDrift:
    email: str
    cloud_app_user_id: str | None
    current_cloud_role: str | None
    projected_legacy_role: str
    project_roles: tuple[str, ...]
    excluded_roles: tuple[str, ...]
    is_owner: bool
    authorization_version: int
    role_would_change: bool


@dataclass(frozen=True)
class ProjectDrift:
    project_id: str
    classification: str
    reasons: tuple[str, ...]
    users: tuple[UserDrift, ...] = field(default_factory=tuple)


def classify_project(
    project_id: str,
    projections: list[MembershipProjection],
    cloud_users: list[CloudUser],
) -> ProjectDrift:
    reasons: list[str] = []
    owners = [p for p in projections if p.is_owner]
    if not owners:
        reasons.append("missing_owner")
    elif len(owners) > 1:
        reasons.append("multiple_owners")

    cloud_by_id = {u.cloud_app_user_id: u for u in cloud_users}
    users: list[UserDrift] = []
    for p in projections:
        if p.excluded_roles:
            reasons.append(f"unclassified_authority_role:{p.email}")
        cloud_user = cloud_by_id.get(p.cloud_app_user_id) if p.cloud_app_user_id else None
        if p.cloud_app_user_id is None:
            reasons.append(f"missing_cloud_identity:{p.email}")
        elif cloud_user is None:
            reasons.append(f"cloud_user_not_found:{p.email}")
        current = cloud_user.role if cloud_user is not None else None
        users.append(
            UserDrift(
                email=p.email,
                cloud_app_user_id=p.cloud_app_user_id,
                current_cloud_role=current,
                projected_legacy_role=p.legacy_role,
                project_roles=p.project_roles,
                excluded_roles=p.excluded_roles,
                is_owner=p.is_owner,
                authorization_version=p.authorization_version,
                role_would_change=cloud_user is not None and current != p.legacy_role,
            )
        )

    if reasons:
        classification = BLOCKED
    elif any(u.role_would_change for u in users):
        classification = DRIFT
    else:
        classification = SAFE
    return ProjectDrift(
        project_id=project_id,
        classification=classification,
        reasons=tuple(sorted(set(reasons))),
        users=tuple(users),
    )


async def fetch_cloud_users(
    base_url: str, internal_token: str, project_id: str, *, transport=None
) -> list[CloudUser]:
    """Read the project's Cloud users through the existing admin list route. Read-only."""
    users: list[CloudUser] = []
    cursor: str | None = None
    async with httpx.AsyncClient(transport=transport, timeout=20) as client:
        while True:
            params = {"limit": 100}
            if cursor:
                params["cursor"] = cursor
            response = await client.get(
                f"{base_url.rstrip('/')}/v1/projects/{project_id}/auth/users",
                params=params,
                headers={"Authorization": f"Bearer {internal_token}"},
            )
            response.raise_for_status()
            body = response.json()
            for row in body.get("users") or body.get("data") or []:
                users.append(
                    CloudUser(
                        cloud_app_user_id=str(row.get("id")),
                        email=(row.get("email") or "").strip().lower(),
                        role=row.get("role"),
                    )
                )
            cursor = body.get("next_cursor")
            if not cursor:
                return users


async def inventory_project(
    db, project_id: UUID, base_url: str, internal_token: str
) -> ProjectDrift:
    """Load canonical state and classify one project. Only SELECTs and one Cloud GET."""
    from src.domain.project_auth.cloud_projection import build_project_projections
    from src.domain.project_auth.cloud_projection_source import load_project_snapshot

    snapshot = await load_project_snapshot(db, project_id)
    projections = build_project_projections(snapshot)
    cloud_users = await fetch_cloud_users(base_url, internal_token, str(project_id))
    return classify_project(str(project_id), projections, cloud_users)
