#!/usr/bin/env python3
"""
Signet Memory Functional Test

Tests memory operations across all harnesses:
- Save with auto-embedding
- Hybrid search (vector + keyword)
- Cross-harness persistence

Run: python ~/.agents/tests/test_memory.py
"""

import subprocess
import sys
import sqlite3
import uuid
import re
import time
from pathlib import Path

MEMORY_DB = Path.home() / ".agents/memory/memories.db"
MEMORY_SCRIPT = Path.home() / ".agents/memory/scripts/memory.py"

# Unique test markers
TEST_ID = uuid.uuid4().hex[:8]
TEST_CONTENT_OPENCLAW = f"[test-{TEST_ID}-openclaw] Signet memory test from OpenClaw"
TEST_CONTENT_CLAUDE = f"[test-{TEST_ID}-claude] Signet memory test from Claude Code"
TEST_CONTENT_OPENCODE = f"[test-{TEST_ID}-opencode] Signet memory test from OpenCode"


def run_memory_script(args: list) -> tuple[bool, str]:
    """Run memory.py with given args."""
    try:
        result = subprocess.run(
            [sys.executable, str(MEMORY_SCRIPT)] + args,
            capture_output=True,
            text=True,
            timeout=30
        )
        return result.returncode == 0, result.stdout + result.stderr
    except Exception as e:
        return False, str(e)


def test_database_schema():
    """Test database has required Level 2 schema."""
    print("1. Testing database schema (Level 2)...")
    
    required_tables = {"memories", "schema_migrations", "conversations", "embeddings"}
    
    conn = sqlite3.connect(MEMORY_DB)
    cursor = conn.execute("SELECT name FROM sqlite_master WHERE type='table'")
    tables = {row[0] for row in cursor.fetchall()}
    
    # Check schema version
    cursor = conn.execute("SELECT MAX(version) FROM schema_migrations")
    version = cursor.fetchone()[0] or 0
    conn.close()
    
    missing = required_tables - tables
    
    if missing:
        print(f"   ✗ Missing tables: {missing}")
        return False
    
    if version < 3:
        print(f"   ✗ Schema version {version} < 3")
        return False
    
    print(f"   ✓ Schema version {version}, all tables present")
    return True


def test_vector_store():
    """Test vector store is initialized and working."""
    print("\n2. Testing vector store...")
    
    vector_dir = Path.home() / ".agents/memory/vectors.zvec"
    
    if not vector_dir.exists():
        print(f"   ✗ Vector store not found: {vector_dir}")
        return False
    
    # Test vector store status
    success, output = run_memory_script(["query", "test", "--limit", "1"])
    
    if "vector" in output.lower() or "hybrid" in output.lower():
        print(f"   ✓ Vector store active (hybrid search enabled)")
        return True
    elif success:
        print(f"   ~ Vector store exists, search working")
        return True
    else:
        print(f"   ✗ Vector store not working")
        return False


def test_save_openclaw():
    """Test saving memory via direct script (OpenClaw style)."""
    print("\n3. Testing memory save (OpenClaw/direct)...")
    
    success, output = run_memory_script([
        "save", "--mode", "explicit",
        "--who", "openclaw-test",
        "--content", TEST_CONTENT_OPENCLAW
    ])
    
    if success:
        print(f"   ✓ Memory saved via script")
        return True
    else:
        print(f"   ✗ Save failed: {output[:100]}")
        return False


def test_save_claude_code():
    """Test saving memory with Claude Code attribution."""
    print("\n4. Testing memory save (Claude Code attribution)...")
    
    # Save memory attributed to Claude Code (via script, not invoking harness)
    success, output = run_memory_script([
        "save", "--mode", "explicit",
        "--who", "claude-code-test",
        "--content", TEST_CONTENT_CLAUDE
    ])
    
    if success:
        print(f"   ✓ Memory saved (Claude Code attribution)")
        return True
    else:
        print(f"   ✗ Save failed: {output[:100]}")
        return False


