"""Read-only planning for the authorization v2 owner and membership backfill (rollout step 2).

For each auth-configured project, computes the Core operations the canonical planner would
apply to converge memberships with the workspace (owner, admins, members), and prints them.
Nothing is written: the planner only reads, and the session is rolled back at the end.

Classification per project (see src/domain/project_auth/backfill.py):
READY    exactly one workspace owner, no planner blocks, and every workspace identity has a
         Cloud user in the project (so memberships can be linked)
BLOCKED  anything else, with the reason listed

Usage (P2HS-BE root, same .env as the API):

    python -m scripts.authz_v2_backfill_plan
    python -m scripts.authz_v2_backfill_plan --project-id <uuid>
    python -m scripts.authz_v2_backfill_plan --json > backfill_plan.json

Exit code is 0 when every planned project is READY, 1 otherwise.
"""

from __future__ import annotations

import argparse
import asyncio
import json
import sys
from collections import Counter
from uuid import UUID

from src.core.orm_import import import_all_orm_models

import_all_orm_models()

READY = "READY"
BLOCKED = "BLOCKED"


async def _project_ids(db, only: UUID | None) -> list[UUID]:
    from sqlalchemy import select
    from src.domain.project_auth.models import ProjectAuthConfig

    if only is not None:
        return [only]
    query = select(ProjectAuthConfig.project_id).order_by(ProjectAuthConfig.project_id)
    return list((await db.execute(query)).scalars().all())


async def _plan_one(db, project_id: UUID, base_url: str, token: str) -> dict:
    from src.domain.project_auth.backfill import plan_backfill
    from src.domain.project_auth.drift_report import fetch_cloud_users

    cloud_users = await fetch_cloud_users(base_url, token, str(project_id))
    result = await plan_backfill(db, project_id, cloud_users)
    plan = result.plan
    kinds = Counter(str(op.kind) for op in plan.operations)
    return {
        "project_id": str(project_id),
        "classification": result.classification,
        "reasons": list(result.reasons),
        "desired_owner": result.desired_owner,
        "cloud_users": len(cloud_users),
        "operations": dict(kinds),
        "held": list(plan.held),
        "changes": [
            {"kind": str(op.kind), "email": op.email,
             "role_id": str(op.role_id) if op.role_id else None,
             "source": op.source, "value": op.value}
            for op in plan.operations
        ],
    }


async def _run(only: UUID | None, as_json: bool) -> int:
    from src.config import get_settings
    from src.core.database import async_session_factory

    settings = get_settings()
    if not settings.cloud_control_api_url or not settings.cloud_internal_token:
        print("[error] cloud_control_api_url and cloud_internal_token must be set", file=sys.stderr)
        return 2

    results: list[dict] = []
    async with async_session_factory() as db:
        try:
            for project_id in await _project_ids(db, only):
                try:
                    results.append(await _plan_one(db, project_id, settings.cloud_control_api_url,
                                                    settings.cloud_internal_token))
                except Exception as exc:  # one project's failure must not hide the others
                    results.append({
                        "project_id": str(project_id),
                        "classification": BLOCKED,
                        "reasons": [f"error:{type(exc).__name__}:{str(exc)[:160]}"],
                    })
        finally:
            await db.rollback()  # belt and braces: nothing here may persist

    if as_json:
        print(json.dumps(results, indent=2))
    else:
        for r in results:
            ops = ", ".join(f"{k}={v}" for k, v in sorted(r.get("operations", {}).items())) or "-"
            print(f"{r['project_id']}  {r['classification']}  owner={r.get('desired_owner') or '-'}"
                  f"  ops[{ops}]  {', '.join(r['reasons']) or '-'}")
            for c in r.get("changes", []):
                print(f"    {c['kind']} {c['email']}"
                      + (f" source={c['source']}" if c["source"] else "")
                      + (f" value={c['value']}" if c["value"] is not None else ""))
        ready = sum(1 for r in results if r["classification"] == READY)
        print(f"\n{len(results)} project(s) planned, {ready} READY; nothing was written.")

    return 0 if results and all(r["classification"] == READY for r in results) else 1


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    parser.add_argument("--project-id", type=UUID, default=None)
    parser.add_argument("--json", action="store_true")
    args = parser.parse_args()
    sys.exit(asyncio.run(_run(args.project_id, args.json)))


if __name__ == "__main__":
    main()
