"""Tests for session management services.

Tests cover:
- SessionStore persistence
- ConversationExporter formats
- ContextManager token tracking
"""

import tempfile
from pathlib import Path

import pytest

from cleave.tui.services.context import ContextManager, ContextWindow, TokenUsage
from cleave.tui.services.exporter import ConversationExporter
from cleave.tui.services.session_store import (
    Conversation,
    ConversationMessage,
    SessionStore,
)


class TestConversationMessage:
    """Tests for ConversationMessage dataclass."""

    def test_default_timestamp(self):
        """Should set timestamp automatically."""
        msg = ConversationMessage(role="user", content="hello")
        assert msg.timestamp != ""

    def test_provided_timestamp(self):
        """Should use provided timestamp."""
        msg = ConversationMessage(role="user", content="hello", timestamp="2024-01-01")
        assert msg.timestamp == "2024-01-01"


class TestConversation:
    """Tests for Conversation dataclass."""

    def test_default_timestamps(self):
        """Should set timestamps automatically."""
        conv = Conversation(id="test-1")
        assert conv.created_at != ""
        assert conv.updated_at != ""

    def test_add_message(self):
        """Should add messages to conversation."""
        conv = Conversation(id="test-1")
        msg = conv.add_message("user", "hello")
        assert len(conv.messages) == 1
        assert msg.role == "user"
        assert msg.content == "hello"

    def test_to_dict(self):
        """Should convert to dictionary."""
        conv = Conversation(id="test-1", title="Test")
        conv.add_message("user", "hello")
        data = conv.to_dict()
        assert data["id"] == "test-1"
        assert data["title"] == "Test"
        assert len(data["messages"]) == 1

    def test_from_dict(self):
        """Should create from dictionary."""
        data = {
            "id": "test-1",
            "title": "Test",
            "messages": [{"role": "user", "content": "hello", "timestamp": "2024-01-01"}],
        }
        conv = Conversation.from_dict(data)
        assert conv.id == "test-1"
        assert len(conv.messages) == 1


class TestSessionStore:
    """Tests for SessionStore."""

    def test_create_conversation(self):
        """Should create new conversations."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = SessionStore(Path(tmpdir))
            conv = store.create("Test Chat")
            assert conv.id.startswith("conv-")
            assert conv.title == "Test Chat"

    def test_save_and_load(self):
        """Should persist and retrieve conversations."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = SessionStore(Path(tmpdir))

            # Create and save
            conv = store.create("Test Chat")
            conv.add_message("user", "hello")
            conv.add_message("assistant", "hi there")
            store.save(conv)

            # Load
            loaded = store.load(conv.id)
            assert loaded is not None
            assert loaded.title == "Test Chat"
            assert len(loaded.messages) == 2

    def test_load_nonexistent(self):
        """Should return None for nonexistent conversations."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = SessionStore(Path(tmpdir))
            assert store.load("nonexistent") is None

    def test_delete(self):
        """Should delete conversations."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = SessionStore(Path(tmpdir))
            conv = store.create()
            store.save(conv)

            assert store.delete(conv.id) is True
            assert store.load(conv.id) is None
            assert store.delete(conv.id) is False  # Already deleted

    def test_list_conversations(self):
        """Should list all conversations."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = SessionStore(Path(tmpdir))

            conv1 = store.create("Chat 1")
            conv2 = store.create("Chat 2")
            store.save(conv1)
            store.save(conv2)

            convs = list(store.list_conversations())
            assert len(convs) == 2

    def test_search_by_title(self):
        """Should search conversations by title."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = SessionStore(Path(tmpdir))

            conv1 = store.create("Python Chat")
            conv2 = store.create("JavaScript Chat")
            store.save(conv1)
            store.save(conv2)

            results = list(store.search("Python"))
            assert len(results) == 1
            assert results[0].title == "Python Chat"

    def test_search_by_content(self):
        """Should search conversations by message content."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = SessionStore(Path(tmpdir))

            conv1 = store.create("Chat 1")
            conv1.add_message("user", "how do I use asyncio?")
            store.save(conv1)

            conv2 = store.create("Chat 2")
            conv2.add_message("user", "what is pandas?")
            store.save(conv2)

            results = list(store.search("asyncio"))
            assert len(results) == 1

    def test_get_recent(self):
        """Should return most recent conversations."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = SessionStore(Path(tmpdir))

            for i in range(5):
                conv = store.create(f"Chat {i}")
                store.save(conv)

            recent = store.get_recent(limit=3)
            assert len(recent) == 3


