from __future__ import annotations

import warnings
from pathlib import Path
from uuid import uuid4

import numpy as np
import pytest

from iai_mcp.hippo import HippoDB
from iai_mcp.types import EMBED_DIM

def _make_store(tmp_path: Path):

    class _Store:
        def __init__(self, db: HippoDB) -> None:
            self.db = db

    return _Store(HippoDB(tmp_path))

def _random_row(tier: str = "episodic") -> dict:
    return {
        "id": str(uuid4()),
        "tier": tier,
        "literal_surface": "hello world",
        "embedding": np.random.rand(EMBED_DIM).astype(np.float32).tolist(),
        "created_at": "2026-01-01T00:00:00+00:00",
    }

def test_optimize_returns_report_for_all_tables(tmp_path: Path) -> None:
    from iai_mcp.maintenance import optimize_hippo_storage

    store = _make_store(tmp_path)
    report = optimize_hippo_storage(store)

    assert isinstance(report, dict)
    for table in ("records", "edges", "events"):
        assert table in report, f"missing table {table!r} in report"
        sub = report[table]
        assert "rows_before" in sub
        assert "rows_after" in sub
        assert "size_bytes_before" in sub
        assert "size_bytes_after" in sub
        assert "elapsed_sec" in sub
        assert "error" not in sub, f"unexpected error on {table}: {sub.get('error')}"

def test_vacuum_actually_runs(tmp_path: Path) -> None:
    from iai_mcp.maintenance import optimize_hippo_storage

    store = _make_store(tmp_path)
    tbl = store.db.open_table("records")
    rows = [_random_row() for _ in range(5)]
    tbl.add(rows)
    for r in rows:
        tbl.delete(f"id = '{r['id']}'")

    report = optimize_hippo_storage(store)

    assert "error" not in report["records"], report["records"].get("error")
    assert isinstance(report["records"]["size_bytes_after"], int)
    assert report["records"]["elapsed_sec"] >= 0.0

def test_hnswlib_index_rebuilt_during_optimize(tmp_path: Path) -> None:
    from iai_mcp.maintenance import optimize_hippo_storage

    store = _make_store(tmp_path)
    tbl = store.db.open_table("records")
    tbl.add([_random_row()])

    hnsw_path: Path = store.db._hippo_dir / "records.hnsw"
    if hnsw_path.exists():
        hnsw_path.unlink()

    report = optimize_hippo_storage(store)

    assert "error" not in report["records"], report["records"].get("error")
    assert hnsw_path.exists(), "records.hnsw should be regenerated by optimize"
    import hnswlib
    idx = hnswlib.Index(space="cosine", dim=EMBED_DIM)
    idx.load_index(str(hnsw_path), max_elements=1000)
    assert idx.get_current_count() >= 1

def test_wal_checkpoint_called_before_vacuum(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    from iai_mcp.maintenance import optimize_hippo_storage

    store = _make_store(tmp_path)
    call_log: list[str] = []

    real_conn = store.db._conn

    class _LoggingProxy:
        def execute(self, sql: str, *args, **kwargs):
            sql_upper = sql.strip().upper()
            if "WAL_CHECKPOINT" in sql_upper:
                call_log.append("checkpoint")
            elif sql_upper == "VACUUM":
                call_log.append("vacuum")
            return real_conn.execute(sql, *args, **kwargs)

        def __getattr__(self, name: str):
            return getattr(real_conn, name)

    monkeypatch.setattr(store.db, "_conn", _LoggingProxy())

    optimize_hippo_storage(store)

    assert "checkpoint" in call_log, "wal_checkpoint was not called"
    assert "vacuum" in call_log, "VACUUM was not called"
    cp_idx = call_log.index("checkpoint")
    vac_idx = call_log.index("vacuum")
    assert cp_idx < vac_idx, (
        f"wal_checkpoint (pos {cp_idx}) must come before VACUUM (pos {vac_idx})"
    )

def test_never_raises_on_per_table_error(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    from iai_mcp.maintenance import optimize_hippo_storage

    store = _make_store(tmp_path)
    real_conn = store.db._conn

    class _VacuumErrorProxy:
        def execute(self, sql: str, *args, **kwargs):
            if sql.strip().upper() == "VACUUM":
                raise ValueError("simulated VACUUM failure")
            return real_conn.execute(sql, *args, **kwargs)

        def __getattr__(self, name: str):
            return getattr(real_conn, name)

    monkeypatch.setattr(store.db, "_conn", _VacuumErrorProxy())

    report = optimize_hippo_storage(store)

    assert isinstance(report, dict)
    errors = [sub["error"] for sub in report.values() if "error" in sub]
    assert errors, "expected at least one error entry in report after VACUUM failure"
    assert any("simulated VACUUM failure" in e for e in errors)

def test_optimize_lance_storage_deprecation_alias(tmp_path: Path) -> None:
    from iai_mcp.maintenance import optimize_lance_storage

    store = _make_store(tmp_path)

    with warnings.catch_warnings(record=True) as caught:
        warnings.simplefilter("always")
        result = optimize_lance_storage(store)

    dep_warnings = [w for w in caught if issubclass(w.category, DeprecationWarning)]
    assert dep_warnings, "optimize_lance_storage must emit DeprecationWarning"
    assert "optimize_lance_storage" in str(dep_warnings[0].message).lower() or \
           "deprecated" in str(dep_warnings[0].message).lower()

    assert isinstance(result, dict)
    for table in ("records", "edges", "events"):
        assert table in result
