import importlib.util
import json
import os
import sys
from pathlib import Path


HERMES_ROOT = Path("/home/tom/hermes-agent")
PLUGIN_PATH = Path(__file__).resolve().parents[1] / "plugins" / "memory" / "memory_system" / "__init__.py"


def load_plugin_module():
    if str(HERMES_ROOT) not in sys.path:
        sys.path.insert(0, str(HERMES_ROOT))
    spec = importlib.util.spec_from_file_location("memory_system_plugin", PLUGIN_PATH)
    module = importlib.util.module_from_spec(spec)
    assert spec.loader is not None
    spec.loader.exec_module(module)
    return module


class FakeClient:
    def __init__(self, commit_status="success"):
        self.posts = []
        self.gets = []
        self.deletes = []
        self.commit_status = commit_status

    def post(self, path, payload=None):
        self.posts.append((path, payload or {}))
        if path == "/memory-system/users":
            return {
                "status": "success",
                "account": {
                    "status": "ok",
                    "result": {
                        "account_id": f"{payload['user_id']}_account",
                        "admin_user_id": payload["user_id"],
                        "user_key": f"{payload['user_id']}-key",
                    },
                },
            }
        if path == "/memory-system/search":
            return {
                "status": "success",
                "items": [
                    {
                        "source_backend": "openviking",
                        "content": "likes latte",
                        "score": 0.9,
                        "uri": "viking://user/user-1/memories/a",
                        "vector": [0.1, 0.2],
                    },
                    {
                        "source_backend": "everos",
                        "memory": "prefers warm coffee",
                        "memory_type": "profile",
                        "original_data": {"large": "payload"},
                    },
                ],
                "backends": {
                    "openviking": {"status": "success", "result": {"verbose": True}},
                    "everos": {"status": "success", "result": {"verbose": True}},
                },
            }
        if path.endswith("/commit"):
            return {"status": self.commit_status}
        if path == "/memory-system/memories":
            return {"status": "success", "memory": {"status": "ok", "result": {"uri": payload.get("uri")}}}
        return {"status": "success"}

    def get(self, path):
        self.gets.append(path)
        if path.startswith("/memory-system/memories/content"):
            return {"status": "success", "memory": {"status": "ok", "result": {"content": "# Python"}}}
        if path.startswith("/memory-system/memories"):
            return {"status": "success", "memory": {"status": "ok", "result": {"children": []}}}
        return {"status": "success", "profile": {"coffee": "latte"}}

    def delete(self, path):
        self.deletes.append(path)
        return {"status": "success", "memory": {"status": "ok", "result": {"estimated_deleted_count": 1}}}


def make_provider():
    module = load_plugin_module()
    provider = module.MemorySystemMemoryProvider()
    provider._client = FakeClient()
    provider._endpoint = "http://127.0.0.1:1934"
    provider._user_id = "user-1"
    provider._user_key = "user-1-key"
    provider._session_id = "session-1"
    provider._commit_every_turns = 0
    provider._commit_interval_seconds = 0
    return provider


def wait_for_sync(provider):
    thread = provider._sync_thread
    if thread and thread.is_alive():
        thread.join(timeout=2.0)


def test_initialize_loads_config_from_hermes_env_file(tmp_path, monkeypatch):
    module = load_plugin_module()
    hermes_home = tmp_path / ".hermes"
    hermes_home.mkdir()
    env_file = hermes_home / ".env"
    env_file.write_text(
        "\n".join(
            [
                "MEMORY_SYSTEM_ENDPOINT=http://127.0.0.1:1934",
                "MEMORY_SYSTEM_USER_ID=file-user",
                "MEMORY_SYSTEM_USER_KEY=file-user-key",
                "MEMORY_SYSTEM_COMMIT_EVERY_TURNS=3",
                "MEMORY_SYSTEM_COMMIT_INTERVAL_SECONDS=60",
                "MEMORY_SYSTEM_TIMEOUT_SECONDS=123",
            ]
        ),
        encoding="utf-8",
    )
    for key in list(os.environ):
        if key.startswith("MEMORY_SYSTEM_"):
            monkeypatch.delenv(key, raising=False)
    monkeypatch.chdir(tmp_path)

    class InitClient(FakeClient):
        def __init__(self, endpoint, api_key="", timeout=0):
            super().__init__()
            self.endpoint = endpoint
            self.api_key = api_key
            self.timeout = timeout

        def health(self):
            return True

    monkeypatch.setattr(module, "_MemorySystemClient", InitClient)
    provider = module.MemorySystemMemoryProvider()

    provider.initialize("session-1", hermes_home=str(hermes_home))

    assert provider._endpoint == "http://127.0.0.1:1934"
    assert provider._user_id == "file-user"
    assert provider._user_key == "file-user-key"
    assert provider._commit_every_turns == 3
    assert provider._commit_interval_seconds == 60
    assert provider._timeout == 123
    assert provider._client.endpoint == "http://127.0.0.1:1934"
    assert provider._client.timeout == 123


