"""Owner and admin backfill for existing projects (rollout step 2).

Plans and applies the canonical membership reconciliation for one project, using the
workspace as the source and the project's live Cloud user list for identity links.

A project is READY only when all of these hold:
- exactly one desired owner comes from the workspace;
- the planner raises no blocks and no legacy holds need a human decision;
- every desired identity (owner, admins, and members) has a Cloud user in this project,
  so the membership can be linked and the drift check can compare roles.

Only READY projects are applied. Callers decide which projects to apply, and the apply
step re-plans first so a stale plan cannot be written.
"""

from __future__ import annotations

from dataclasses import dataclass
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from src.domain.project_auth.drift_report import CloudUser
from src.domain.project_auth.membership_reconciliation import Plan, normalize_email

READY = "READY"
BLOCKED = "BLOCKED"


@dataclass(frozen=True)
class ProjectBackfill:
    project_id: str
    classification: str
    reasons: tuple[str, ...]
    desired_owner: str | None = None
    plan: Plan | None = None
    applied_operations: int = 0


def _classify(
    owners: list[str], identities_emails: list[str], cloud_ids: dict[str, str], plan: Plan
) -> tuple[str, tuple[str, ...]]:
    reasons: list[str] = []
    if len(owners) != 1:
        reasons.append(f"desired_owner_count_{len(owners)}")
    for email in identities_emails:
        if normalize_email(email) not in cloud_ids:
            reasons.append(f"cloud_user_missing:{normalize_email(email)}")
    reasons.extend(plan.blocked)
    classification = BLOCKED if reasons else READY
    return classification, tuple(sorted(set(reasons)))


async def plan_backfill(
    db: AsyncSession, project_id: UUID, cloud_users: list[CloudUser]
) -> ProjectBackfill:
    """Read-only: compute the plan and its readiness. Writes nothing."""
    from src.domain.project_auth.membership_projection import (
        plan_project_memberships,
        workspace_identities_for_project,
    )

    identities = await workspace_identities_for_project(db, project_id)
    cloud_ids = {normalize_email(u.email): u.cloud_app_user_id for u in cloud_users}
    owners = [i.email for i in identities if i.is_owner]
    plan = await plan_project_memberships(db, project_id, identities, cloud_user_ids=cloud_ids)
    classification, reasons = _classify(owners, [i.email for i in identities], cloud_ids, plan)
    return ProjectBackfill(
        project_id=str(project_id),
        classification=classification,
        reasons=reasons,
        desired_owner=owners[0] if len(owners) == 1 else None,
        plan=plan,
    )


async def apply_backfill(
    db: AsyncSession, project_id: UUID, cloud_users: list[CloudUser]
) -> ProjectBackfill:
    """Re-plan, and apply only if the project is READY. Caller commits (one project per commit)."""
    from src.domain.project_auth.membership_projection import apply_project_memberships

    result = await plan_backfill(db, project_id, cloud_users)
    if result.classification != READY or result.plan is None:
        return result
    applied = await apply_project_memberships(db, project_id, result.plan)
    return ProjectBackfill(
        project_id=result.project_id,
        classification=READY,
        reasons=(),
        desired_owner=result.desired_owner,
        plan=result.plan,
        applied_operations=applied.operations_applied,
    )
