#!/usr/bin/env python3
"""
Echo Memory CLI - Search, Answer, Receipt Lookup, and Plan Critique.

Usage:
    python -m helix_cdc.echo.memory search "CDNA streaming"
    python -m helix_cdc.echo.memory answer "What is the memory savings factor?"
    python -m helix_cdc.echo.memory receipt "WO-STREAM-FULL-BLOCK-01"
    python -m helix_cdc.echo.memory critique < plan.md
    python -m helix_cdc.echo.memory build  # Build indexes

Environment:
    ECHO_INDEX_DIR: Index directory (default: ./index)
    ECHO_EMBED_MODEL: Embedding model (default: all-MiniLM-L6-v2)
"""

import argparse
import json
import sys
from pathlib import Path
from typing import Optional


def cmd_search(args):
    """Search memory index."""
    from helix_cdc.echo.tools.memory import MemoryIndex

    index = MemoryIndex(args.index_dir)
    results = index.search(args.query, k=args.k)

    if args.json:
        print(json.dumps([r.to_dict() for r in results], indent=2))
    else:
        print(f"Found {len(results)} results for: {args.query}\n")
        for i, chunk in enumerate(results, 1):
            print(f"{i}. [{chunk.source_type}] {chunk.source_path}")
            print(f"   Score: {chunk.relevance_score:.3f}")
            text_preview = chunk.text[:200].replace("\n", " ")
            print(f"   {text_preview}...")
            print()


def cmd_answer(args):
    """Answer with evidence and confidence."""
    from helix_cdc.echo.tools.memory import MemoryIndex

    index = MemoryIndex(args.index_dir)
    result = index.answer(args.query, k=args.k)

    if args.json:
        print(json.dumps(result.to_dict(), indent=2))
    else:
        print("Evidence:")
        for i, ev in enumerate(result.evidence, 1):
            print(f"  {i}. [{ev['source']}]")
            print(f"     \"{ev['quote'][:150]}...\"")
            print(f"     (type: {ev['type']}, score: {ev['score']})")
            print()

        print(f"Result:\n  {result.result}\n")
        print(f"Confidence: {result.confidence}")


def cmd_receipt(args):
    """Find and display a receipt."""
    from helix_cdc.echo.tools.memory import MemoryIndex

    index = MemoryIndex(args.index_dir)
    receipt = index.get_receipt(args.query)

    if receipt is None:
        print(f"Receipt not found: {args.query}", file=sys.stderr)
        sys.exit(1)

    if args.json:
        print(json.dumps(receipt, indent=2))
    else:
        print(f"Receipt: {receipt.get('_path', 'unknown')}")
        print(f"SHA256: {receipt.get('_sha256', 'unknown')[:16]}...")
        print()

        # Pretty print key fields
        for key in ["schema", "work_order", "status", "timestamp_utc"]:
            if key in receipt:
                print(f"{key}: {receipt[key]}")

        if "claim_hygiene" in receipt:
            print("\nClaim Hygiene:")
            ch = receipt["claim_hygiene"]
            if isinstance(ch, dict):
                for k, v in ch.items():
                    print(f"  {k}: {v}")

        if "conclusion" in receipt:
            print(f"\nConclusion: {receipt['conclusion']}")


def cmd_critique(args):
    """Critique a plan."""
    from helix_cdc.echo.tools.memory import MemoryIndex

    # Read plan from stdin or file
    if args.file:
        plan_text = Path(args.file).read_text()
    else:
        plan_text = sys.stdin.read()

    if not plan_text.strip():
        print("No plan provided. Use --file or pipe plan via stdin.", file=sys.stderr)
        sys.exit(1)

    index = MemoryIndex(args.index_dir)
    result = index.critique_plan(plan_text)

    if args.json:
        print(json.dumps(result.to_dict(), indent=2))
    else:
        print("=" * 60)
        print("PLAN CRITIQUE")
        print("=" * 60)

        if result.risks:
            print("\nRISKS:")
            for risk in result.risks:
                severity_marker = {"HIGH": "!!!", "MEDIUM": "!!", "LOW": "!"}[risk["severity"]]
                print(f"  {severity_marker} [{risk['severity']}] {risk['description']}")
                print(f"      Pattern: {risk['pattern']}")

        if result.missing_proofs:
            print("\nMISSING PROOFS:")
            for proof in result.missing_proofs:
                print(f"  - {proof}")

        print("\nPROVEN vs ASSUMED:")
        print("  Proven:")
        for item in result.proven_vs_assumed.get("proven", [])[:3]:
            print(f"    - {item[:80]}...")
        print("  Assumed:")
        for item in result.proven_vs_assumed.get("assumed", [])[:3]:
            print(f"    - {item[:80]}...")

        print(f"\nSMALLEST TEST: {result.smallest_test}")
        print(f"ROLLBACK PLAN: {result.rollback_plan}")

        if result.patch:
            print("\nSUGGESTED PATCH:")
            print(result.patch)


