"""Tests for NovyxMemory, NovyxChatMessageHistory, and NovyxRAMClient."""
import json
import pytest
from unittest.mock import patch, Mock
from tests.conftest import MockMemory, MockSearchResult


# ============================================================================
# NovyxRAMClient Tests
# ============================================================================

class TestNovyxRAMClient:
    """Tests for the NovyxRAMClient wrapper."""

    def test_init(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxRAMClient

        client = NovyxRAMClient(api_key=api_key)
        assert client.api_key == api_key
        assert client.tenant_id == "test"

    def test_store(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxRAMClient

        client = NovyxRAMClient(api_key=api_key)
        result = client.store("Test observation", tags=["test"], importance=7)

        assert result == "mem-123"
        mock_novyx_sdk.remember.assert_called_once_with(
            observation="Test observation",
            tags=["test"],
            importance=7,
            metadata=None,
        )

    def test_search_returns_memory_items(self, mock_novyx_sdk, api_key):
        """search() should handle Memory dataclass objects from recall()."""
        from novyx_langchain.memory import NovyxRAMClient

        client = NovyxRAMClient(api_key=api_key)
        results = client.search("test query", limit=5)

        assert len(results) == 1
        assert results[0].id == "mem-123"
        assert results[0].observation == '{"type": "human", "content": "Hello"}'
        assert results[0].score == 0.95
        mock_novyx_sdk.recall.assert_called_once()

    def test_search_with_tags(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxRAMClient

        client = NovyxRAMClient(api_key=api_key)
        client.search("query", tags=["session:123"])

        call_kwargs = mock_novyx_sdk.recall.call_args[1]
        assert call_kwargs["tags"] == ["session:123"]

    def test_list_memories(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxRAMClient

        client = NovyxRAMClient(api_key=api_key)
        results = client.list_memories(limit=50)

        assert len(results) == 1
        assert results[0].id == "mem-123"
        mock_novyx_sdk.memories.assert_called_once()

    def test_get_memory(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxRAMClient

        client = NovyxRAMClient(api_key=api_key)
        result = client.get_memory("mem-123")

        assert result is not None
        assert result.id == "mem-123"
        mock_novyx_sdk.memory.assert_called_once_with("mem-123")

    def test_delete_memory(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxRAMClient

        client = NovyxRAMClient(api_key=api_key)
        result = client.delete_memory("mem-123")

        assert result is True
        mock_novyx_sdk.forget.assert_called_once_with("mem-123")

    def test_rollback(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxRAMClient

        client = NovyxRAMClient(api_key=api_key)
        result = client.rollback("2 hours ago")

        assert result["success"] is True
        mock_novyx_sdk.rollback.assert_called_once()

    def test_rollback_preview(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxRAMClient

        client = NovyxRAMClient(api_key=api_key)
        result = client.rollback_preview("2 hours ago")

        assert result["safe_rollback"] is True
        mock_novyx_sdk.rollback_preview.assert_called_once_with(target="2 hours ago")

    def test_audit(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxRAMClient

        client = NovyxRAMClient(api_key=api_key)
        result = client.audit(limit=10)

        assert len(result) == 1
        assert result[0]["operation"] == "CREATE"

    def test_audit_export(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxRAMClient

        client = NovyxRAMClient(api_key=api_key)
        result = client.audit_export(format="csv")

        assert b"timestamp" in result
        mock_novyx_sdk.audit_export.assert_called_once_with(format="csv")

    def test_usage(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxRAMClient

        client = NovyxRAMClient(api_key=api_key)
        result = client.usage()

        assert result["tier"] == "free"
        mock_novyx_sdk.usage.assert_called_once()


# ============================================================================
# NovyxChatMessageHistory Tests
# ============================================================================

class TestNovyxChatMessageHistory:
    """Tests for BaseChatMessageHistory implementation."""

    def test_add_message(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxChatMessageHistory
        from langchain_core.messages import HumanMessage

        history = NovyxChatMessageHistory(
            api_key=api_key,
            session_id="test-session",
        )

        history.add_message(HumanMessage(content="Hello"))

        mock_novyx_sdk.remember.assert_called_once()
        call_kwargs = mock_novyx_sdk.remember.call_args[1]
        obs = json.loads(call_kwargs["observation"])
        assert obs["type"] == "human"
        assert obs["content"] == "Hello"

    def test_add_ai_message(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxChatMessageHistory
        from langchain_core.messages import AIMessage

        history = NovyxChatMessageHistory(
            api_key=api_key,
            session_id="test-session",
        )

        history.add_message(AIMessage(content="Hi there"))

        call_kwargs = mock_novyx_sdk.remember.call_args[1]
        obs = json.loads(call_kwargs["observation"])
        assert obs["type"] == "ai"
        assert call_kwargs["importance"] == 6  # AI messages get importance 6

    def test_messages_property(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxChatMessageHistory

        history = NovyxChatMessageHistory(
            api_key=api_key,
            session_id="test-session",
        )

        messages = history.messages

        assert len(messages) == 1
        assert messages[0].content == "Hello"

    def test_clear(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxChatMessageHistory

        # Make clear_all find and delete memories
        mock_novyx_sdk.memories.return_value = [
            {"uuid": "mem-1", "observation": "test", "tags": [], "importance": 5}
        ]

        history = NovyxChatMessageHistory(
            api_key=api_key,
            session_id="test-session",
        )

        history.clear()
        mock_novyx_sdk.forget.assert_called()

    def test_session_tags(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxChatMessageHistory
        from langchain_core.messages import HumanMessage

        history = NovyxChatMessageHistory(
            api_key=api_key,
            session_id="my-session",
        )

        history.add_message(HumanMessage(content="test"))

        call_kwargs = mock_novyx_sdk.remember.call_args[1]
        assert "session:my-session" in call_kwargs["tags"]
        assert "chat:message" in call_kwargs["tags"]


# ============================================================================
# NovyxMemory Tests
# ============================================================================

class TestNovyxMemory:
    """Tests for the full BaseMemory implementation."""

    def test_memory_variables(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxMemory

        memory = NovyxMemory(api_key=api_key, session_id="test")
        assert memory.memory_variables == ["history"]

    def test_load_memory_variables(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxMemory

        memory = NovyxMemory(
            api_key=api_key,
            session_id="test",
            return_messages=True,
        )

        result = memory.load_memory_variables({"input": "test query"})

        assert "history" in result
        mock_novyx_sdk.recall.assert_called()

    def test_save_context(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxMemory

        memory = NovyxMemory(api_key=api_key, session_id="test")

        memory.save_context(
            {"input": "Hello"},
            {"output": "Hi there"},
        )

        # Should store both human and AI messages
        assert mock_novyx_sdk.remember.call_count == 2

    def test_clear(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxMemory

        mock_novyx_sdk.memories.return_value = []

        memory = NovyxMemory(api_key=api_key, session_id="test")
        memory.clear()

    def test_rollback(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxMemory

        memory = NovyxMemory(api_key=api_key, session_id="test")
        result = memory.rollback("2 hours ago")

        assert result["success"] is True
        mock_novyx_sdk.rollback.assert_called_once()

    def test_rollback_preview(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxMemory

        memory = NovyxMemory(api_key=api_key, session_id="test")
        result = memory.rollback_preview("2 hours ago")

        assert result["safe_rollback"] is True
        mock_novyx_sdk.rollback_preview.assert_called_once()

    def test_audit(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxMemory

        memory = NovyxMemory(api_key=api_key, session_id="test")
        result = memory.audit(limit=10)

        assert len(result) == 1
        mock_novyx_sdk.audit.assert_called_once()

    def test_audit_export(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxMemory

        memory = NovyxMemory(api_key=api_key, session_id="test")
        result = memory.audit_export(format="csv")

        assert b"timestamp" in result
        mock_novyx_sdk.audit_export.assert_called_once()

    def test_usage(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxMemory

        memory = NovyxMemory(api_key=api_key, session_id="test")
        result = memory.usage()

        assert result["tier"] == "free"
        mock_novyx_sdk.usage.assert_called_once()

    def test_store_memory(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxMemory

        memory = NovyxMemory(api_key=api_key, session_id="test")
        result = memory.store_memory("Custom note", tags=["custom"], importance=8)

        assert result == "mem-123"
        call_kwargs = mock_novyx_sdk.remember.call_args[1]
        assert "session:test" in call_kwargs["tags"]
        assert "custom" in call_kwargs["tags"]

    def test_return_as_string(self, mock_novyx_sdk, api_key):
        from novyx_langchain.memory import NovyxMemory

        memory = NovyxMemory(
            api_key=api_key,
            session_id="test",
            return_messages=False,
        )

        result = memory.load_memory_variables({"input": "test"})
        assert isinstance(result["history"], str)
