"""
Tests for sequence filter module.

These tests match the original filter.py implementation.
"""

import pytest
import pandas as pd

from crisprdesigner2 import is_valid_guide, apply_filters, get_passing_guides
from crisprdesigner2.filters import (
    _pol3_termination, _high_tu_content, _tu_homopolymer,
    _mononucleotide_repeat, _low_complexity, _low_gc, _high_gc,
    _is_low_complexity,
)


class TestIsLowComplexity:
    """Tests for the _is_low_complexity regex function."""

    def test_2nt_repeat_5_times(self):
        """10 nucleotides of 2-nt repeat should fail."""
        assert _is_low_complexity('ATATATATAT', 2, 5) is True
        assert _is_low_complexity('GCGCGCGCGC', 2, 5) is True

    def test_2nt_repeat_4_times_passes(self):
        """8 nucleotides of 2-nt repeat should pass."""
        assert _is_low_complexity('ATATATAT', 2, 5) is False

    def test_3nt_repeat_4_times(self):
        """12 nucleotides of 3-nt repeat should fail."""
        assert _is_low_complexity('ATGATGATGATG', 3, 4) is True

    def test_4nt_repeat_3_times(self):
        """12 nucleotides of 4-nt repeat should fail."""
        assert _is_low_complexity('ACGTACGTACGT', 4, 3) is True


class TestPol3Termination:
    """Tests for Pol III termination filter (>1 T in last 4 nt)."""

    def test_failing_sequence(self):
        """Sequence with 4 T's in last 4 positions should fail."""
        assert _pol3_termination('ACGTACGTACGTACGTTTTT') is True

    def test_passing_sequence(self):
        """Sequence with 1 T in last 4 positions should pass."""
        assert _pol3_termination('ACGTACGTACGTACGTACGA') is False

    def test_exactly_two_t(self):
        """Sequence with exactly 2 T's in last 4 should fail."""
        assert _pol3_termination('ACGTACGTACGTACGTACTT') is True

    def test_exactly_one_t(self):
        """Sequence with exactly 1 T in last 4 should pass."""
        assert _pol3_termination('ACGTACGTACGTACGTACCG') is False

    def test_u_replacement(self):
        """U should be treated as T."""
        assert _pol3_termination('ACGTACGTACGTACGTUUUU') is True


class TestHighTUContent:
    """Tests for high T/U content filter (>40% T)."""

    def test_failing_sequence(self):
        """Sequence with 80% T should fail."""
        assert _high_tu_content('TTTTTTTTTTTTTTTTACGA') is True

    def test_passing_sequence(self):
        """Sequence with 25% T should pass."""
        # Sequence with exactly 8 T's out of 20 = 40%, should pass
        assert _high_tu_content('TTTTTTTTAAAAAAAAAAAA') is False

    def test_exactly_40_percent(self):
        """Sequence with exactly 40% T should pass (>40% fails)."""
        # 8 T's out of 20 = 40%
        seq = 'TTTTTTTTACACACACACAC'
        assert _high_tu_content(seq) is False

    def test_just_over_40_percent(self):
        """Sequence with 45% T should fail."""
        # 9 T's out of 20 = 45%
        seq = 'TTTTTTTTTACACACACACA'
        assert _high_tu_content(seq) is True


class TestTUHomopolymer:
    """Tests for T/U homopolymer filter (4+ consecutive T's)."""

    def test_failing_sequence(self):
        """Sequence with 5 consecutive T's should fail."""
        assert _tu_homopolymer('ACGTACGTTTTTACGTACGA') is True

    def test_passing_sequence(self):
        """Sequence with no 4+ consecutive T's should pass."""
        assert _tu_homopolymer('ACGTTACGTTACGTTACGTA') is False

    def test_exactly_four_t(self):
        """Sequence with exactly 4 consecutive T's should fail."""
        assert _tu_homopolymer('ACGTACGTTTTCACGTACGA') is True

    def test_three_t_passes(self):
        """Sequence with 3 consecutive T's should pass."""
        assert _tu_homopolymer('ACGTACGTTTACGTACGTAA') is False


class TestMononucleotideRepeat:
    """Tests for mononucleotide repeat filter (5+ consecutive same base)."""

    def test_failing_sequence_a(self):
        """Sequence with 5 consecutive A's should fail."""
        assert _mononucleotide_repeat('ACGTAAAAACGTACGTACGA') is True

    def test_passing_sequence(self):
        """Sequence with no 5+ consecutive same bases should pass."""
        assert _mononucleotide_repeat('ACGTAAAACGTACGTACGAA') is False

    def test_failing_sequence_g(self):
        """Sequence with 5 consecutive G's should fail."""
        assert _mononucleotide_repeat('ACGTGGGGGCGTACGTACGA') is True

    def test_failing_sequence_c(self):
        """Sequence with 5 consecutive C's should fail."""
        assert _mononucleotide_repeat('ACGTCCCCCGTACGTACGAA') is True

    def test_four_consecutive_passes(self):
        """Sequence with 4 consecutive same bases should pass."""
        assert _mononucleotide_repeat('ACGTAAAACGTACGTACGAA') is False