def cmd_build(args):
    """Build memory indexes."""
    from tools.corpus_build.gather_sources import gather_sources
    from tools.corpus_build.chunk_corpus import chunk_corpus
    from tools.corpus_build.embed_index import build_vector_index
    from tools.corpus_build.build_lexical import build_lexical_index
    import hashlib
    from datetime import datetime

    root = Path(args.root).resolve()
    index_dir = Path(args.index_dir).resolve()
    corpus_dir = root / "corpus"

    print(f"Building Echo Memory indexes")
    print(f"  Root: {root}")
    print(f"  Index: {index_dir}")
    print()

    # Step 1: Gather sources
    print("[1/4] Gathering sources...")
    manifest_path = corpus_dir / "raw_manifest.json"
    manifest = gather_sources(root, manifest_path)
    print()

    # Step 2: Chunk corpus
    print("[2/4] Chunking corpus...")
    chunks_path = index_dir / "chunks.parquet"
    chunk_stats = chunk_corpus(manifest_path, chunks_path, args.max_files)
    print()

    # Step 3: Build vector index
    print("[3/4] Building vector index...")
    vectors_path = index_dir / ("vectors.faiss" if args.faiss else "vectors.sqlite")
    vector_stats = build_vector_index(
        chunks_path, vectors_path,
        model_name=args.model,
        batch_size=args.batch_size,
        use_faiss=args.faiss
    )
    print()

    # Step 4: Build lexical index
    print("[4/4] Building lexical index...")
    lexical_path = index_dir / "lexical.sqlite"
    lexical_stats = build_lexical_index(chunks_path, lexical_path)
    print()

    # Generate build receipt
    receipt = {
        "schema": "echo_memory_index_v1",
        "work_order": "WO-ECHO-MEMORY-PIPELINE-01",
        "timestamp_utc": datetime.utcnow().isoformat() + "Z",
        "corpus": {
            "total_files": manifest["stats"]["total_files"],
            "total_bytes": manifest["stats"]["total_bytes"],
            "source_manifest_sha256": manifest["manifest_sha256"],
        },
        "index": {
            "chunks_count": chunk_stats["total_chunks"],
            "chunks_parquet_sha256": chunk_stats["chunks_parquet_sha256"],
            "vectors_sqlite_sha256": vector_stats["index_sha256"],
            "lexical_sqlite_sha256": lexical_stats["index_sha256"],
            "embed_model": args.model,
            "chunk_size": 512,
            "chunk_overlap": 64,
        },
        "determinism": {
            "random_seed": None,
            "all_operations_deterministic": True,
        },
        "status": "PASS",
    }

    # Write receipt
    receipts_dir = root / "receipts" / "memory_index"
    receipts_dir.mkdir(parents=True, exist_ok=True)
    receipt_path = receipts_dir / f"build_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.json"
    receipt_path.write_text(json.dumps(receipt, indent=2))

    print("=" * 60)
    print("BUILD COMPLETE")
    print("=" * 60)
    print(f"  Chunks: {receipt['index']['chunks_count']}")
    print(f"  Files: {receipt['corpus']['total_files']}")
    print(f"  Receipt: {receipt_path}")


def main():
    parser = argparse.ArgumentParser(
        description="Echo Memory CLI",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=__doc__
    )
    parser.add_argument("--index-dir", type=Path, default=None,
                        help="Index directory (default: ./index)")
    parser.add_argument("--json", action="store_true",
                        help="Output as JSON")

    subparsers = parser.add_subparsers(dest="command", required=True)

    # search
    p_search = subparsers.add_parser("search", help="Search memory index")
    p_search.add_argument("query", help="Search query")
    p_search.add_argument("-k", type=int, default=8, help="Number of results")
    p_search.set_defaults(func=cmd_search)

    # answer
    p_answer = subparsers.add_parser("answer", help="Answer with evidence")
    p_answer.add_argument("query", help="Question to answer")
    p_answer.add_argument("-k", type=int, default=8, help="Number of evidence chunks")
    p_answer.set_defaults(func=cmd_answer)

    # receipt
    p_receipt = subparsers.add_parser("receipt", help="Find a receipt")
    p_receipt.add_argument("query", help="Receipt ID or path fragment")
    p_receipt.set_defaults(func=cmd_receipt)

    # critique
    p_critique = subparsers.add_parser("critique", help="Critique a plan")
    p_critique.add_argument("--file", "-f", help="Plan file (or use stdin)")
    p_critique.set_defaults(func=cmd_critique)

    # build
    p_build = subparsers.add_parser("build", help="Build memory indexes")
    p_build.add_argument("--root", type=Path, default=Path.cwd(),
                         help="Project root")
    p_build.add_argument("--max-files", type=int, default=None,
                         help="Limit files for testing")
    p_build.add_argument("--model", type=str, default="all-MiniLM-L6-v2",
                         help="Embedding model")
    p_build.add_argument("--batch-size", type=int, default=64,
                         help="Embedding batch size")
    p_build.add_argument("--faiss", action="store_true",
                         help="Use FAISS instead of SQLite")
    p_build.set_defaults(func=cmd_build)

    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()
