"""
Tests for P2PChatSession.

Verifies the incremental session recording functionality:
- Message buffering and auto-checkpoint
- Linked-list MCard structure
- Summary compilation
- Chain traversal
"""

import json

import pytest

from mcard.model.card_collection import CardCollection
from mcard.ptr.core.p2p_session import (
    P2PChatSession,
    SessionMessage,
)


@pytest.fixture
def collection():
    """Create an in-memory CardCollection."""
    return CardCollection(db_path=":memory:")


@pytest.fixture
def session(collection):
    """Create a P2PChatSession with default settings."""
    return P2PChatSession(
        collection=collection, session_id="test-session", max_buffer_size=3
    )


class TestSessionMessage:
    """Tests for SessionMessage dataclass."""

    def test_create_message(self):
        """Should create a message with timestamp."""
        msg = SessionMessage(sender="Alice", content="Hello!")

        assert msg.sender == "Alice"
        assert msg.content == "Hello!"
        assert msg.timestamp > 0


class TestP2PChatSessionBasic:
    """Basic tests for P2PChatSession."""

    def test_initial_state(self, session):
        """New session should have empty buffer and no head hash."""
        assert session.get_head_hash() is None
        assert len(session.buffer) == 0
        assert session.sequence == 0

    def test_add_message_buffers(self, session):
        """Adding message should buffer without checkpointing."""
        result = session.add_message("Alice", "Hello!")

        assert result is None  # No checkpoint yet
        assert len(session.buffer) == 1
        assert session.buffer[0].sender == "Alice"
        assert session.buffer[0].content == "Hello!"

    def test_add_message_triggers_checkpoint(self, session):
        """Buffer should checkpoint when full."""
        # Add messages up to buffer size
        session.add_message("Alice", "Message 1")
        session.add_message("Bob", "Message 2")
        result = session.add_message("Alice", "Message 3")  # Triggers checkpoint

        assert result is not None  # Checkpoint hash returned
        assert session.get_head_hash() == result
        assert len(session.buffer) == 0  # Buffer cleared
        assert session.sequence == 1

    def test_checkpoint_creates_mcard(self, session, collection):
        """Checkpoint should create an MCard in the collection."""
        session.add_message("Alice", "Test message")
        hash_val = session.checkpoint()

        assert hash_val is not None
        card = collection.get(hash_val)
        assert card is not None

        content = json.loads(card.get_content().decode("utf-8"))
        assert content["type"] == "p2p_session_segment"
        assert content["sessionId"] == "test-session"
        assert len(content["messages"]) == 1

    def test_checkpoint_links_to_previous(self, session, collection):
        """Each checkpoint should link to the previous one."""
        # First checkpoint
        session.add_message("Alice", "First")
        first_hash = session.checkpoint()

        # Second checkpoint
        session.add_message("Bob", "Second")
        second_hash = session.checkpoint()

        # Verify link
        second_card = collection.get(second_hash)
        content = json.loads(second_card.get_content().decode("utf-8"))

        assert content["previousHash"] == first_hash

    def test_checkpoint_empty_buffer(self, session):
        """Checkpoint on empty buffer should return existing head."""
        hash1 = session.checkpoint()  # Empty, returns ""

        session.add_message("Alice", "Test")
        hash2 = session.checkpoint()

        hash3 = session.checkpoint()  # Empty again

        assert hash1 == ""
        assert hash2 is not None
        assert hash3 == hash2  # Same as last real checkpoint


class TestP2PChatSessionResume:
    """Tests for session resumption."""

    def test_resume_from_head_hash(self, collection):
        """Should resume session from initial head hash."""
        # Create first session with some data
        session1 = P2PChatSession(
            collection=collection, session_id="resume-test", max_buffer_size=5
        )
        session1.add_message("Alice", "Original message")
        head_hash = session1.checkpoint()

        # Create second session resuming from head
        session2 = P2PChatSession(
            collection=collection,
            session_id="resume-test",
            max_buffer_size=5,
            initial_head_hash=head_hash,
        )

        assert session2.get_head_hash() == head_hash

        # Add more messages
        session2.add_message("Bob", "Continued message")
        new_hash = session2.checkpoint()

        # Verify chain
        card = collection.get(new_hash)
        content = json.loads(card.get_content().decode("utf-8"))
        assert content["previousHash"] == head_hash


