"""Read-only authorization v2 drift inventory across projects (rollout step 2).

For each project with auth configured, loads canonical Core state, reads the Cloud user
list, and prints one line per project: SAFE, DRIFT, or BLOCKED, with the reasons and the
users whose Cloud role would change. Writes nothing to Core or Cloud and does not change
projection_state.

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

    python -m scripts.authz_v2_drift_inventory                    # every auth-configured project
    python -m scripts.authz_v2_drift_inventory --project-id <uuid>
    python -m scripts.authz_v2_drift_inventory --json > drift.json

Exit code is 0 when every inventoried project is SAFE, 1 otherwise.
"""

from __future__ import annotations

import argparse
import asyncio
import json
import sys
from dataclasses import asdict
from uuid import UUID

from src.core.orm_import import import_all_orm_models

import_all_orm_models()


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 _run(only: UUID | None, as_json: bool) -> int:
    from src.config import get_settings
    from src.core.database import async_session_factory
    from src.domain.project_auth.drift_report import SAFE, inventory_project

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

    results = []
    async with async_session_factory() as db:
        ids = await _project_ids(db, only)
        for project_id in ids:
            try:
                results.append(await inventory_project(db, project_id, base_url, token))
            except Exception as exc:  # one project's failure must not hide the others
                results.append({"project_id": str(project_id), "classification": "ERROR",
                                "reasons": [type(exc).__name__ + ": " + str(exc)[:200]]})

    if as_json:
        print(json.dumps([r if isinstance(r, dict) else asdict(r) for r in results], indent=2))
    else:
        for r in results:
            if isinstance(r, dict):
                print(f"{r['project_id']}  ERROR  {r['reasons'][0]}")
                continue
            print(f"{r.project_id}  {r.classification}  {', '.join(r.reasons) or '-'}")
            for u in r.users:
                if u.role_would_change:
                    print(f"    change {u.email}: cloud={u.current_cloud_role} "
                          f"projected={u.projected_legacy_role}")
        print(f"\n{len(results)} project(s) inventoried; nothing was written.")

    all_safe = all(not isinstance(r, dict) and r.classification == SAFE for r in results)
    return 0 if all_safe and 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()