class TestLowComplexity:
    """Tests for low-complexity filter using regex backreferences."""

    def test_failing_2nt_repeat(self):
        """10+ nucleotides of 2-nt repeat should fail."""
        assert _low_complexity('ATATATATATATATATACGA') is True
        assert _low_complexity('GCGCGCGCGCACGTACGTAA') is True

    def test_failing_3nt_repeat(self):
        """12+ nucleotides of 3-nt repeat should fail."""
        assert _low_complexity('ATGATGATGATGATGATGAA') is True

    def test_failing_4nt_repeat(self):
        """12+ nucleotides of 4-nt repeat should fail."""
        # ACGT repeated 3 times = 12 nt
        assert _low_complexity('ACGTACGTACGTACGTACGA') is True

    def test_failing_5nt_repeat(self):
        """15+ nucleotides of 5-nt repeat should fail."""
        # ACGTA repeated 3 times = 15 nt
        assert _low_complexity('ACGTAACGTAACGTAACGTA') is True

    def test_failing_6nt_repeat(self):
        """18+ nucleotides of 6-nt repeat should fail."""
        # ACGTAC repeated 3 times = 18 nt
        assert _low_complexity('ACGTACACGTACACGTACAA') is True

    def test_passing_sequence(self):
        """Non-repetitive sequence should pass."""
        assert _low_complexity('ACGTACGATCGATCGACGAA') is False

    def test_short_repeat_passes(self):
        """Short repeat that doesn't meet threshold should pass."""
        # Only 8 nt of AT repeat (need 10)
        assert _low_complexity('ATATATATACGTACGACGAA') is False


class TestLowGC:
    """Tests for low GC content filter (<=20% GC)."""

    def test_failing_sequence(self):
        """Sequence with 0% GC should fail."""
        assert _low_gc('ATATATATATATATATATA') is True

    def test_passing_sequence(self):
        """Sequence with 50% GC should pass."""
        # Need to avoid low_complexity, so use non-repetitive
        assert _low_gc('ACGTACGATCGATCGACGAA') is False

    def test_exactly_20_percent(self):
        """Sequence with exactly 20% GC should fail (<=20% fails)."""
        # 4 G/C out of 20 = 20%
        seq = 'GCGCAAAAAAAAAAAAAAAA'
        assert _low_gc(seq) is True

    def test_just_over_20_percent(self):
        """Sequence with 25% GC should pass."""
        # 5 G/C out of 20 = 25%
        seq = 'GCGCGAAAAAAAAAAAAAAA'
        assert _low_gc(seq) is False


class TestHighGC:
    """Tests for high GC content filter (>=90% GC)."""

    def test_failing_sequence(self):
        """Sequence with 100% GC should fail."""
        assert _high_gc('GCGCGCGCGCGCGCGCGCGC') is True

    def test_passing_sequence(self):
        """Sequence with 50% GC should pass."""
        assert _high_gc('ACGTACGATCGATCGACGAA') is False

    def test_exactly_90_percent(self):
        """Sequence with exactly 90% GC should fail (>=90% fails)."""
        # 18 G/C out of 20 = 90%
        seq = 'GCGCGCGCGCGCGCGCGCAA'
        assert _high_gc(seq) is True

    def test_just_under_90_percent(self):
        """Sequence with 85% GC should pass."""
        # 17 G/C out of 20 = 85%
        seq = 'GCGCGCGCGCGCGCGCGAAA'
        assert _high_gc(seq) is False


class TestIsValidGuide:
    """Tests for the main is_valid_guide function."""

    def test_valid_guide(self):
        """A good guide should return True."""
        # Non-repetitive, balanced GC, no termination signals
        assert is_valid_guide('ACGTACGATCGATCGACGAA') is True

    def test_invalid_pol3_termination(self):
        """Guide with >1 T in last 4 should return False."""
        assert is_valid_guide('ACGTCGATCGATCGATCTTT') is False

    def test_invalid_high_tu(self):
        """Guide with >40% T should return False."""
        assert is_valid_guide('TTTTTTTTTTCGATCGATCA') is False

    def test_invalid_tu_homopolymer(self):
        """Guide with TTTT should return False."""
        assert is_valid_guide('ACGTCGATTTTTCGATCGAA') is False

    def test_invalid_mononucleotide(self):
        """Guide with 5+ same base should return False."""
        assert is_valid_guide('ACGTCGAAAAACGATCGAAC') is False

    def test_invalid_low_complexity(self):
        """Guide with low-complexity pattern should return False."""
        assert is_valid_guide('ATATATATATATCGATCGAA') is False

    def test_invalid_low_gc(self):
        """Guide with <=20% GC should return False."""
        assert is_valid_guide('AATAATAATAATAATAATAA') is False

    def test_invalid_high_gc(self):
        """Guide with >=90% GC should return False."""
        assert is_valid_guide('GCGCGCGCGCGCGCGCGCGC') is False


