from __future__ import annotations

"""OpenBrain-style MCP bridge for the AI Archive project.

This is a local, provenance-backed memory surface that exposes:
- archive search (SQLite FTS)
- approved memory lookup
- source-only lookup
- basic install status

It is intentionally lightweight so Hermes can connect to a concrete
memory surface while the full Supabase/OpenBrain backend is being
prepared or swapped in.
"""

from dataclasses import asdict, dataclass
from pathlib import Path
import argparse
import json
import sqlite3
import sys
from typing import Any

from mcp.server.fastmcp import FastMCP

ROOT = Path(__file__).resolve().parents[1]
INDEX_DB = ROOT / "index" / "archive.db"
APPROVED_BATCH = ROOT / "approved-memory" / "install-batch-proposed.jsonl"
APPROVED_MEMORIES = ROOT / "approved-memory" / "approved-memories.jsonl"
SOURCE_ONLY_INDEX = ROOT / "approved-memory" / "source-only-index.jsonl"

mcp = FastMCP(
    "ai-archive-openbrain-bridge",
    instructions=(
        "OpenBrain-style local memory bridge for Bert's archive. "
        "Use this to search the archive, approved memories, and source-only evidence."
    ),
)


def _read_jsonl(path: Path) -> list[dict[str, Any]]:
    if not path.exists():
        return []
    items: list[dict[str, Any]] = []
    with path.open("r", encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            try:
                items.append(json.loads(line))
            except json.JSONDecodeError:
                continue
    return items


def _phrase_query(query: str) -> str:
    return '"' + query.replace('"', '""') + '"'


def _search_chunks(query: str, source: str | None = None, limit: int = 5, raw_fts: bool = False) -> list[dict[str, Any]]:
    if not INDEX_DB.exists():
        return []
    conn = sqlite3.connect(INDEX_DB)
    conn.row_factory = sqlite3.Row
    try:
        sql = """
        SELECT
          c.chunk_id,
          c.conversation_id,
          c.source,
          c.title,
          c.message_start,
          c.message_end,
          c.text,
          c.metadata_json,
          snippet(chunks_fts, 4, '[', ']', '…', 18) AS snippet
        FROM chunks_fts f
        JOIN chunks c ON c.rowid = f.rowid
        WHERE chunks_fts MATCH ?
        """
        params: list[Any] = [query if raw_fts else _phrase_query(query)]
        if source:
            sql += " AND c.source = ?"
            params.append(source)
        sql += " LIMIT ?"
        params.append(limit)
        rows = conn.execute(sql, params).fetchall()
    finally:
        conn.close()

    payload: list[dict[str, Any]] = []
    for row in rows:
        item = dict(row)
        try:
            item["metadata"] = json.loads(item.pop("metadata_json") or "{}")
        except json.JSONDecodeError:
            item["metadata"] = {"raw_metadata_json": item.pop("metadata_json")}
        payload.append(item)
    return payload


def _search_jsonl(path: Path, query: str, limit: int = 10) -> list[dict[str, Any]]:
    results = []
    q = query.lower().strip()
    for row in _read_jsonl(path):
        blob = json.dumps(row, ensure_ascii=False).lower()
        if q in blob:
            results.append(row)
        if len(results) >= limit:
            break
    return results


@mcp.tool(description="Get a quick status summary for the OpenBrain-style bridge.")
def status() -> dict[str, Any]:
    approved = _read_jsonl(APPROVED_BATCH)
    approved_count = sum(1 for row in approved if row.get("decision") == "APPROVE")
    source_only = _read_jsonl(SOURCE_ONLY_INDEX)
    return {
        "bridge": "ai-archive-openbrain-bridge",
        "root": str(ROOT),
        "index_db_exists": INDEX_DB.exists(),
        "approved_batch_path": str(APPROVED_BATCH),
        "approved_count": approved_count,
        "source_only_count": len(source_only),
        "mcp_surface": "local stdio",
        "backend_note": "OpenBrain-compatible bridge over local archive + reviewed memory artifacts",
    }


@mcp.tool(description="Search the archive index by phrase or raw SQLite FTS syntax.")
def search_archive(query: str, source: str | None = None, limit: int = 5, raw_fts: bool = False) -> list[dict[str, Any]]:
    return _search_chunks(query=query, source=source, limit=limit, raw_fts=raw_fts)


@mcp.tool(description="Search approved memory items from the reviewed install batch.")
def search_approved_memories(query: str, memory_type: str | None = None, project: str | None = None, limit: int = 10) -> list[dict[str, Any]]:
    rows = []
    q = query.lower().strip()
    for row in _read_jsonl(APPROVED_BATCH):
        if row.get("decision") != "APPROVE":
            continue
        blob = json.dumps(row, ensure_ascii=False).lower()
        if q not in blob:
            continue
        if memory_type and row.get("memory_type") != memory_type:
            continue
        if project and row.get("project") != project:
            continue
        rows.append(row)
        if len(rows) >= limit:
            break
    return rows


@mcp.tool(description="List approved memory items, optionally filtered by memory type or project.")
def list_approved_memories(memory_type: str | None = None, project: str | None = None, limit: int = 50) -> list[dict[str, Any]]:
    rows = []
    for row in _read_jsonl(APPROVED_BATCH):
        if row.get("decision") != "APPROVE":
            continue
        if memory_type and row.get("memory_type") != memory_type:
            continue
        if project and row.get("project") != project:
            continue
        rows.append(row)
        if len(rows) >= limit:
            break
    return rows


@mcp.tool(description="Get one approved memory item by candidate ID.")
def get_memory(candidate_id: str) -> dict[str, Any] | None:
    for row in _read_jsonl(APPROVED_BATCH):
        if row.get("candidate_id") == candidate_id and row.get("decision") == "APPROVE":
            return row
    return None


@mcp.tool(description="Get one source-only item by candidate ID.")
def get_source_only(candidate_id: str) -> dict[str, Any] | None:
    for row in _read_jsonl(SOURCE_ONLY_INDEX):
        if row.get("candidate_id") == candidate_id:
            return row
    return None


@mcp.tool(description="Fetch a source chunk from the local archive index by chunk ID.")
def get_source_chunk(chunk_id: str) -> dict[str, Any] | None:
    if not INDEX_DB.exists():
        return None
    conn = sqlite3.connect(INDEX_DB)
    conn.row_factory = sqlite3.Row
    try:
        row = conn.execute(
            """
            SELECT chunk_id, conversation_id, source, title, message_start, message_end, text, metadata_json
            FROM chunks
            WHERE chunk_id = ?
            """,
            (chunk_id,),
        ).fetchone()
    finally:
        conn.close()
    if row is None:
        return None
    item = dict(row)
    try:
        item["metadata"] = json.loads(item.pop("metadata_json") or "{}")
    except json.JSONDecodeError:
        item["metadata"] = {"raw_metadata_json": item.pop("metadata_json")}
    return item


@mcp.tool(description="List reviewed candidate packets from the install batch.")
def list_review_items(decision: str | None = None, limit: int = 50) -> list[dict[str, Any]]:
    rows = []
    for row in _read_jsonl(APPROVED_BATCH):
        if decision and row.get("decision") != decision:
            continue
        rows.append(row)
        if len(rows) >= limit:
            break
    return rows


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--transport", default="stdio", choices=["stdio", "sse", "streamable-http"])
    args = parser.parse_args()
    mcp.run(args.transport)