def test_save_opencode():
    """Test saving memory via OpenCode."""
    print("\n5. Testing memory save (OpenCode)...")
    
    # For OpenCode, save directly and attribute to opencode
    success, output = run_memory_script([
        "save", "--mode", "explicit",
        "--who", "opencode-test",
        "--content", TEST_CONTENT_OPENCODE
    ])
    
    if success:
        print(f"   ✓ Memory saved (OpenCode attribution)")
        return True
    else:
        print(f"   ✗ Save failed: {output[:100]}")
        return False


def test_hybrid_search():
    """Test hybrid search finds test memories."""
    print("\n6. Testing hybrid search...")
    
    # Wait for embeddings to be generated
    time.sleep(2)
    
    success, output = run_memory_script(["query", f"test-{TEST_ID}", "--limit", "5"])
    
    if not success:
        print(f"   ✗ Query failed")
        return False
    
    # Check for hybrid indicators
    is_hybrid = "hybrid" in output.lower() or "vector" in output.lower()
    
    # Check if test memories found
    found_openclaw = "openclaw" in output.lower()
    found_claude = "claude" in output.lower()
    found_opencode = "opencode" in output.lower()
    
    found_count = sum([found_openclaw, found_claude, found_opencode])
    
    if found_count >= 2:
        search_type = "hybrid" if is_hybrid else "keyword"
        print(f"   ✓ Found {found_count}/3 test memories ({search_type} search)")
        return True
    else:
        print(f"   ~ Found {found_count}/3 test memories")
        return found_count > 0


def test_cross_harness_read():
    """Test that memories from one harness can be read by another."""
    print("\n7. Testing cross-harness persistence...")
    
    # Query for all test memories
    conn = sqlite3.connect(MEMORY_DB)
    cursor = conn.execute(
        "SELECT who, content FROM memories WHERE content LIKE ?",
        (f"%{TEST_ID}%",)
    )
    rows = cursor.fetchall()
    conn.close()
    
    harnesses_found = set()
    for who, content in rows:
        if "openclaw" in who.lower() or "openclaw" in content.lower():
            harnesses_found.add("openclaw")
        if "claude" in who.lower() or "claude" in content.lower():
            harnesses_found.add("claude")
        if "opencode" in who.lower() or "opencode" in content.lower():
            harnesses_found.add("opencode")
    
    if len(harnesses_found) >= 2:
        print(f"   ✓ Memories from {len(harnesses_found)} harnesses in shared DB")
        return True
    else:
        print(f"   ✗ Only {len(harnesses_found)} harness(es) found")
        return False


def cleanup_test_data():
    """Remove test data from database."""
    print("\n8. Cleaning up test data...")
    
    try:
        conn = sqlite3.connect(MEMORY_DB)
        cursor = conn.execute(
            "DELETE FROM memories WHERE content LIKE ?",
            (f"%{TEST_ID}%",)
        )
        deleted = cursor.rowcount
        conn.commit()
        conn.close()
        print(f"   ✓ Cleaned up {deleted} test record(s)")
        return True
    except Exception as e:
        print(f"   ✗ Cleanup failed: {e}")
        return False


def main():
    print("=" * 60)
    print("SIGNET MEMORY FUNCTIONAL TEST")
    print("=" * 60)
    print(f"Test ID: {TEST_ID}")
    print()
    
    tests = [
        ("Database schema", test_database_schema),
        ("Vector store", test_vector_store),
        ("Save (OpenClaw)", test_save_openclaw),
        ("Save (Claude Code)", test_save_claude_code),
        ("Save (OpenCode)", test_save_opencode),
        ("Hybrid search", test_hybrid_search),
        ("Cross-harness read", test_cross_harness_read),
        ("Cleanup", cleanup_test_data),
    ]
    
    results = []
    for name, test_fn in tests:
        try:
            results.append(test_fn())
        except Exception as e:
            print(f"   ✗ Error: {e}")
            results.append(False)
    
    print()
    print("=" * 60)
    passed = sum(results)
    total = len(results)
    
    if all(results):
        print(f"RESULT: ALL TESTS PASSED ({passed}/{total})")
        print("=" * 60)
        return 0
    else:
        print(f"RESULT: {passed}/{total} TESTS PASSED")
        print("=" * 60)
        return 1


if __name__ == "__main__":
    sys.exit(main())
