#!/usr/bin/env python3
"""One-time migration: re-route pending background_noticed proposals to structured fields.

Reads pending proposals whose changes only touch ``preferences.background_noticed``,
reclassifies the fact text via ``build_proposal_changes_from_fact``, dedupes by
serialized changes, and rewrites a smaller set of pending proposal files.

Usage:
  python scripts/restructure_background_noticed_proposals.py [--apply]
"""
from __future__ import annotations

import argparse
import json
import os
import sys
import uuid
from datetime import datetime, timezone

_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _REPO_ROOT not in sys.path:
    sys.path.insert(0, _REPO_ROOT)

from core import get_workspace_path
from utils.user_model import (
    PROPOSALS_DIR,
    build_proposal_changes_from_fact,
    list_proposals,
    proposal_write_context,
    _resolve_user_model_path,
)
from persistence import secure_atomic_write


def _is_background_noticed_only(changes: dict) -> bool:
    prefs = changes.get("preferences")
    if not isinstance(prefs, dict) or len(prefs) != 1:
        return False
    return "background_noticed" in prefs


def _fact_text_from_proposal(proposal: dict) -> str:
    changes = proposal.get("changes") or {}
    prefs = changes.get("preferences") or {}
    if isinstance(prefs, dict) and prefs.get("background_noticed"):
        return str(prefs["background_noticed"])
    rationale = str(proposal.get("rationale") or "")
    return rationale


def restructure(*, apply: bool) -> dict[str, int]:
    ws = get_workspace_path()
    pending = list_proposals(ws)
    targets = [p for p in pending if _is_background_noticed_only(p.get("changes") or {})]
    if not targets:
        return {"examined": len(pending), "rewritten": 0, "removed": 0}

    merged: dict[str, dict] = {}
    for prop in targets:
        fact = _fact_text_from_proposal(prop)
        new_changes = build_proposal_changes_from_fact(fact)
        if not new_changes:
            continue
        key = json.dumps(new_changes, sort_keys=True, ensure_ascii=False)
        if key not in merged:
            merged[key] = {
                "changes": new_changes,
                "rationale": prop.get("rationale") or fact,
                "source": prop.get("source") or "restructure_migration",
                "created_at": prop.get("created_at"),
            }

    # Separate targets into reclassifiable vs unclassifiable
    reclassifiable = []
    unclassifiable = []
    for prop in targets:
        fact = _fact_text_from_proposal(prop)
        new_changes = build_proposal_changes_from_fact(fact)
        if new_changes:
            reclassifiable.append(prop)
        else:
            unclassifiable.append(prop)

    removed = 0
    written = 0
    skipped = len(unclassifiable)

    if apply:
        with proposal_write_context():
            # Write replacements FIRST, then delete originals (no data loss on crash)
            for payload in merged.values():
                prop_id = uuid.uuid4().hex[:16]
                out = {
                    "id": prop_id,
                    "status": "pending",
                    "changes": payload["changes"],
                    "rationale": payload["rationale"],
                    "source": payload["source"],
                    "created_at": payload.get("created_at")
                    or datetime.now(timezone.utc).isoformat(),
                }
                path = _resolve_user_model_path(ws, f"{PROPOSALS_DIR}/{prop_id}.proposal.json", allow_write=True)
                if path:
                    secure_atomic_write(path, json.dumps(out, indent=2, ensure_ascii=False))
                    written += 1

            # Only delete reclassifiable proposals (leave unclassifiable ones intact)
            for prop in reclassifiable:
                prop_id = str(prop.get("id") or "")
                path = _resolve_user_model_path(ws, f"{PROPOSALS_DIR}/{prop_id}.proposal.json", allow_write=True)
                if path and os.path.isfile(path):
                    os.remove(path)
                    removed += 1

    return {
        "examined": len(pending),
        "background_noticed": len(targets),
        "merged_into": len(merged),
        "removed": removed if apply else len(reclassifiable),
        "rewritten": written if apply else len(merged),
        "skipped_unclassifiable": skipped,
    }


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--apply",
        action="store_true",
        help="Rewrite proposal files (default is dry-run summary only)",
    )
    args = parser.parse_args()
    stats = restructure(apply=args.apply)
    mode = "APPLIED" if args.apply else "DRY-RUN"
    print(f"[{mode}] {stats}")
    return 0


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