class TestConversationExporter:
    """Tests for ConversationExporter."""

    def test_to_markdown(self):
        """Should export to markdown."""
        conv = Conversation(id="test-1", title="Test Chat")
        conv.add_message("user", "hello")
        conv.add_message("assistant", "hi there")

        exporter = ConversationExporter()
        md = exporter.to_markdown(conv)

        assert "# Test Chat" in md
        assert "**You:**" in md
        assert "hello" in md
        assert "**Claude:**" in md
        assert "hi there" in md

    def test_to_markdown_with_code(self):
        """Should preserve code blocks in markdown."""
        conv = Conversation(id="test-1", title="Code Chat")
        conv.add_message("assistant", "Here's code:\n```python\nprint('hello')\n```")

        exporter = ConversationExporter()
        md = exporter.to_markdown(conv)

        assert "```python" in md
        assert "print('hello')" in md

    def test_to_json(self):
        """Should export to JSON."""
        conv = Conversation(id="test-1", title="Test Chat")
        conv.add_message("user", "hello")

        exporter = ConversationExporter()
        json_str = exporter.to_json(conv)

        import json
        data = json.loads(json_str)
        assert data["id"] == "test-1"
        assert data["title"] == "Test Chat"
        assert "_export" in data

    def test_from_json(self):
        """Should import from JSON."""
        with tempfile.TemporaryDirectory() as tmpdir:
            json_path = Path(tmpdir) / "test.json"

            conv = Conversation(id="test-1", title="Test Chat")
            conv.add_message("user", "hello")

            exporter = ConversationExporter()
            exporter.to_json(conv, json_path)

            loaded = exporter.from_json(json_path)
            assert loaded is not None
            assert loaded.id == "test-1"
            assert len(loaded.messages) == 1

    def test_to_text(self):
        """Should export to plain text."""
        conv = Conversation(id="test-1", title="Test Chat")
        conv.add_message("user", "hello")

        exporter = ConversationExporter()
        text = exporter.to_text(conv)

        assert "Test Chat" in text
        assert "[You]" in text
        assert "hello" in text


class TestTokenUsage:
    """Tests for TokenUsage tracking."""

    def test_default_values(self):
        """Should start at zero."""
        usage = TokenUsage()
        assert usage.input_tokens == 0
        assert usage.output_tokens == 0
        assert usage.total == 0

    def test_add_tokens(self):
        """Should track token additions."""
        usage = TokenUsage()
        usage.add(input_tokens=100, output_tokens=200)
        assert usage.input_tokens == 100
        assert usage.output_tokens == 200
        assert usage.total == 300

    def test_reset(self):
        """Should reset to zero."""
        usage = TokenUsage()
        usage.add(100, 200)
        usage.reset()
        assert usage.total == 0


class TestContextWindow:
    """Tests for ContextWindow tracking."""

    def test_available_tokens(self):
        """Should calculate available tokens."""
        window = ContextWindow(max_tokens=100_000)
        window.add_usage(30_000)
        assert window.available == 70_000

    def test_usage_percentage(self):
        """Should calculate usage percentage."""
        window = ContextWindow(max_tokens=100_000)
        window.add_usage(50_000)
        assert window.usage_percentage == 0.5

    def test_warning_threshold(self):
        """Should detect warning threshold."""
        window = ContextWindow(max_tokens=100_000, warning_threshold=0.8)
        assert not window.is_warning

        window.add_usage(80_000)
        assert window.is_warning

    def test_is_full(self):
        """Should detect full context."""
        window = ContextWindow(max_tokens=100_000)
        assert not window.is_full

        window.add_usage(95_000)
        assert window.is_full


class TestContextManager:
    """Tests for ContextManager."""

    def test_default_model(self):
        """Should use default model settings."""
        ctx = ContextManager()
        assert ctx.model == "sonnet"
        assert ctx.context_window.max_tokens == 200_000

    def test_set_model(self):
        """Should update context window for model."""
        ctx = ContextManager(model="haiku")
        ctx.set_model("opus")
        assert ctx.model == "opus"

    def test_add_message_tokens(self):
        """Should track message tokens."""
        ctx = ContextManager()
        ctx.add_message_tokens(input_tokens=100, output_tokens=500)

        assert ctx.token_usage.input_tokens == 100
        assert ctx.token_usage.output_tokens == 500
        assert ctx.message_count == 1

    def test_estimate_tokens(self):
        """Should estimate tokens from content."""
        ctx = ContextManager()
        # Roughly 4 chars per token
        estimate = ctx.estimate_message_tokens("a" * 400)
        assert estimate == 100

    def test_get_status(self):
        """Should return status dictionary."""
        ctx = ContextManager()
        ctx.add_message_tokens(1000, 2000)

        status = ctx.get_status()
        assert status["model"] == "sonnet"
        assert status["input_tokens"] == 1000
        assert status["output_tokens"] == 2000
        assert status["total_tokens"] == 3000
        assert status["message_count"] == 1

    def test_get_status_line(self):
        """Should return status line string."""
        ctx = ContextManager()
        ctx.add_message_tokens(50_000, 50_000)

        line = ctx.get_status_line()
        assert "100K/200K" in line
        assert "50%" in line
        assert "1 messages" in line

    def test_reset(self):
        """Should reset all tracking."""
        ctx = ContextManager()
        ctx.add_message_tokens(1000, 2000)
        ctx.reset()

        assert ctx.token_usage.total == 0
        assert ctx.context_window.used_tokens == 0
        assert ctx.message_count == 0
