"""Read-only audit for W4P cutover and legacy Pages retirement.

Examples (run on the API server):

  python scripts/audit_w4p_migration.py --all --output w4p-audit.json
  python scripts/audit_w4p_migration.py --project PROJECT_UUID --output w4p-audit.json

The output is the only input accepted by delete_cf_pages_projects.py.  This
script never changes Core, Cloudflare, GitHub, or routing state.
"""
from __future__ import annotations

import argparse
import asyncio
import json
import sys
from datetime import UTC, datetime
from pathlib import Path
from uuid import UUID

import httpx
from sqlalchemy import select

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))


def _latest(rows, environment: str):
    candidates = [row for row in rows if row.environment == environment]
    return max(candidates, key=lambda row: row.created_at or datetime.min.replace(tzinfo=UTC), default=None)


def _absolute_url(value: str | None) -> str | None:
    raw = (value or "").strip()
    if not raw:
        return None
    return raw if "://" in raw else f"https://{raw}"


async def _health(client: httpx.AsyncClient, url: str | None, project_id: str) -> dict:
    absolute = _absolute_url(url)
    if not absolute:
        return {"ok": False, "reason": "hostname_missing"}
    endpoint = absolute.rstrip("/") + "/api/__p2hs/health"
    try:
        response = await client.get(endpoint, follow_redirects=True)
        body = response.json()
    except Exception as exc:
        return {"ok": False, "url": endpoint, "reason": type(exc).__name__}
    ok = (
        response.status_code == 200
        and body.get("ok") is True
        and body.get("bff") is True
        and body.get("project_id") == project_id
        and body.get("runtime") == "p2hs-application-runtime"
    )
    return {"ok": ok, "url": endpoint, "status": response.status_code, "body": body}


