"""
Tests for pre-designed guide cache module.
"""

import os
import tempfile
import pytest
import pandas as pd

from crisprdesigner2 import (
    load_predesigned_guides, filter_predesigned_regions,
    merge_with_predesigned, get_covered_guide_sets, count_guides_per_region,
)


class TestLoadPredesignedGuides:
    """Tests for the load_predesigned_guides function."""

    def test_none_path_returns_empty(self):
        """None path should return empty DataFrame."""
        result = load_predesigned_guides(None)
        assert isinstance(result, pd.DataFrame)
        assert result.empty

    def test_empty_string_returns_empty(self):
        """Empty string path should return empty DataFrame."""
        result = load_predesigned_guides('')
        assert isinstance(result, pd.DataFrame)
        assert result.empty

    def test_nonexistent_file_returns_empty(self):
        """Non-existent file should return empty DataFrame."""
        result = load_predesigned_guides('/nonexistent/path/to/file.txt')
        assert isinstance(result, pd.DataFrame)
        assert result.empty

    def test_valid_file_loaded(self):
        """Valid file should be loaded correctly."""
        # Create a temporary file
        df = pd.DataFrame({
            'chr': ['chr1', 'chr2'],
            'start': [100, 200],
            'end': [120, 220],
            'guideSet': ['region1', 'region2'],
            'RS3_score': [0.85, 0.72]
        })

        with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
            df.to_csv(f, sep='\t', index=False)
            file_path = f.name

        try:
            result = load_predesigned_guides(file_path)
            assert len(result) == 2
            assert 'guideSet' in result.columns
            assert result['guideSet'].iloc[0] == 'region1'
        finally:
            os.unlink(file_path)


class TestFilterPredesignedRegions:
    """Tests for the filter_predesigned_regions function."""

    @pytest.fixture
    def sample_regions_df(self):
        """Create a sample regions DataFrame."""
        return pd.DataFrame({
            'chr': ['chr1', 'chr1', 'chr2'],
            'start': [100, 500, 100],
            'end': [200, 600, 200],
            'name': ['region1', 'region2', 'region3']
        })

    @pytest.fixture
    def predesigned_df(self):
        """Create a sample pre-designed guides DataFrame."""
        return pd.DataFrame({
            'chr': ['chr1', 'chr1'],
            'start': [110, 120],
            'end': [130, 140],
            'guideSet': ['region1', 'region1'],
            'RS3_score': [0.85, 0.82]
        })

    def test_filters_covered_regions(self, sample_regions_df, predesigned_df):
        """Regions with pre-designed guides should be filtered out."""
        result = filter_predesigned_regions(sample_regions_df, predesigned_df)

        # region1 is covered, region2 and region3 are not
        assert len(result) == 2
        assert 'region1' not in result['name'].values
        assert 'region2' in result['name'].values
        assert 'region3' in result['name'].values

    def test_retains_uncovered_regions(self, sample_regions_df, predesigned_df):
        """Regions without pre-designed guides should be retained."""
        result = filter_predesigned_regions(sample_regions_df, predesigned_df)

        assert len(result) == 2
        assert 'region2' in result['name'].values
        assert 'region3' in result['name'].values

    def test_empty_predesigned_returns_all(self, sample_regions_df):
        """Empty pre-designed DataFrame should return all regions."""
        result = filter_predesigned_regions(sample_regions_df, pd.DataFrame())

        assert len(result) == len(sample_regions_df)

    def test_empty_regions_returns_empty(self, predesigned_df):
        """Empty regions DataFrame should return empty."""
        result = filter_predesigned_regions(pd.DataFrame(), predesigned_df)

        assert result.empty

    def test_all_covered_returns_empty(self):
        """If all regions are covered, return empty DataFrame."""
        regions_df = pd.DataFrame({
            'chr': ['chr1'],
            'start': [100],
            'end': [200],
            'name': ['region1']
        })

        predesigned_df = pd.DataFrame({
            'chr': ['chr1'],
            'start': [110],
            'end': [130],
            'guideSet': ['region1'],
            'RS3_score': [0.85]
        })

        result = filter_predesigned_regions(regions_df, predesigned_df)
        assert result.empty


