#!/usr/bin/env python3
"""
Phase 6: One-time migration script for the author's workspace.

This script is deliberately conservative and tailored for a single-user
(author) scenario. It is **not** a general product migration tool.

Safety principles:
- Dry-run by default. Nothing is written unless you explicitly ask.
- Always creates a User Model checkpoint first (via the existing mechanism).
- Strongly prefers creating **proposals** over direct apply_patch.
- Never touches or deletes legacy files (USER.md, drives.json, open_loops.jsonl, etc.).
- All changes are auditable.
- Idempotent where possible.

Usage (recommended order):

    # 1. See exactly what would happen (completely safe)
    python scripts/migrate_legacy_to_user_model.py --dry-run

    # 2. Create proposals for high-confidence items (still very safe)
    python scripts/migrate_legacy_to_user_model.py --propose

    # 3. (Optional, more aggressive) Apply some items directly after review
    python scripts/migrate_legacy_to_user_model.py --apply --i-am-sure-i-reviewed-the-report

After running, you should:
- Review any created proposals in memory/user_model/proposals/
- Accept the ones you like via the UI or manually
- Create a named checkpoint ("Post Phase 6 migration baseline")

Legacy files remain untouched and continue to work during the transition.
"""

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

# Add repo root to path when run directly
REPO_ROOT = Path(__file__).resolve().parents[1]
if str(REPO_ROOT) not in sys.path:
    sys.path.insert(0, str(REPO_ROOT))

from core import get_workspace_path
from utils.user_model import (
    load_user_model,
    apply_patch,
    create_proposal,
    UserModel,
)


def _read_text_safe(path: Path) -> str:
    try:
        return path.read_text(encoding="utf-8", errors="replace")
    except Exception:
        return ""


def _load_jsonl_safe(path: Path) -> list[dict]:
    items = []
    if not path.exists():
        return items
    try:
        for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
            line = line.strip()
            if not line:
                continue
            try:
                obj = json.loads(line)
                if isinstance(obj, dict):
                    items.append(obj)
            except Exception:
                continue
    except Exception:
        pass
    return items


def _load_drives_safe(workspace: str) -> list[dict]:
    drives_path = Path(workspace) / "memory" / "drives.json"
    if not drives_path.exists():
        return []
    try:
        data = json.loads(drives_path.read_text(encoding="utf-8", errors="replace"))
        if isinstance(data, dict):
            return data.get("active_drives", []) or []
    except Exception:
        pass
    return []


def extract_identity_from_user_md(content: str) -> dict[str, str]:
    """Very conservative extraction of obvious identity fields from USER.md."""
    identity: dict[str, str] = {}
    lines = content.splitlines()

    # Look for common patterns in the current USER.md template style
    for line in lines:
        line = line.strip()
        if line.startswith("- **") and ":**" in line:
            try:
                key_part, value_part = line.split(":**", 1)
                key = key_part.replace("- **", "").strip().lower()
                value = value_part.strip()
                if not value or value in ("—", "-", ""):
                    continue

                # Map common fields
                if key in ("name", "what to call them", "pronouns", "timezone"):
                    identity[key] = value
                elif "timezone" in key:
                    identity["timezone"] = value
            except Exception:
                continue

    return identity


def extract_open_commitments_from_open_loops(open_loops: list[dict]) -> list[dict]:
    commitments = []
    for item in open_loops:
        if item.get("status") != "open":
            continue
        text = item.get("text", "").strip()
        if not text:
            continue
        commitments.append({
            "text": text[:400],
            "source": item.get("source", "legacy_open_loop"),
            "created_at": item.get("created_at"),
            "status": "open",
        })
    return commitments


def extract_goals_from_drives(drives: list[dict]) -> list[dict]:
    goals = []
    for d in drives:
        title = (d.get("title") or d.get("description") or "").strip()
        if not title:
            continue
        goals.append({
            "title": title[:200],
            "why_it_matters": d.get("description", "")[:300],
            "status": "active",
            "horizon": "medium",
            "source": "legacy_drive",
        })
    return goals


def extract_facts_from_insights(insights_content: str) -> list[str]:
    """Very conservative extraction of bullet-like facts from recent insights."""
    facts = []
    for line in insights_content.splitlines():
        line = line.strip()
        if line.startswith(("- ", "* ", "• ")):
            text = line[2:].strip()
            if len(text) > 15 and len(text) < 300:
                # Only take relatively clean, non-meta lines
                if not any(bad in text.lower() for bad in ["consolidation", "heartbeat", "system", "memory model"]):
                    facts.append(text)
    return facts[:20]  # be conservative


