"""
Tests for MLS group functionality.
"""

import pytest

from openmls_dave import CommitResult
from openmls_dave import MlsGroupWrapper as MlsGroup


def to_bytes(data):
    """Convert data to bytes if it's a list."""
    if isinstance(data, list):
        return bytes(data)
    return data


class TestMlsGroup:
    """Tests for MlsGroup class."""

    def test_create_group(self, client_alice, group_id):
        """Test creating a new group."""
        group = MlsGroup(client_alice, group_id)

        assert to_bytes(group.group_id) == group_id
        assert group.epoch == 0
        assert group.member_count == 1

    def test_group_members(self, alice_group, client_alice):
        """Test getting group members."""
        members = alice_group.members()

        assert len(members) == 1
        assert to_bytes(members[0]) == to_bytes(client_alice.identity)

    def test_add_member(self, alice_group, client_bob):
        """Test adding a member to the group."""
        key_package = to_bytes(client_bob.generate_key_package())
        result = alice_group.add_member(key_package)

        assert isinstance(result, CommitResult)
        assert result.commit_message is not None
        assert result.welcome_message is not None

        # Merge the commit
        alice_group.merge_pending_commit()

        assert alice_group.member_count == 2
        assert alice_group.epoch == 1

    def test_add_multiple_members(self, alice_group, client_bob, client_charlie):
        """Test adding multiple members at once."""
        key_packages = [
            to_bytes(client_bob.generate_key_package()),
            to_bytes(client_charlie.generate_key_package()),
        ]

        result = alice_group.add_members(key_packages)
        alice_group.merge_pending_commit()

        assert alice_group.member_count == 3
        assert alice_group.epoch == 1

    def test_join_from_welcome(self, alice_group, client_bob):
        """Test joining a group via welcome message."""
        key_package = to_bytes(client_bob.generate_key_package())
        result = alice_group.add_member(key_package)
        alice_group.merge_pending_commit()

        # Bob joins using the welcome
        welcome = to_bytes(result.welcome_message)
        bob_group = MlsGroup.from_welcome(client_bob, welcome)

        assert to_bytes(bob_group.group_id) == to_bytes(alice_group.group_id)
        assert bob_group.member_count == 2

    def test_remove_member(self, alice_group, client_bob):
        """Test removing a member from the group."""
        # First add Bob
        key_package = to_bytes(client_bob.generate_key_package())
        alice_group.add_member(key_package)
        alice_group.merge_pending_commit()

        assert alice_group.member_count == 2

        # Remove Bob
        result = alice_group.remove_member(to_bytes(client_bob.identity))
        alice_group.merge_pending_commit()

        assert alice_group.member_count == 1
        assert result.welcome_message is None  # No welcome for removals

    def test_self_update(self, alice_group):
        """Test self-update operation."""
        initial_epoch = alice_group.epoch

        result = alice_group.self_update()
        alice_group.merge_pending_commit()

        assert alice_group.epoch == initial_epoch + 1
        assert result.commit_message is not None

    def test_export_secret(self, alice_group):
        """Test exporting a secret from the group."""
        secret = to_bytes(
            alice_group.export_secret(
                label="test label",
                context=b"test context",
                length=32,
            )
        )

        assert isinstance(secret, bytes)
        assert len(secret) == 32

    def test_derive_media_key(self, alice_group):
        """Test deriving media key for DAVE."""
        media_key = to_bytes(alice_group.derive_media_key())

        assert isinstance(media_key, bytes)
        assert len(media_key) == 32

    def test_derive_sender_key(self, alice_group):
        """Test deriving sender-specific key."""
        sender_id = 12345
        sender_key = to_bytes(alice_group.derive_sender_key(sender_id))

        assert isinstance(sender_key, bytes)
        assert len(sender_key) == 32

        # Different sender IDs should produce different keys
        other_key = to_bytes(alice_group.derive_sender_key(67890))
        assert sender_key != other_key

    def test_epoch_authenticator(self, alice_group):
        """Test getting epoch authenticator."""
        authenticator = to_bytes(alice_group.get_epoch_authenticator())

        assert isinstance(authenticator, bytes)
        assert len(authenticator) > 0

    def test_process_commit_message(self, alice_group, client_bob):
        """Test processing a commit message."""
        # Add Bob and get the commit
        key_package = to_bytes(client_bob.generate_key_package())
        result = alice_group.add_member(key_package)

        # Bob joins
        welcome = to_bytes(result.welcome_message)
        bob_group = MlsGroup.from_welcome(client_bob, welcome)

        # Alice merges locally
        alice_group.merge_pending_commit()

        # Simulate Bob receiving a subsequent update from Alice
        update_result = alice_group.self_update()
        alice_group.merge_pending_commit()

        # Bob processes the commit
        commit_msg = to_bytes(update_result.commit_message)
        processed = bob_group.process_message(commit_msg)

        assert processed.message_type == "commit"
        assert bob_group.epoch == alice_group.epoch

    def test_export_ratchet_tree(self, alice_group):
        """Test exporting ratchet tree."""
        tree = to_bytes(alice_group.export_ratchet_tree())

        assert isinstance(tree, bytes)
        assert len(tree) > 0

    def test_group_repr(self, alice_group):
        """Test group string representation."""
        repr_str = repr(alice_group)

        assert "MlsGroup" in repr_str
        assert "epoch=" in repr_str
        assert "members=" in repr_str


