#!/usr/bin/env python3
"""migrate.py — bring an existing ECHO vault up to the plugin's current schema.

Reads the marker's schema_version and applies each intervening migration in order.
Migrations are idempotent and additive; every destructive step (DELETE/move) is
gated behind --apply AND printed first. Default mode is a DRY-RUN plan.
Cross-platform: pure Python via echo.py.

Usage:
  migrate.py            # print the migration plan (no changes)
  migrate.py --apply    # perform the migration (moves/deletes included)

Env: ECHO_BASE, ECHO_KEY (via echo.py).
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(SCRIPT_DIR))

import echo  # noqa: E402

CURRENT_SCHEMA = 4


def get_text(path: str) -> str | None:
    status, body = echo.request("GET", echo.vault_url(path))
    if status == 404:
        return None
    echo.check(status, body, f"get {path}")
    return body.decode(errors="replace")


def list_files(path: str) -> list[str]:
    if not path.endswith("/"):
        path += "/"
    status, body = echo.request("GET", echo.vault_url(path))
    if status == 404:
        return []
    echo.check(status, body, f"ls {path}")
    try:
        payload = json.loads(body)
    except json.JSONDecodeError:
        return []
    return [entry for entry in payload.get("files", []) if not entry.endswith("/")]


def put_text(path: str, text: str) -> None:
    status, body = echo.request("PUT", echo.vault_url(path), data=text.encode(),
                                headers={"Content-Type": "text/markdown"})
    echo.check(status, body, f"put {path}")


def delete(path: str) -> None:
    status, body = echo.request("DELETE", echo.vault_url(path))
    if status != 404:
        echo.check(status, body, f"delete {path}")


def move(src: str, dst: str) -> None:
    text = get_text(src)
    if text is None:
        return
    put_text(dst, text)
    delete(src)


def do_or_show(apply: bool, desc: str, func=None) -> None:
    if apply:
        print(f"migrate: APPLY  {desc}")
        if func:
            func()
    else:
        print(f"migrate: PLAN   {desc}")


def marker_schema() -> int:
    marker = get_text("_agent/echo-vault.md")
    if marker is None:
        print("migrate: marker missing — vault not bootstrapped. Run bootstrap.py, not migrate.py.")
        raise SystemExit(3)
    for line in marker.splitlines():
        if line.startswith("schema_version:"):
            try:
                return int(line.split(":", 1)[1].strip())
            except ValueError:
                return 0
    return 0


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description="Migrate ECHO vault schema")
    parser.add_argument("--apply", action="store_true")
    args = parser.parse_args(argv)

    start = marker_schema()
    print(f"migrate: vault schema_version={start}, plugin schema={CURRENT_SCHEMA} "
          f"{'(APPLY)' if args.apply else '(dry-run)'}")
    if start >= CURRENT_SCHEMA:
        print("migrate: up to date — nothing to do.")
        return 0

    if start < 1:
        print("migrate: [0->1] retire in-vault control docs (CLAUDE/BOOTSTRAP/STRUCTURE/index.md)")
        for filename in ["CLAUDE.md", "BOOTSTRAP.md", "STRUCTURE.md", "index.md"]:
            if get_text(filename) is not None:
                do_or_show(args.apply, f"delete vault/{filename} (back it up outside the vault first)",
                           lambda f=filename: delete(f))
        print("migrate: [0->1] reminder: scrub dangling control-doc [[links]] from Related sections.")

    if start < 2:
        print("migrate: [1->2] fold reviews/ into journal/ and _agent/health/")
        for filename in list_files("reviews/weekly"):
            if filename.endswith(".md"):
                dst = f"journal/weekly/{filename.replace('-review.md', '.md')}"
                do_or_show(args.apply, f"move reviews/weekly/{filename} -> {dst}",
                           lambda f=filename, d=dst: move(f"reviews/weekly/{f}", d))
        for filename in list_files("reviews/monthly"):
            if filename.endswith(".md"):
                dst = f"_agent/health/{filename}" if filename.endswith("vault-health.md") else f"journal/monthly/{filename}"
                do_or_show(args.apply, f"move reviews/monthly/{filename} -> {dst}",
                           lambda f=filename, d=dst: move(f"reviews/monthly/{f}", d))
        for period in ["quarterly", "annual"]:
            for filename in list_files(f"reviews/{period}"):
                if filename.endswith(".md"):
                    dst = f"journal/{period}/{filename}"
                    do_or_show(args.apply, f"move reviews/{period}/{filename} -> {dst}",
                               lambda p=period, f=filename, d=dst: move(f"reviews/{p}/{f}", d))
        print("migrate: [1->2] reminder: update inbound [[reviews/...]] wikilinks manually.")

    if start < 3:
        print("migrate: [2->3] add the entity-index folder (cross-link / recall feature)")
        if get_text("_agent/index/README.md") is None:
            do_or_show(args.apply, "create _agent/index/README.md",
                       lambda: put_text("_agent/index/README.md",
                                        "# index\n\nMachine-maintained entity index. See the echo-memory plugin.\n"))
        print("migrate: [2->3] then run `sweep.py --apply` to build entities.json and symmetrize links.")

    if start < 4:
        print("migrate: [3->4] add the recall (BM25) index — hybrid lexical+graph recall")
        print("migrate: [3->4] _agent/index/ already exists (schema 3); no moves needed.")
        print("migrate: [3->4] then run `sweep.py --apply` to build _agent/index/recall-index.json.")

    do_or_show(args.apply, f"set _agent/echo-vault.md schema_version -> {CURRENT_SCHEMA}",
               lambda: echo.cmd_fm("_agent/echo-vault.md", "schema_version", str(CURRENT_SCHEMA)))
    if args.apply:
        print(f"migrate: migration complete -> schema {CURRENT_SCHEMA}. Run sweep.py --apply, then vault_lint.py.")
    else:
        print("migrate: dry-run only. Re-run with --apply to perform the moves/deletes above.")
    return 0


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