def main():
    parser = argparse.ArgumentParser(description="Safe one-time migration to User Model (author only)")
    parser.add_argument("--dry-run", action="store_true", default=True,
                        help="Default. Show exactly what would be proposed (safe).")
    parser.add_argument("--propose", action="store_true",
                        help="Create proposals in memory/user_model/proposals/ for review (recommended).")
    parser.add_argument("--apply", action="store_true",
                        help="DANGEROUS: Apply changes directly (only after --propose + manual review).")
    parser.add_argument("--i-am-sure-i-reviewed-the-report", action="store_true",
                        help="Required safety flag when using --apply.")
    args = parser.parse_args()

    if args.apply and not args.i_am_sure_i_reviewed_the_report:
        print("ERROR: --apply requires the safety flag --i-am-sure-i-reviewed-the-report")
        print("Please run with --propose first, review the output/proposals, then re-run if desired.")
        sys.exit(1)

    if args.apply and args.propose:
        print("ERROR: Choose either --propose or --apply, not both.")
        sys.exit(1)

    mode = "apply" if args.apply else ("propose" if args.propose else "dry-run")

    ws = get_workspace_path()
    if not ws or not os.path.isdir(ws):
        print("ERROR: Could not determine workspace path.")
        sys.exit(1)

    print(f"Workspace: {ws}")
    print(f"Mode: {mode}")
    print()

    # 1. Always create a checkpoint first (via the existing mechanism if possible)
    print("Creating pre-migration checkpoint...")
    try:
        # Direct import only — the previous attempt to import the private helper
        # from user_model_http_routes (where it is only locally imported inside
        # create_checkpoint) would always fail and mask the real fallback.
        from utils.user_model import _append_audit
        ts = datetime.now(timezone.utc).isoformat()
        _append_audit(ws, {
            "timestamp": ts,
            "actor": "migration_script",
            "action": "pre_migration_checkpoint",
            "label": "Pre-Phase-6 migration baseline",
        })
        print("  → Pre-migration checkpoint recorded in audit.")
    except Exception as e:
        print(f"  → Warning: Could not record checkpoint via normal path ({e}). Continuing.")

    # 2. Read legacy sources (read-only)
    print("\nReading legacy sources (read-only)...")

    user_md = _read_text_safe(Path(ws) / "USER.md")
    insights = _read_text_safe(Path(ws) / "memory" / "insights.md")
    recurring = _read_text_safe(Path(ws) / "memory" / "recurring-patterns.md")

    open_loops = _load_jsonl_safe(Path(ws) / "memory" / "open_loops.jsonl")
    drives = _load_drives_safe(ws)

    print(f"  USER.md: {len(user_md)} chars")
    print(f"  insights.md: {len(insights)} chars")
    print(f"  open_loops: {len(open_loops)} items")
    print(f"  drives: {len(drives)} active")

    # 3. Build proposed patch (conservative)
    patch: dict[str, Any] = {}

    # Identity from USER.md
    identity = extract_identity_from_user_md(user_md)
    if identity:
        patch["identity"] = identity
        print(f"\n  Proposed identity fields: {list(identity.keys())}")

    # Open commitments from open_loops
    commitments = extract_open_commitments_from_open_loops(open_loops)
    if commitments:
        patch["open_commitments"] = commitments
        print(f"  Proposed open_commitments: {len(commitments)}")

    # Goals from drives (as a starting point)
    goals = extract_goals_from_drives(drives)
    if goals:
        patch["goals"] = goals
        print(f"  Proposed goals (from drives): {len(goals)}")

    # High-signal facts from insights (very conservative)
    facts = extract_facts_from_insights(insights)
    if facts:
        # Put strong facts under "preferences" or as notes for now
        if "preferences" not in patch:
            patch["preferences"] = {}
        patch["preferences"]["notable_facts_from_insights"] = facts[:5]
        print(f"  Proposed notable facts from insights: {len(facts[:5])}")

    if not patch:
        print("\nNo high-confidence items found to migrate. Nothing to do.")
        return

    print(f"\nProposed patch has {len(patch)} top-level sections.")

    if mode == "dry-run":
        print("\n--- DRY RUN REPORT ---")
        print(json.dumps(patch, indent=2, ensure_ascii=False))
        print("\nThis is what would be proposed. Run with --propose to create reviewable proposals.")
        return

    if mode == "propose":
        print("\nCreating proposals (safe — nothing is applied yet)...")
        prop_id = create_proposal(
            ws,
            patch,
            rationale="Phase 6 automated legacy migration (USER.md + insights + drives + open_loops). Review and accept the parts you like.",
            source="phase6_migration",
        )
        if prop_id:
            print(f"  → Created proposal: {prop_id}")
            print("    Review it in memory/user_model/proposals/ or via the UI.")
            print("    Run the script again with --propose if you want additional proposals from other sources.")
        else:
            print("  → Failed to create proposal.")
        return

    if mode == "apply":
        print("\n!!! APPLY MODE — This will directly modify your User Model !!!")
        print("You claimed you reviewed the report. Proceeding with direct apply...")
        result = apply_patch(
            ws,
            patch,
            actor="phase6_migration",
            provenance={"source": "migration_script", "note": "Author-approved direct migration"},
        )
        if result.get("ok"):
            print("  → Direct apply succeeded.")
            print("  → Strongly recommended: immediately create a named checkpoint in the UI.")
        else:
            print(f"  → Apply failed: {result}")
        return


if __name__ == "__main__":
    main()