"""
Integration tests for complete DAVE scenarios.
"""

import pytest

from openmls_dave import (
    DaveSession,
    SessionState,
)


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


class TestDaveSession:
    """Tests for high-level DaveSession class."""

    def test_create_session(self, session_alice):
        """Test creating a DAVE session."""
        assert session_alice.user_id == 12345
        assert session_alice.state == SessionState.INITIALIZED

    def test_get_key_package(self, session_alice):
        """Test getting key package from session."""
        kp = to_bytes(session_alice.get_key_package())

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

    def test_create_call(self, session_alice):
        """Test creating a call."""
        call_id = b"call_12345"
        session_alice.create_call(call_id)

        assert session_alice.state == SessionState.ACTIVE
        assert to_bytes(session_alice.group_id) == call_id
        assert session_alice.participant_count == 1

    def test_join_call(self, session_alice, session_bob):
        """Test joining a call."""
        call_id = b"call_12345"
        session_alice.create_call(call_id)

        # Alice adds Bob
        bob_kp = to_bytes(session_bob.get_key_package())
        result = session_alice.add_participant(bob_kp)

        # Bob joins
        welcome = to_bytes(result.welcome_message)
        session_bob.join_call(welcome)

        assert session_bob.state == SessionState.ACTIVE
        assert to_bytes(session_bob.group_id) == call_id

    def test_encrypt_decrypt_frame(self, session_alice, sample_audio_frame):
        """Test encrypting and decrypting a frame."""
        session_alice.create_call(b"call_12345")

        # Encrypt
        encrypted = session_alice.encrypt_frame(sample_audio_frame)
        encrypted_data = to_bytes(encrypted.data)
        assert encrypted_data != sample_audio_frame
        assert encrypted.key_id == session_alice.key_id

        # Decrypt own frame
        decrypted = session_alice.decrypt_frame(encrypted_data)
        assert to_bytes(decrypted.data) == sample_audio_frame

    def test_two_party_media_exchange(self, session_alice, session_bob, sample_audio_frame):
        """Test media exchange between two parties."""
        # Setup call
        session_alice.create_call(b"call_12345")
        bob_kp = to_bytes(session_bob.get_key_package())
        result = session_alice.add_participant(bob_kp)
        welcome = to_bytes(result.welcome_message)
        session_bob.join_call(welcome)

        # Register each other's keys
        alice_media_key = to_bytes(session_alice._group.derive_media_key())
        bob_media_key = to_bytes(session_bob._group.derive_media_key())

        session_alice.register_sender(session_bob.key_id, bob_media_key)
        session_bob.register_sender(session_alice.key_id, alice_media_key)

        # Alice sends to Bob
        alice_encrypted = session_alice.encrypt_frame(b"hello from alice")
        bob_decrypted = session_bob.decrypt_frame(to_bytes(alice_encrypted.data))
        assert to_bytes(bob_decrypted.data) == b"hello from alice"

        # Bob sends to Alice
        bob_encrypted = session_bob.encrypt_frame(b"hello from bob")
        alice_decrypted = session_alice.decrypt_frame(to_bytes(bob_encrypted.data))
        assert to_bytes(alice_decrypted.data) == b"hello from bob"

    def test_leave_call(self, session_alice):
        """Test leaving a call."""
        session_alice.create_call(b"call_12345")
        session_alice.leave_call()

        assert session_alice.state == SessionState.LEFT
        assert session_alice.group_id is None

    def test_get_epoch_authenticator(self, session_alice):
        """Test getting epoch authenticator."""
        session_alice.create_call(b"call_12345")

        auth = to_bytes(session_alice.get_epoch_authenticator())

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

    def test_process_message(self, session_alice, session_bob):
        """Test processing MLS messages."""
        session_alice.create_call(b"call_12345")
        bob_kp = to_bytes(session_bob.get_key_package())
        result = session_alice.add_participant(bob_kp)
        welcome = to_bytes(result.welcome_message)
        session_bob.join_call(welcome)

        # Alice does a self-update
        update_result = session_alice._group.self_update()
        session_alice._group.merge_pending_commit()

        # Bob processes the update
        commit_msg = to_bytes(update_result.commit_message)
        processed = session_bob.process_message(commit_msg)

        assert processed.message_type == "commit"
        assert session_bob.epoch == session_alice.epoch

    def test_session_repr(self, session_alice):
        """Test session string representation."""
        repr_str = repr(session_alice)

        assert "DaveSession" in repr_str
        assert "user_id=" in repr_str


