"""Archive the legacy shared Flora project inventory without mutating Flora."""

from __future__ import annotations

import argparse
import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import requests

from recoil.core.paths import ProjectPaths
from recoil.execution.providers.flora_projects import _cache_update, _projects_path

_BASE_URL = "https://app.flora.ai/api/v1"
_USER_AGENT = "recoil-pipeline/1.0"
_REASON = "per-episode projects supersede shared scratch (SYNTHESIS d.19)"
_NODE_FIELDS = ("node_id", "type", "asset_id", "url", "label", "width", "height")


class FloraArchiveError(RuntimeError):
    """Raised when the Flora inventory cannot be completed."""


def _utc_now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def _today_utc() -> str:
    return datetime.now(timezone.utc).date().isoformat()


def _headers() -> dict[str, str]:
    return {
        "Authorization": f"Bearer {os.environ.get('FLORA_API_KEY', '')}",
        "Content-Type": "application/json",
        "User-Agent": _USER_AGENT,
    }


def _get_json(url: str) -> Any:
    try:
        resp = requests.get(url, headers=_headers(), timeout=30)
    except requests.RequestException as exc:
        raise FloraArchiveError(f"Flora inventory request failed: {exc}") from exc

    if resp.status_code >= 400:
        raise FloraArchiveError(
            f"Flora inventory request failed: GET {url} -> "
            f"HTTP {resp.status_code}: {resp.text[:500]}"
        )

    try:
        return resp.json()
    except ValueError as exc:
        raise FloraArchiveError(
            f"Flora inventory response was not JSON: GET {url}"
        ) from exc


def _coerce_nodes(data: Any) -> list[dict[str, Any]]:
    if isinstance(data, list):
        raw_nodes = data
    elif isinstance(data, dict):
        raw_nodes = []
        for key in ("nodes", "items", "results", "data"):
            value = data.get(key)
            if isinstance(value, list):
                raw_nodes = value
                break
            if isinstance(value, dict):
                nested_nodes = _coerce_nodes(value)
                if nested_nodes:
                    raw_nodes = nested_nodes
                    break
        if not raw_nodes:
            nested = data.get("result")
            if isinstance(nested, dict):
                raw_nodes = _coerce_nodes(nested)
    else:
        raw_nodes = []

    nodes: list[dict[str, Any]] = []
    for node in raw_nodes:
        if not isinstance(node, dict):
            continue
        nodes.append({key: node[key] for key in _NODE_FIELDS if key in node})
    return nodes


def inventory_flora_project(project_id: str) -> dict[str, Any]:
    _get_json(f"{_BASE_URL}/projects/{project_id}")
    # GET /projects/{id}/nodes is the REST path behind the SDK's
    # projects.listNodes — LIVE-VERIFIED 2026-06-11 (HTTP 200, sanitized
    # node list {node_id,type,label,url,...}) against app.flora.ai.
    nodes = _coerce_nodes(_get_json(f"{_BASE_URL}/projects/{project_id}/nodes"))
    return {"project_id": project_id, "node_count": len(nodes), "nodes": nodes}


def _receipt_path(paths: ProjectPaths, today: str | None = None) -> Path:
    stamp = today or _today_utc()
    return paths.history_archives_dir / f"flora_junk_drawer_{stamp}.json"


def _write_receipt(paths: ProjectPaths, receipt: dict[str, Any]) -> Path:
    path = _receipt_path(paths)
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(receipt, indent=2, sort_keys=True) + "\n", encoding="utf-8")
    return path


def _tombstone_shared_project(project: str, project_id: str, receipt_path: Path) -> None:
    paths = ProjectPaths.for_project(project)
    rel_receipt = str(receipt_path.relative_to(paths.project_root))

    def _mutate(data: dict[str, Any]) -> dict[str, Any]:
        data["_legacy_shared"] = {
            "project_id": project_id,
            "tombstoned_at": _utc_now_iso(),
            "receipt": rel_receipt,
        }
        return data

    _cache_update(_projects_path(project), _mutate)


def archive_junk_drawer(project: str, *, dry_run: bool = False) -> dict[str, Any]:
    project_id = os.environ.get("RECOIL_FLORA_PROJECT", "")
    if not project_id:
        raise FloraArchiveError("RECOIL_FLORA_PROJECT environment variable not set")

    inventory = inventory_flora_project(project_id)
    archived_at = _utc_now_iso()
    receipt = {
        "project_id": project_id,
        "node_count": inventory["node_count"],
        "nodes": inventory["nodes"],
        "archived_at": archived_at,
        "reason": _REASON,
    }

    if dry_run:
        return {
            "dry_run": True,
            "project": project,
            "project_id": project_id,
            "node_count": inventory["node_count"],
            "receipt": None,
        }

    paths = ProjectPaths.for_project(project)
    receipt_path = _write_receipt(paths, receipt)
    _tombstone_shared_project(project, project_id, receipt_path)
    rel_receipt = str(receipt_path.relative_to(paths.project_root))
    return {
        "dry_run": False,
        "project": project,
        "project_id": project_id,
        "node_count": inventory["node_count"],
        "receipt": rel_receipt,
    }


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(
        description="Archive the legacy shared Flora project inventory."
    )
    parser.add_argument("--project", required=True)
    parser.add_argument("--dry-run", action="store_true")
    args = parser.parse_args(argv)

    try:
        result = archive_junk_drawer(args.project, dry_run=args.dry_run)
    except FloraArchiveError as exc:
        print(str(exc), file=sys.stderr)
        return 1

    print(json.dumps(result, sort_keys=True))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