def test_sync_turn_posts_user_and_assistant_messages():
    provider = make_provider()

    provider.sync_turn("hello", "hi there")
    wait_for_sync(provider)

    assert provider._client.posts == [
        (
            "/memory-system/messages",
            {
                "user_id": "user-1",
                "user_key": "user-1-key",
                "session_id": "session-1",
                "user_message": "hello",
                "assistant_message": "hi there",
                "metadata": {"source": "hermes", "provider": "memory_system"},
            },
        )
    ]


def test_on_session_end_commits_after_turn_sync():
    provider = make_provider()
    provider.sync_turn("hello", "hi there")

    provider.on_session_end([])

    assert provider._client.posts[-1] == (
        "/memory-system/sessions/session-1/commit",
        {"user_id": "user-1", "user_key": "user-1-key"},
    )


def test_sync_turn_commits_every_configured_turns():
    provider = make_provider()
    provider._commit_every_turns = 2
    provider._commit_interval_seconds = 0

    provider.sync_turn("turn 1", "reply 1")
    wait_for_sync(provider)
    provider.sync_turn("turn 2", "reply 2")
    wait_for_sync(provider)

    assert provider._client.posts[-1] == (
        "/memory-system/sessions/session-1/commit",
        {"user_id": "user-1", "user_key": "user-1-key"},
    )
    assert provider._last_commit_turn == 2


def test_partial_commit_does_not_mark_turns_committed():
    provider = make_provider()
    provider._client = FakeClient(commit_status="partial_success")
    provider._turn_count = 2

    response = provider._commit_session("session-1")

    assert response["status"] == "partial_success"
    assert provider._last_commit_turn == 0


def test_on_session_end_skips_when_periodic_commit_is_current():
    provider = make_provider()
    provider._commit_every_turns = 1
    provider._commit_interval_seconds = 0
    provider.sync_turn("hello", "hi there")
    wait_for_sync(provider)

    provider.on_session_end([])

    commit_posts = [
        post for post in provider._client.posts if post[0] == "/memory-system/sessions/session-1/commit"
    ]
    assert len(commit_posts) == 1


def test_search_tool_uses_memory_system_api():
    provider = make_provider()

    result = json.loads(provider.handle_tool_call("memory_system_search", {"query": "coffee", "limit": 3}))

    assert result["status"] == "success"
    assert result["items"] == [
        {
            "source_backend": "openviking",
            "score": 0.9,
            "text": "likes latte",
            "uri": "viking://user/user-1/memories/a",
        },
        {
            "source_backend": "everos",
            "memory_type": "profile",
            "text": "prefers warm coffee",
        },
    ]
    assert "backends" not in result
    assert "vector" not in json.dumps(result)
    assert "original_data" not in json.dumps(result)
    assert provider._client.posts[-1] == (
            "/memory-system/search",
            {
                "user_id": "user-1",
                "user_key": "user-1-key",
                "session_id": "session-1",
                "query": "coffee",
                "use_llm": False,
            "limit": 3,
        },
    )


def test_profile_tool_reads_user_profile():
    provider = make_provider()

    result = json.loads(provider.handle_tool_call("memory_system_profile", {}))

    assert result["profile"] == {"coffee": "latte"}
    assert provider._client.gets == ["/memory-system/users/user-1/profile?user_key=user-1-key"]


