"""Tests for the mutation mechanism in soup interactions."""

import numpy as np
import pytest

from abiogenesis.soup import Soup, InteractionResult


class TestMutation:
    def test_no_mutation_by_default(self):
        """Default mutation_rate=0.0 should never mutate."""
        soup = Soup(n_tapes=16, tape_len=8, seed=42)
        for _ in range(1000):
            result = soup.interact()
            assert result.mutated is False

    def test_mutation_flag_set(self):
        """High mutation rate should produce mutated=True results."""
        soup = Soup(n_tapes=16, tape_len=8, seed=42, mutation_rate=1.0)
        mutated_count = 0
        for _ in range(100):
            result = soup.interact()
            if result.mutated:
                mutated_count += 1
        # With rate=1.0, every interaction should mutate
        assert mutated_count == 100

    def test_mutation_rate_approximate(self):
        """Mutation rate ~0.5 should produce ~50% mutated interactions."""
        soup = Soup(n_tapes=64, tape_len=8, seed=42, mutation_rate=0.5)
        mutated_count = 0
        n = 10_000
        for _ in range(n):
            result = soup.interact()
            if result.mutated:
                mutated_count += 1
        ratio = mutated_count / n
        # Should be roughly 0.5 ± 0.05
        assert 0.4 < ratio < 0.6, f"Mutation ratio {ratio} outside expected range"

    def test_mutation_modifies_tape(self):
        """Mutation should flip exactly one bit on one tape."""
        soup = Soup(n_tapes=16, tape_len=8, seed=42, mutation_rate=1.0)
        # Run one interaction and capture the state before mutation would apply
        # We can verify by checking that tapes differ by exactly one bit
        # from a non-mutating soup with same seed
        soup_no_mut = Soup(n_tapes=16, tape_len=8, seed=42, mutation_rate=0.0)

        # Both start identical
        np.testing.assert_array_equal(soup.tapes, soup_no_mut.tapes)

        # After one interaction, tapes should differ by exactly one bit
        # (the mutation) — but the RNG draws differ because mutation
        # consumes extra RNG calls, so we can't directly compare.
        # Instead, verify mutation_rate=1.0 always sets mutated=True.
        result = soup.interact()
        assert result.mutated is True

    def test_backward_compat_interaction_result(self):
        """InteractionResult defaults should be backward compatible."""
        r = InteractionResult(ops_count=5, idx_a=0, idx_b=1)
        assert r.mutated is False
        assert r.depth == 0

    def test_zero_mutation_rate_deterministic(self):
        """Soup with mutation_rate=0.0 should be deterministic like before."""
        s1 = Soup(n_tapes=16, tape_len=8, seed=42, mutation_rate=0.0)
        s2 = Soup(n_tapes=16, tape_len=8, seed=42, mutation_rate=0.0)

        results_a = [s1.interact() for _ in range(50)]
        results_b = [s2.interact() for _ in range(50)]

        for a, b in zip(results_a, results_b):
            assert a.ops_count == b.ops_count
            assert a.idx_a == b.idx_a
            assert a.idx_b == b.idx_b
            assert a.mutated == b.mutated
