import pytest

fastapi = pytest.importorskip("fastapi")
from fastapi.testclient import TestClient

from neurovault.api import create_app
from neurovault.engine import NeurovaultEngine


def test_api_health_and_ingest(monkeypatch, tmp_path):
    vault = NeurovaultEngine.init("test", no_embedding=True, base_dir=str(tmp_path))
    monkeypatch.setenv("NEUROVAULT_BOOTSTRAP_TOKEN", "test-bootstrap-token")

    import neurovault.api as api_module

    original_open = api_module.NeurovaultEngine.open
    monkeypatch.setattr(api_module.NeurovaultEngine, "open", lambda name: vault)
    try:
        app = create_app(vault_name="test")
        client = TestClient(app)

        health = client.get("/api/v1/health")
        assert health.status_code == 200

        ready = client.get("/api/v1/health/ready")
        assert ready.status_code == 200
        assert ready.json()["status"] == "ready"

        ingest = client.post("/api/v1/ingest", json={"content": "API memory", "source": "api"})
        assert ingest.status_code == 200

        recall = client.get("/api/v1/recall", params={"q": "API", "mode": "keyword", "limit": 5})
        assert recall.status_code == 200
        assert len(recall.json()) >= 1

        decay = client.post("/api/v1/decay/run")
        assert decay.status_code == 200

        events = client.get("/api/v1/events", params={"limit": 5})
        assert events.status_code == 200
        assert isinstance(events.json(), list)

        plugins = client.get("/api/v1/plugins")
        assert plugins.status_code == 200
        assert isinstance(plugins.json(), list)

        local_plugins = client.get("/api/v1/plugins/local")
        assert local_plugins.status_code == 200
        assert isinstance(local_plugins.json(), list)

        enable_local = client.post("/api/v1/plugins/demo-plugin/enable")
        assert enable_local.status_code == 200

        disable_local = client.post("/api/v1/plugins/demo-plugin/disable")
        assert disable_local.status_code == 200

        graph_rebuild = client.post("/api/v1/chronos/graph/rebuild")
        assert graph_rebuild.status_code == 200
        assert isinstance(graph_rebuild.json().get("ok"), bool)

        graph_neighbors = client.get("/api/v1/chronos/graph/neighbors", params={"entity_name": "API"})
        assert graph_neighbors.status_code == 200
        assert isinstance(graph_neighbors.json().get("neighbors"), list)

        graph_path = client.get(
            "/api/v1/chronos/graph/path",
            params={"source": "API", "target": "memory"},
        )
        assert graph_path.status_code == 200
        assert isinstance(graph_path.json().get("path"), list)

        stats = client.get("/api/v1/stats")
        assert stats.status_code == 200
        assert isinstance(stats.json(), dict)
        assert "memory_count" in stats.json()

        metrics = client.get("/metrics")
        assert metrics.status_code == 200
        assert "neurovault_memories_total" in metrics.text

        graphql_stats = client.post("/api/v1/graphql", json={"query": "query { stats { memoryCount } }"})
        assert graphql_stats.status_code == 200
        assert "data" in graphql_stats.json()
        assert "stats" in graphql_stats.json()["data"]

        graphql_ingest = client.post(
            "/api/v1/graphql",
            json={
                "query": "mutation Ingest($content: String!) { ingest(content: $content) { id } }",
                "variables": {"content": "GraphQL memory ingest sample", "source": "graphql"},
            },
        )
        assert graphql_ingest.status_code == 200
        assert "data" in graphql_ingest.json()

        hnsw_status = client.get("/api/v1/search/hnsw/status")
        assert hnsw_status.status_code == 200
        assert isinstance(hnsw_status.json(), dict)

        network_device = client.get("/api/v1/network/device")
        assert network_device.status_code == 200
        assert isinstance(network_device.json(), dict)

        network_sync_status = client.get("/api/v1/network/sync/status")
        assert network_sync_status.status_code == 200
        assert isinstance(network_sync_status.json(), dict)

        network_bundle = client.post("/api/v1/network/sync/bundle", json={})
        assert network_bundle.status_code == 200
        assert isinstance(network_bundle.json(), dict)

        network_apply = client.post("/api/v1/network/sync/apply", json=network_bundle.json())
        assert network_apply.status_code == 200
        assert isinstance(network_apply.json(), dict)

        network_peer = client.post("/api/v1/network/sync/peer", json={"host": "127.0.0.1", "port": 9473})
        assert network_peer.status_code == 200
        assert isinstance(network_peer.json(), dict)

        admin_create = client.post(
            "/api/v1/admin/users",
            json={"username": "alice", "role": "admin"},
            headers={"X-Neurovault-Bootstrap-Token": "test-bootstrap-token"},
        )
        assert admin_create.status_code == 200
        admin_payload = admin_create.json()
        assert admin_payload["username"] == "alice"
        admin_headers = {"Authorization": f"Bearer {admin_payload['token']}"}

        admin_list = client.get("/api/v1/admin/users", headers=admin_headers)
        assert admin_list.status_code == 200
        assert any(item["username"] == "alice" for item in admin_list.json())

        device_token = client.post(
            "/api/v1/admin/device-token",
            json={"device_name": "ios-phone", "permissions": ["read"]},
            headers=admin_headers,
        )
        assert device_token.status_code == 200
        assert device_token.json()["device_name"] == "ios-phone"

        audit_log = client.get("/api/v1/audit/log", headers=admin_headers, params={"limit": 20})
        assert audit_log.status_code == 200
        assert isinstance(audit_log.json(), list)

        admin_revoke = client.post("/api/v1/admin/users/alice/revoke", headers=admin_headers)
        assert admin_revoke.status_code == 200
        assert admin_revoke.json()["ok"] is True

        enrich = client.post(
            "/api/v1/ingest",
            json={"content": "Alex uses Python in Project Atlas", "source": "api"},
        )
        assert enrich.status_code == 200

        ents = client.get("/api/v1/entities", params={"limit": 10})
        assert ents.status_code == 200
        assert len(ents.json()) >= 1
        entity_id = ents.json()[0]["id"]

        entity_relationships = client.get(f"/api/v1/entities/{entity_id}/relationships")
        assert entity_relationships.status_code == 200
        assert isinstance(entity_relationships.json(), list)

        entity_hierarchy = client.get(f"/api/v1/entities/{entity_id}/hierarchy")
        assert entity_hierarchy.status_code == 200
        assert isinstance(entity_hierarchy.json(), dict)

        concept_tree = client.get("/api/v1/concepts/tree", params={"limit": 10})
        assert concept_tree.status_code == 200
        assert isinstance(concept_tree.json(), list)

        sensors_status = client.get("/api/v1/sensors/status")
        assert sensors_status.status_code == 200
        assert isinstance(sensors_status.json(), dict)

        pipeline_stats = client.get("/api/v1/pipeline/stats")
        assert pipeline_stats.status_code == 200
        assert isinstance(pipeline_stats.json(), dict)

        personas = client.get("/api/v1/personas")
        assert personas.status_code == 200
        assert personas.json()["active_persona"] == "default"

        create_persona = client.post("/api/v1/personas", json={"name": "work"})
        assert create_persona.status_code == 200
        assert create_persona.json()["name"] == "work"

        switch = client.put("/api/v1/personas/active", json={"name": "work"})
        assert switch.status_code == 200
        assert switch.json()["active_persona"] == "work"

        ingest_work = client.post("/api/v1/ingest", json={"content": "Work milestone due Friday", "source": "api"})
        assert ingest_work.status_code == 200

        switch_back = client.put("/api/v1/personas/active", json={"name": "default"})
        assert switch_back.status_code == 200
        assert switch_back.json()["active_persona"] == "default"

        merge = client.post(
            "/api/v1/merge",
            json={"source_persona": "work", "strategy": "additive", "dry_run": True},
        )
        assert merge.status_code == 200
        assert merge.json()["added"] >= 1

        cp1 = client.post("/api/v1/checkpoint", json={"message": "before-diff"})
        assert cp1.status_code == 200
        ingest2 = client.post("/api/v1/ingest", json={"content": "Diff payload memory", "source": "api"})
        assert ingest2.status_code == 200
        cp2 = client.post("/api/v1/checkpoint", json={"message": "after-diff"})
        assert cp2.status_code == 200

        diff = client.get("/api/v1/checkpoints/diff", params={"ref1": "HEAD~1", "ref2": "HEAD", "limit": 10})
        assert diff.status_code == 200
        assert isinstance(diff.json().get("added"), list)

        milestone = client.post("/api/v1/milestones", json={"name": "api-pre", "ref": "HEAD~1"})
        assert milestone.status_code == 200
        milestone_list = client.get("/api/v1/milestones", params={"limit": 10})
        assert milestone_list.status_code == 200
        assert any(item["name"] == "api-pre" for item in milestone_list.json())

        revert = client.post("/api/v1/revert", json={"ref": "api-pre", "hard": False})
        assert revert.status_code == 200

        verify = client.get("/api/v1/verify")
        assert verify.status_code == 200
        assert "memories_checked" in verify.json()

        key = client.post("/api/v1/keys/generate", json={"name": "default"})
        assert key.status_code == 200
        assert isinstance(key.json(), dict)

        sync_push = client.post(
            "/api/v1/sync/push",
            json={"peer_name": "peer-demo", "output_path": str(tmp_path / "bundle.csf")},
        )
        assert sync_push.status_code == 200
        assert isinstance(sync_push.json(), dict)

        sync_pull = client.post(
            "/api/v1/sync/pull",
            json={"bundle_path": str(tmp_path / "bundle.csf"), "strategy": "newer_wins"},
        )
        assert sync_pull.status_code == 200
        assert isinstance(sync_pull.json(), dict)
    finally:
        monkeypatch.setattr(api_module.NeurovaultEngine, "open", original_open)


def test_admin_bootstrap_requires_token(monkeypatch, tmp_path):
    vault = NeurovaultEngine.init("test", no_embedding=True, base_dir=str(tmp_path))

    import neurovault.api as api_module

    original_open = api_module.NeurovaultEngine.open
    monkeypatch.setattr(api_module.NeurovaultEngine, "open", lambda name: vault)
    try:
        app = create_app(vault_name="test")
        client = TestClient(app)
        disabled = client.post("/api/v1/admin/users", json={"username": "alice", "role": "admin"})
        assert disabled.status_code == 503

        monkeypatch.setenv("NEUROVAULT_BOOTSTRAP_TOKEN", "bootstrap-secret")
        app = create_app(vault_name="test")
        client = TestClient(app)

        missing = client.post("/api/v1/admin/users", json={"username": "alice", "role": "admin"})
        assert missing.status_code == 401

        invalid = client.post(
            "/api/v1/admin/users",
            json={"username": "alice", "role": "admin"},
            headers={"X-Neurovault-Bootstrap-Token": "wrong-secret"},
        )
        assert invalid.status_code == 403
    finally:
        monkeypatch.setattr(api_module.NeurovaultEngine, "open", original_open)