def test_remember_tool_writes_and_commits():
    provider = make_provider()

    result = json.loads(provider.handle_tool_call("memory_system_remember", {"content": "likes latte"}))

    assert result["status"] == "success"
    assert provider._client.posts == [
        (
            "/memory-system/messages",
            {
                "user_id": "user-1",
                "user_key": "user-1-key",
                "session_id": "session-1",
                "user_message": "likes latte",
                "metadata": {"source": "hermes", "provider": "memory_system"},
            },
        ),
        (
            "/memory-system/sessions/session-1/commit",
            {"user_id": "user-1", "user_key": "user-1-key"},
        ),
    ]


def test_initialize_creates_user_when_user_key_is_not_configured(tmp_path, monkeypatch):
    module = load_plugin_module()
    for key in list(os.environ):
        if key.startswith("MEMORY_SYSTEM_"):
            monkeypatch.delenv(key, raising=False)
    monkeypatch.setenv("MEMORY_SYSTEM_ENDPOINT", "http://127.0.0.1:1934")
    monkeypatch.setenv("MEMORY_SYSTEM_USER_ID", "new-user")
    monkeypatch.chdir(tmp_path)

    class InitClient(FakeClient):
        def __init__(self, endpoint, api_key="", timeout=0):
            super().__init__()
            self.endpoint = endpoint

        def health(self):
            return True

    monkeypatch.setattr(module, "_MemorySystemClient", InitClient)
    provider = module.MemorySystemMemoryProvider()

    provider.initialize("session-1")

    assert provider._user_key == "new-user-key"
    assert provider._client.posts == [("/memory-system/users", {"user_id": "new-user"})]


def test_memory_tool_schemas_include_memory_management_tools():
    provider = make_provider()

    tool_names = {schema["name"] for schema in provider.get_tool_schemas()}

    assert {
        "memory_system_memory_list",
        "memory_system_memory_read",
        "memory_system_memory_write",
        "memory_system_memory_delete",
    } <= tool_names


def test_memory_list_tool_calls_memories_endpoint():
    provider = make_provider()

    result = json.loads(provider.handle_tool_call("memory_system_memory_list", {"recursive": True}))

    assert result["status"] == "success"
    assert provider._client.gets == [
        "/memory-system/memories?user_id=user-1&user_key=user-1-key&uri=viking%3A%2F%2Fuser%2Fmemories&recursive=true"
    ]


def test_memory_read_tool_calls_memory_content_endpoint():
    provider = make_provider()

    result = json.loads(provider.handle_tool_call(
        "memory_system_memory_read",
        {"uri": "viking://user/memories/preferences/python.md"},
    ))

    assert result["status"] == "success"
    assert provider._client.gets == [
        "/memory-system/memories/content?user_id=user-1&user_key=user-1-key&uri=viking%3A%2F%2Fuser%2Fmemories%2Fpreferences%2Fpython.md"
    ]


def test_memory_write_tool_posts_memory_payload():
    provider = make_provider()

    result = json.loads(provider.handle_tool_call(
        "memory_system_memory_write",
        {
            "uri": "viking://user/memories/preferences/python.md",
            "content": "# Python",
            "mode": "replace",
            "wait": True,
        },
    ))

    assert result["status"] == "success"
    assert provider._client.posts == [
        (
            "/memory-system/memories",
            {
                "user_id": "user-1",
                "user_key": "user-1-key",
                "uri": "viking://user/memories/preferences/python.md",
                "content": "# Python",
                "mode": "replace",
                "wait": True,
            },
        )
    ]


def test_memory_delete_tool_deletes_memory_uri_non_recursive_by_default():
    provider = make_provider()

    result = json.loads(provider.handle_tool_call(
        "memory_system_memory_delete",
        {"uri": "viking://user/memories/preferences/python.md"},
    ))

    assert result["status"] == "success"
    assert provider._client.deletes == [
        "/memory-system/memories?user_id=user-1&user_key=user-1-key&uri=viking%3A%2F%2Fuser%2Fmemories%2Fpreferences%2Fpython.md&recursive=false"
    ]


def test_register_adds_provider():
    module = load_plugin_module()

    class Ctx:
        def __init__(self):
            self.providers = []

        def register_memory_provider(self, provider):
            self.providers.append(provider)

    ctx = Ctx()
    module.register(ctx)

    assert len(ctx.providers) == 1
    assert ctx.providers[0].name == "memory_system"