class TestP2PChatSessionSummarize:
    """Tests for session summarization."""

    def test_summarize_creates_summary(self, session, collection):
        """Summarize should create a summary MCard."""
        session.add_message("Alice", "Hello")
        session.add_message("Bob", "Hi there")
        session.add_message("Alice", "How are you?")
        session.checkpoint()

        summary_hash = session.summarize(keep_originals=True)

        assert summary_hash is not None
        card = collection.get(summary_hash)
        content = json.loads(card.get_content().decode("utf-8"))

        assert content["type"] == "p2p_session_summary"
        assert content["sessionId"] == "test-session"
        assert len(content["fullTranscript"]) == 3

    def test_summarize_preserves_order(self, session, collection):
        """Summary should preserve message order."""
        messages = ["First", "Second", "Third", "Fourth", "Fifth"]

        for i, msg in enumerate(messages):
            sender = "Alice" if i % 2 == 0 else "Bob"
            session.add_message(sender, msg)

        summary_hash = session.summarize(keep_originals=True)

        card = collection.get(summary_hash)
        content = json.loads(card.get_content().decode("utf-8"))

        transcript_contents = [m["content"] for m in content["fullTranscript"]]
        assert transcript_contents == messages

    def test_summarize_flushes_buffer(self, session, collection):
        """Summarize should flush any remaining buffer."""
        session.add_message("Alice", "Pending message")
        # Buffer has 1 message, not yet checkpointed

        summary_hash = session.summarize(keep_originals=True)

        card = collection.get(summary_hash)
        content = json.loads(card.get_content().decode("utf-8"))

        assert len(content["fullTranscript"]) == 1
        assert content["fullTranscript"][0]["content"] == "Pending message"

    def test_summarize_removes_segments(self, session, collection):
        """Summarize with keep_originals=False should remove segment MCards."""
        session.add_message("Alice", "One")
        session.add_message("Bob", "Two")
        session.add_message("Alice", "Three")
        segment_hash = session.checkpoint()

        # Verify segment exists
        assert collection.get(segment_hash) is not None

        summary_hash = session.summarize(keep_originals=False)

        # Segment should be deleted
        assert collection.get(segment_hash) is None

        # Summary should exist
        assert collection.get(summary_hash) is not None

    def test_summarize_keeps_segments(self, session, collection):
        """Summarize with keep_originals=True should preserve segment MCards."""
        session.add_message("Alice", "One")
        session.add_message("Bob", "Two")
        session.add_message("Alice", "Three")
        segment_hash = session.checkpoint()

        summary_hash = session.summarize(keep_originals=True)

        # Both should exist
        assert collection.get(segment_hash) is not None
        assert collection.get(summary_hash) is not None

    def test_summarize_empty_session(self, session, collection):
        """Summarize on empty session should create empty summary."""
        summary_hash = session.summarize()

        card = collection.get(summary_hash)
        content = json.loads(card.get_content().decode("utf-8"))

        assert content["type"] == "p2p_session_summary"
        assert content["fullTranscript"] == []
        assert content["originalHeadHash"] is None


class TestP2PChatSessionChainTraversal:
    """Tests for chain traversal functionality."""

    def test_traverse_single_segment(self, session, collection):
        """Should traverse a single segment."""
        session.add_message("Alice", "Hello")
        session.add_message("Bob", "Hi")
        head_hash = session.checkpoint()

        messages, hashes = session._traverse_chain(head_hash)

        assert len(messages) == 2
        assert len(hashes) == 1
        assert messages[0]["content"] == "Hello"
        assert messages[1]["content"] == "Hi"

    def test_traverse_multiple_segments(self, session, collection):
        """Should traverse multiple linked segments."""
        # Create first segment
        session.add_message("Alice", "First")
        session.add_message("Bob", "Second")
        session.add_message("Alice", "Third")
        session.checkpoint()

        # Create second segment
        session.add_message("Bob", "Fourth")
        session.add_message("Alice", "Fifth")
        session.add_message("Bob", "Sixth")
        head_hash = session.checkpoint()

        messages, hashes = session._traverse_chain(head_hash)

        assert len(messages) == 6
        assert len(hashes) == 2

        contents = [m["content"] for m in messages]
        assert contents == ["First", "Second", "Third", "Fourth", "Fifth", "Sixth"]

    def test_traverse_broken_chain(self, session, collection):
        """Should handle broken chain gracefully."""
        session.add_message("Alice", "Test")
        head_hash = session.checkpoint()

        # Delete the card to break the chain
        collection.delete(head_hash)

        # Try to traverse (should handle missing card)
        messages, hashes = session._traverse_chain(head_hash)

        # Should return empty since head is missing
        assert len(messages) == 0


class TestP2PChatSessionIntegration:
    """Integration tests for realistic usage scenarios."""

    def test_full_conversation_flow(self, collection):
        """Test a complete conversation flow."""
        session = P2PChatSession(
            collection=collection,
            session_id="integration-test",
            max_buffer_size=2,  # Small buffer for testing
        )

        # Simulate a conversation
        conversation = [
            ("Alice", "Hey, are you there?"),
            ("Bob", "Yes, I'm here!"),
            ("Alice", "Great, let's discuss the project."),
            ("Bob", "Sure, what's on your mind?"),
            ("Alice", "I think we should refactor the code."),
            ("Bob", "Agreed, let's do it tomorrow."),
        ]

        for sender, content in conversation:
            session.add_message(sender, content)

        # Summarize
        summary_hash = session.summarize(keep_originals=False)

        # Verify summary
        card = collection.get(summary_hash)
        summary = json.loads(card.get_content().decode("utf-8"))

        assert summary["type"] == "p2p_session_summary"
        assert len(summary["fullTranscript"]) == 6

        # Verify message order and content
        for i, (sender, content) in enumerate(conversation):
            assert summary["fullTranscript"][i]["sender"] == sender
            assert summary["fullTranscript"][i]["content"] == content

    def test_multiple_sessions_independent(self, collection):
        """Multiple sessions should be independent."""
        session1 = P2PChatSession(collection, "session-1", max_buffer_size=5)
        session2 = P2PChatSession(collection, "session-2", max_buffer_size=5)

        session1.add_message("Alice", "In session 1")
        session2.add_message("Bob", "In session 2")

        hash1 = session1.checkpoint()
        hash2 = session2.checkpoint()

        # Different hashes
        assert hash1 != hash2

        # Summarize each
        summary1 = session1.summarize(keep_originals=True)
        summary2 = session2.summarize(keep_originals=True)

        # Verify content is independent
        card1 = collection.get(summary1)
        card2 = collection.get(summary2)

        content1 = json.loads(card1.get_content().decode("utf-8"))
        content2 = json.loads(card2.get_content().decode("utf-8"))

        assert content1["sessionId"] == "session-1"
        assert content2["sessionId"] == "session-2"
        assert content1["fullTranscript"][0]["sender"] == "Alice"
        assert content2["fullTranscript"][0]["sender"] == "Bob"


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