class TestApplyFilters:
    """Integration tests for apply_filters function."""

    def test_passing_guide(self):
        """A good guide should pass all filters."""
        df = pd.DataFrame({
            'protospacer': ['ACGTACGATCGATCGACGAA']
        })
        result = apply_filters(df)
        assert result['filter_reason'].iloc[0] == ''

    def test_failing_guide_pol3(self):
        """Guide with pol3 termination signal should be marked."""
        df = pd.DataFrame({
            'protospacer': ['ACGTACGATCGATCGACTTT']
        })
        result = apply_filters(df)
        assert result['filter_reason'].iloc[0] == 'pol3_termination'

    def test_multiple_guides(self):
        """Test with multiple guides, some passing and some failing."""
        df = pd.DataFrame({
            'protospacer': [
                'ACGTACGATCGATCGACGAA',  # Should pass
                'ACGTACGATCGATCGACTTT',  # Fails pol3_termination
                'ATATATATATATACGACGAA',  # Fails low_complexity
            ]
        })
        result = apply_filters(df)

        assert result['filter_reason'].iloc[0] == ''
        assert result['filter_reason'].iloc[1] == 'pol3_termination'
        assert result['filter_reason'].iloc[2] == 'low_complexity'

    def test_preserves_other_columns(self):
        """apply_filters should preserve other columns in the DataFrame."""
        df = pd.DataFrame({
            'protospacer': ['ACGTACGATCGATCGACGAA'],
            'chr': ['chr1'],
            'start': [100],
            'end': [120],
            'strand': ['+']
        })
        result = apply_filters(df)

        assert 'chr' in result.columns
        assert 'start' in result.columns
        assert result['chr'].iloc[0] == 'chr1'

    def test_get_passing_guides(self):
        """Test get_passing_guides returns only passing guides."""
        df = pd.DataFrame({
            'protospacer': [
                'ACGTACGATCGATCGACGAA',  # Should pass
                'ACGTACGATCGATCGACTTT',  # Fails pol3
                'ACGATCGACGATCGACGACA',  # Should pass
            ]
        })
        result = get_passing_guides(df)

        assert len(result) == 2
        assert 'filter_reason' not in result.columns
        assert result['protospacer'].iloc[0] == 'ACGTACGATCGATCGACGAA'
        assert result['protospacer'].iloc[1] == 'ACGATCGACGATCGACGACA'

    def test_all_filters_can_be_triggered(self):
        """Test that each filter type can be triggered."""
        # Each sequence is designed to trigger ONLY one specific filter
        # Filter order: pol3_termination, high_tu_content, tu_homopolymer,
        #               mononucleotide_repeat, low_complexity, low_gc, high_gc
        df = pd.DataFrame({
            'protospacer': [
                'ACGTCGATCGATCGATCTTT',   # pol3_termination (3 T's in last 4)
                'TTTTTTTTTTTACGTCGACA',   # high_tu_content (55% T, 1 T in last 4)
                'ACGTCGATTTTTCGATCGAA',   # tu_homopolymer (5 consecutive T's)
                'ACGTCGAAAAACGATCGAAC',   # mononucleotide_repeat (5 A's)
                'ACACACACACACGTCGATCA',   # low_complexity (12 nt of AC repeat)
                'AATAATAATGATAAATAACA',   # low_gc (10% GC), avoids repeats
                'GCGCGCGCAGCGCGCGCAGC',   # high_gc (90% GC), avoids 10-nt repeat
            ]
        })
        result = apply_filters(df)

        assert result['filter_reason'].iloc[0] == 'pol3_termination'
        assert result['filter_reason'].iloc[1] == 'high_tu_content'
        assert result['filter_reason'].iloc[2] == 'tu_homopolymer'
        assert result['filter_reason'].iloc[3] == 'mononucleotide_repeat'
        assert result['filter_reason'].iloc[4] == 'low_complexity'
        assert result['filter_reason'].iloc[5] == 'low_gc'
        assert result['filter_reason'].iloc[6] == 'high_gc'


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