async def audit(project_ids: list[str] | None, workspace_ids: list[str] | None) -> dict:
    from src.config import get_settings
    from src.core.database import async_session_factory
    from src.domain.deployments.models import ProjectDeploy, ProjectDeploymentTarget
    from src.domain.hosting.hostnames import canonical_live_url, canonical_preview_url
    from src.domain.hosting.project_provider import W4P_STATIC
    from src.domain.projects.models import Project
    from src.domain.workspaces.models import Workspace

    project_filter = {UUID(value) for value in project_ids or []}
    workspace_filter = {UUID(value) for value in workspace_ids or []}
    async with async_session_factory() as db:
        projects = list((await db.execute(
            select(Project).where(Project.deleted_at.is_(None)).order_by(Project.created_at.asc())
        )).scalars())
        if workspace_filter:
            projects = [row for row in projects if row.workspace_id in workspace_filter]
        if project_filter:
            projects = [row for row in projects if row.id in project_filter]
        project_ids_found = {row.id for row in projects}
        deploys = list((await db.execute(
            select(ProjectDeploy).where(ProjectDeploy.project_id.in_(project_ids_found))
        )).scalars()) if project_ids_found else []
        targets = list((await db.execute(
            select(ProjectDeploymentTarget).where(ProjectDeploymentTarget.project_id.in_(project_ids_found))
        )).scalars()) if project_ids_found else []
        workspaces = {
            row.id: row.slug
            for row in (await db.execute(select(Workspace))).scalars()
        }

    deploy_by_project: dict[UUID, list] = {}
    target_by_project: dict[UUID, list] = {}
    for row in deploys:
        deploy_by_project.setdefault(row.project_id, []).append(row)
    for row in targets:
        target_by_project.setdefault(row.project_id, []).append(row)

    settings = get_settings()
    apex = settings.hosting_public_apex or "p2hsapp.com"
    timeout = httpx.Timeout(15.0)
    async with httpx.AsyncClient(timeout=timeout) as client:
        results = []
        for project in projects:
            rows = deploy_by_project.get(project.id, [])
            prod = _latest(rows, "production")
            preview = _latest(rows, "preview")
            project_targets = target_by_project.get(project.id, [])
            page_names = sorted({
                row.provider_project_name
                for row in project_targets
                if row.provider_project_name
            })
            health = {
                "production": await _health(
                    client,
                    project.live_hostname or (canonical_live_url(project.project_ref, apex) if project.project_ref else None),
                    str(project.id),
                ),
                "preview": await _health(
                    client,
                    project.preview_hostname or (canonical_preview_url(project.project_ref, apex) if project.project_ref else None),
                    str(project.id),
                ),
            }
            provider_ok = (project.primary_hosting_provider or "").lower() == W4P_STATIC
            deploy_ok = all(
                row is not None
                and row.status == "live"
                and (row.hosting_provider or "").lower() == W4P_STATIC
                and row.activation_status == "live"
                and bool(row.provider_namespace)
                and bool(row.provider_version_id)
                for row in (prod, preview)
            )
            # Older Pages rows may remain historically marked live after a
            # successful W4P activation. Only the latest row for each
            # environment represents the active deployment.
            legacy_live = [
                {
                    "environment": row.environment,
                    "id": str(row.id),
                    "hosting_provider": row.hosting_provider,
                    "status": row.status,
                }
                for row in (prod, preview)
                if row is not None
                and row.status == "live"
                and (row.hosting_provider or "").lower() in {"cloudflare_pages", "cloudflare"}
            ]
            safe = provider_ok and deploy_ok and all(item["ok"] for item in health.values()) and not legacy_live
            blockers = []
            if not provider_ok:
                blockers.append("project_not_marked_w4p")
            if prod is None or prod.status != "live" or (prod.hosting_provider or "").lower() != W4P_STATIC or prod.activation_status != "live" or not prod.provider_namespace or not prod.provider_version_id:
                blockers.append("production_not_w4p_live")
            if preview is None or preview.status != "live" or (preview.hosting_provider or "").lower() != W4P_STATIC or preview.activation_status != "live" or not preview.provider_namespace or not preview.provider_version_id:
                blockers.append("preview_not_w4p_live")
            for environment, result in health.items():
                if not result["ok"]:
                    blockers.append(f"{environment}_health_failed:{result.get('reason') or result.get('status')}")
            if legacy_live:
                blockers.append("latest_deployment_still_pages")
            results.append({
                "workspace_id": str(project.workspace_id),
                "workspace_slug": workspaces.get(project.workspace_id),
                "project_id": str(project.id),
                "project_ref": project.project_ref,
                "name": project.name,
                "primary_hosting_provider": project.primary_hosting_provider,
                "pages_project_names": page_names,
                "latest_deployments": {
                    "production": _deployment_summary(prod),
                    "preview": _deployment_summary(preview),
                },
                "legacy_live_deployments": legacy_live,
                "health": health,
                "blockers": blockers,
                "safe_to_delete_pages": safe,
                "status": "W4P_READY_TO_RETIRE_PAGES" if safe else "W4P_NOT_READY",
            })
    return {
        "audited_at": datetime.now(UTC).isoformat(),
        "projects": results,
        "safe_count": sum(row["safe_to_delete_pages"] for row in results),
        "blocked_count": sum(not row["safe_to_delete_pages"] for row in results),
    }


def _deployment_summary(row) -> dict | None:
    if row is None:
        return None
    return {
        "id": str(row.id),
        "status": row.status,
        "hosting_provider": row.hosting_provider,
        "activation_status": row.activation_status,
        "provider_namespace": row.provider_namespace,
        "provider_version_id": row.provider_version_id,
        "source_git_sha": row.source_git_sha,
        "created_at": row.created_at.isoformat() if row.created_at else None,
    }


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    scope = parser.add_mutually_exclusive_group(required=True)
    scope.add_argument("--all", action="store_true", help="Audit all non-deleted projects.")
    scope.add_argument("--project", action="append", dest="project_ids", help="Project UUID; repeatable.")
    scope.add_argument("--workspace", action="append", dest="workspace_ids", help="Workspace UUID; repeatable.")
    parser.add_argument("--output", default="w4p-audit.json")
    args = parser.parse_args()
    result = asyncio.run(audit(args.project_ids, args.workspace_ids))
    Path(args.output).write_text(json.dumps(result, indent=2, default=str) + "\n", encoding="utf-8")
    print(json.dumps({"output": args.output, "safe_count": result["safe_count"], "blocked_count": result["blocked_count"]}, indent=2))
    raise SystemExit(0 if result["blocked_count"] == 0 else 2)


if __name__ == "__main__":
    main()