class TestFullCallScenario:
    """Complete call scenario tests."""

    def test_three_party_call_with_media(
        self,
        session_alice,
        session_bob,
        session_charlie,
        sample_audio_frame,
    ):
        """Test a three-party call with media exchange."""
        # Alice creates the call
        session_alice.create_call(b"group_call_001")

        # Bob joins
        bob_kp = to_bytes(session_bob.get_key_package())
        result = session_alice.add_participant(bob_kp)
        welcome = to_bytes(result.welcome_message)
        session_bob.join_call(welcome)

        # Charlie joins
        charlie_kp = to_bytes(session_charlie.get_key_package())
        result = session_alice.add_participant(charlie_kp)

        # Bob processes Alice's commit
        commit_msg = to_bytes(result.commit_message)
        session_bob.process_message(commit_msg)

        # Charlie joins via welcome
        welcome = to_bytes(result.welcome_message)
        session_charlie.join_call(welcome)

        # Verify everyone is in sync
        assert session_alice.epoch == session_bob.epoch == session_charlie.epoch
        assert session_alice.participant_count == 3

        # Setup media keys
        media_key = to_bytes(session_alice._group.derive_media_key())

        for session in [session_alice, session_bob, session_charlie]:
            for other in [session_alice, session_bob, session_charlie]:
                if session != other:
                    session.register_sender(other.key_id, media_key)

        # Everyone can communicate
        msg_from_alice = session_alice.encrypt_frame(b"alice speaking")
        msg_from_bob = session_bob.encrypt_frame(b"bob speaking")
        msg_from_charlie = session_charlie.encrypt_frame(b"charlie speaking")

        # Bob receives from Alice and Charlie
        assert to_bytes(session_bob.decrypt_frame(to_bytes(msg_from_alice.data)).data) == b"alice speaking"
        assert to_bytes(session_bob.decrypt_frame(to_bytes(msg_from_charlie.data)).data) == b"charlie speaking"

        # Alice receives from Bob and Charlie
        assert to_bytes(session_alice.decrypt_frame(to_bytes(msg_from_bob.data)).data) == b"bob speaking"
        assert to_bytes(session_alice.decrypt_frame(to_bytes(msg_from_charlie.data)).data) == b"charlie speaking"

    def test_member_removal_invalidates_old_keys(
        self,
        session_alice,
        session_bob,
        sample_audio_frame,
    ):
        """Test that removing a member changes the encryption keys."""
        # Setup call
        session_alice.create_call(b"call_12345")
        bob_kp = to_bytes(session_bob.get_key_package())
        result = session_alice.add_participant(bob_kp)
        welcome = to_bytes(result.welcome_message)
        session_bob.join_call(welcome)

        # Get key before removal
        key_before = to_bytes(session_alice._group.derive_media_key())

        # Remove Bob
        session_alice.remove_participant(session_bob.user_id)

        # Get key after removal
        key_after = to_bytes(session_alice._group.derive_media_key())

        # Keys should be different (forward secrecy)
        assert key_before != key_after

    def test_epoch_authenticator_changes_on_membership_change(
        self,
        session_alice,
        session_bob,
    ):
        """Test that epoch authenticator changes with membership."""
        session_alice.create_call(b"call_12345")
        auth_before = to_bytes(session_alice.get_epoch_authenticator())

        # Add Bob
        bob_kp = to_bytes(session_bob.get_key_package())
        session_alice.add_participant(bob_kp)

        auth_after = to_bytes(session_alice.get_epoch_authenticator())

        assert auth_before != auth_after


class TestEdgeCases:
    """Edge case and error handling tests."""

    def test_encrypt_before_call_fails(self, session_alice, sample_audio_frame):
        """Test that encrypting before joining a call fails."""
        with pytest.raises(RuntimeError):
            session_alice.encrypt_frame(sample_audio_frame)

    def test_decrypt_before_call_fails(self, session_alice):
        """Test that decrypting before joining a call fails."""
        with pytest.raises(RuntimeError):
            session_alice.decrypt_frame(b"ciphertext")

    def test_add_participant_before_call_fails(self, session_alice, session_bob):
        """Test that adding participant before creating call fails."""
        bob_kp = to_bytes(session_bob.get_key_package())

        with pytest.raises(RuntimeError):
            session_alice.add_participant(bob_kp)

    def test_multiple_creates_resets_session(self, session_alice):
        """Test that creating a new call resets the session."""
        session_alice.create_call(b"call_1")
        epoch1 = session_alice.epoch

        session_alice.create_call(b"call_2")
        epoch2 = session_alice.epoch

        assert epoch2 == 0  # Reset
        assert to_bytes(session_alice.group_id) == b"call_2"

    def test_large_group(self):
        """Test with a larger group."""
        # Create host
        host = DaveSession(user_id=0)
        host.create_call(b"large_call")

        # Add multiple participants
        participants = []
        for i in range(1, 10):
            p = DaveSession(user_id=i)
            kp = to_bytes(p.get_key_package())
            result = host.add_participant(kp)
            welcome = to_bytes(result.welcome_message)
            p.join_call(welcome)
            participants.append(p)

        assert host.participant_count == 10