class TestGroupScenarios:
    """Test realistic group scenarios."""

    def test_three_party_call(self, client_alice, client_bob, client_charlie, group_id):
        """Test a three-party call scenario."""
        # Alice creates the group
        alice_group = MlsGroup(client_alice, group_id)

        # Add Bob
        bob_kp = to_bytes(client_bob.generate_key_package())
        result = alice_group.add_member(bob_kp)
        alice_group.merge_pending_commit()

        welcome = to_bytes(result.welcome_message)
        bob_group = MlsGroup.from_welcome(client_bob, welcome)

        # Add Charlie (from Alice)
        charlie_kp = to_bytes(client_charlie.generate_key_package())
        result = alice_group.add_member(charlie_kp)
        alice_group.merge_pending_commit()

        # Bob processes the commit
        commit_msg = to_bytes(result.commit_message)
        bob_group.process_message(commit_msg)

        # Charlie joins
        welcome = to_bytes(result.welcome_message)
        charlie_group = MlsGroup.from_welcome(client_charlie, welcome)

        # Verify all groups are in sync
        assert alice_group.epoch == bob_group.epoch == charlie_group.epoch
        assert alice_group.member_count == bob_group.member_count == charlie_group.member_count == 3

    def test_member_leaves(self, client_alice, client_bob, client_charlie, group_id):
        """Test member leaving scenario."""
        # Setup three-party call
        alice_group = MlsGroup(client_alice, group_id)

        bob_kp = to_bytes(client_bob.generate_key_package())
        charlie_kp = to_bytes(client_charlie.generate_key_package())

        result = alice_group.add_members([bob_kp, charlie_kp])
        alice_group.merge_pending_commit()

        welcome = to_bytes(result.welcome_message)
        bob_group = MlsGroup.from_welcome(client_bob, welcome)
        charlie_group = MlsGroup.from_welcome(client_charlie, welcome)

        # Bob leaves (Alice removes him)
        remove_result = alice_group.remove_member(to_bytes(client_bob.identity))
        alice_group.merge_pending_commit()

        # Charlie processes the removal
        commit_msg = to_bytes(remove_result.commit_message)
        charlie_group.process_message(commit_msg)

        assert alice_group.member_count == 2
        assert charlie_group.member_count == 2

    def test_epoch_changes_key(self, alice_group, client_bob):
        """Test that epoch changes produce different keys."""
        key1 = to_bytes(alice_group.derive_media_key())

        # Add Bob (causes epoch change)
        key_package = to_bytes(client_bob.generate_key_package())
        alice_group.add_member(key_package)
        alice_group.merge_pending_commit()

        key2 = to_bytes(alice_group.derive_media_key())

        assert key1 != key2