class TestMergeWithPredesigned:
    """Tests for the merge_with_predesigned function."""

    @pytest.fixture
    def new_guides_df(self):
        """Create a sample new guides DataFrame."""
        return pd.DataFrame({
            'chr': ['chr1', 'chr1'],
            'start': [100, 150],
            'end': [120, 170],
            'strand': ['+', '-'],
            'guideSet': ['region2', 'region2'],
            'RS3_score': [0.90, 0.88]
        })

    @pytest.fixture
    def predesigned_df(self):
        """Create a sample pre-designed guides DataFrame."""
        return pd.DataFrame({
            'chr': ['chr1', 'chr1'],
            'start': [100, 200],  # First one overlaps with new guide
            'end': [120, 220],
            'strand': ['+', '+'],
            'guideSet': ['region1', 'region1'],
            'RS3_score': [0.85, 0.82]
        })

    def test_merges_guides(self, new_guides_df, predesigned_df):
        """Should merge new and pre-designed guides."""
        result = merge_with_predesigned(new_guides_df, predesigned_df)

        # Should have 3 guides (2 new + 2 predesigned - 1 duplicate)
        assert len(result) == 3

    def test_deduplicates_correctly(self, new_guides_df, predesigned_df):
        """Duplicate guides should be removed."""
        result = merge_with_predesigned(new_guides_df, predesigned_df)

        # Check that the duplicate at chr1:100-120:+ appears only once
        duplicates = result[
            (result['chr'] == 'chr1') &
            (result['start'] == 100) &
            (result['end'] == 120) &
            (result['strand'] == '+')
        ]
        assert len(duplicates) == 1

        # The kept duplicate should be from new guides (RS3_score = 0.90)
        assert duplicates['RS3_score'].iloc[0] == 0.90

    def test_empty_predesigned_returns_new(self, new_guides_df):
        """Empty pre-designed should return only new guides."""
        result = merge_with_predesigned(new_guides_df, pd.DataFrame())

        assert len(result) == len(new_guides_df)

    def test_empty_new_returns_predesigned(self, predesigned_df):
        """Empty new guides should return only pre-designed."""
        result = merge_with_predesigned(pd.DataFrame(), predesigned_df)

        assert len(result) == len(predesigned_df)

    def test_both_empty_returns_empty(self):
        """Both empty should return empty DataFrame."""
        result = merge_with_predesigned(pd.DataFrame(), pd.DataFrame())

        assert result.empty


class TestGetCoveredGuideSets:
    """Tests for the get_covered_guide_sets function."""

    def test_returns_set_of_guidesets(self):
        """Should return set of guideSet values."""
        df = pd.DataFrame({
            'chr': ['chr1', 'chr1', 'chr2'],
            'guideSet': ['region1', 'region1', 'region2']
        })

        result = get_covered_guide_sets(df)

        assert result == {'region1', 'region2'}

    def test_empty_dataframe_returns_empty_set(self):
        """Empty DataFrame should return empty set."""
        result = get_covered_guide_sets(pd.DataFrame())
        assert result == set()

    def test_missing_guideset_column_returns_empty_set(self):
        """DataFrame without guideSet column should return empty set."""
        df = pd.DataFrame({'chr': ['chr1'], 'start': [100]})
        result = get_covered_guide_sets(df)
        assert result == set()


class TestCountGuidesPerRegion:
    """Tests for the count_guides_per_region function."""

    def test_counts_correctly(self):
        """Should correctly count guides per region."""
        df = pd.DataFrame({
            'chr': ['chr1', 'chr1', 'chr1', 'chr2'],
            'guideSet': ['region1', 'region1', 'region2', 'region2']
        })

        result = count_guides_per_region(df)

        assert result == {'region1': 2, 'region2': 2}

    def test_empty_dataframe_returns_empty_dict(self):
        """Empty DataFrame should return empty dict."""
        result = count_guides_per_region(pd.DataFrame())
        assert result == {}

    def test_missing_guideset_column_returns_empty_dict(self):
        """DataFrame without guideSet column should return empty dict."""
        df = pd.DataFrame({'chr': ['chr1'], 'start': [100]})
        result = count_guides_per_region(df)
        assert result == {}


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