"""
Comprehensive test for Non-Pleiotropic Gene Identification features.

Tests ALL requirements:
1. Multi-database integration: GO.org, UniProt, KEGG simultaneously
2. Pleiotropy scoring: Exponential decay based on BP GO terms
3. Evidence-based filtering: IDA, IMP, IGI = experimental; IEA = prediction
4. Cross-species validation: human, mouse, fly, worm
5. Customizable thresholds: 0-10 other GO terms
6. Weighted ranking: specificity, evidence, literature, conservation
7. Specificity score: 0-1 scale (NOT 0-10)
"""

import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))

import unittest
import math


class TestPleiotropyScoring(unittest.TestCase):
    """Test the pleiotropy scoring algorithm."""
    
    def test_exponential_decay_formula(self):
        """Test that pleiotropy score uses exponential decay."""
        from k_sites.gene_analysis.pleiotropy_scorer import calculate_pleiotropy_score
        
        # 1 BP term = 0 pleiotropy (highly specific)
        self.assertEqual(calculate_pleiotropy_score(1), 0.0)
        
        # More BP terms = higher pleiotropy
        score_2 = calculate_pleiotropy_score(2)
        score_5 = calculate_pleiotropy_score(5)
        score_10 = calculate_pleiotropy_score(10)
        
        self.assertGreater(score_5, score_2)
        self.assertGreater(score_10, score_5)
        
        # 10+ BP terms approaches maximum (10)
        self.assertGreater(score_10, 9.0)
        self.assertLessEqual(score_10, 10.0)
    
    def test_pleiotropy_scale_0_to_10(self):
        """Test that pleiotropy score is 0-10 scale."""
        from k_sites.gene_analysis.pleiotropy_scorer import calculate_pleiotropy_score
        
        for bp_count in range(1, 20):
            score = calculate_pleiotropy_score(bp_count)
            self.assertGreaterEqual(score, 0.0)
            self.assertLessEqual(score, 10.0)
    
    def test_specificity_scale_0_to_1(self):
        """CRITICAL: Test that specificity score is 0-1 scale (NOT 0-10)."""
        from k_sites.gene_analysis.pleiotropy_scorer import calculate_specificity_score
        
        # High pleiotropy (10) = low specificity (0)
        self.assertEqual(calculate_specificity_score(10.0), 0.0)
        
        # Low pleiotropy (0) = high specificity (1)
        self.assertEqual(calculate_specificity_score(0.0), 1.0)
        
        # Medium values
        for pleiotropy in [0, 2, 5, 7, 10]:
            specificity = calculate_specificity_score(pleiotropy)
            self.assertGreaterEqual(specificity, 0.0)
            self.assertLessEqual(specificity, 1.0)


class TestEvidenceClassification(unittest.TestCase):
    """Test evidence-based filtering."""
    
    def test_experimental_evidence_codes(self):
        """Test that IDA, IMP, IGI are classified as experimental."""
        from k_sites.data_retrieval.multi_database_client import MultiDatabaseClient
        
        client = MultiDatabaseClient()
        
        # These MUST be experimental
        experimental_codes = ["IDA", "IMP", "IGI", "IPI", "IEP"]
        for code in experimental_codes:
            result = client._classify_evidence([code])
            self.assertEqual(result, "experimental", 
                           f"{code} should be classified as experimental")
    
    def test_iea_is_not_experimental(self):
        """CRITICAL: IEA must be classified as computational prediction, NOT experimental."""
        from k_sites.data_retrieval.multi_database_client import MultiDatabaseClient
        
        client = MultiDatabaseClient()
        
        result = client._classify_evidence(["IEA"])
        self.assertEqual(result, "IEA", 
                        "IEA should be classified as 'IEA' (prediction), NOT experimental")
        self.assertNotEqual(result, "experimental",
                          "IEA must NOT be classified as experimental")


class TestMultiDatabaseIntegration(unittest.TestCase):
    """Test multi-database integration (GO.org, UniProt, KEGG)."""
    
    def test_multi_database_client_structure(self):
        """Test that multi-database client is properly structured."""
        from k_sites.data_retrieval.multi_database_client import MultiDatabaseClient
        
        client = MultiDatabaseClient()
        
        # Verify all required databases are configured
        self.assertTrue(hasattr(client, '_query_quickgo'))  # GO.org
        self.assertTrue(hasattr(client, '_query_uniprot'))  # UniProt
        self.assertTrue(hasattr(client, '_query_kegg'))     # KEGG
    
    def test_simultaneous_query_function_exists(self):
        """Test that simultaneous query function exists."""
        from k_sites.data_retrieval.multi_database_client import query_gene_from_all_databases
        
        # Function should be callable
        self.assertTrue(callable(query_gene_from_all_databases))


class TestCrossSpeciesValidation(unittest.TestCase):
    """Test cross-species validation across model organisms."""
    
    def test_model_organisms_defined(self):
        """Test that model organisms (human, mouse, fly, worm) are defined."""
        from k_sites.data_retrieval.multi_database_client import MultiDatabaseClient
        
        client = MultiDatabaseClient()
        
        required_organisms = {
            "9606": "Homo sapiens",      # Human
            "10090": "Mus musculus",     # Mouse
            "7227": "Drosophila melanogaster",  # Fly
            "6239": "Caenorhabditis elegans",   # Worm
        }
        
        for taxid, name in required_organisms.items():
            self.assertIn(taxid, client.MODEL_ORGANISMS)
            self.assertEqual(client.MODEL_ORGANISMS[taxid], name)
    
    def test_cross_species_validation_function(self):
        """Test that cross-species validation function exists."""
        from k_sites.gene_analysis.pleiotropy_scorer import validate_across_species
        
        self.assertTrue(callable(validate_across_species))


class TestCustomizableThresholds(unittest.TestCase):
    """Test customizable pleiotropy thresholds (0-10 other GO terms)."""
    
    def test_threshold_in_ranking(self):
        """Test that threshold parameter is used in ranking."""
        from k_sites.gene_analysis.pleiotropy_scorer import rank_genes_by_specificity
        
        # Function should accept max_pleiotropy_threshold parameter
        import inspect
        sig = inspect.signature(rank_genes_by_specificity)
        params = sig.parameters
        
        self.assertIn('max_pleiotropy_threshold', params)


class TestWeightedRanking(unittest.TestCase):
    """Test weighted ranking combining all factors."""
    
    def test_ranking_includes_all_factors(self):
        """Test that ranking includes specificity, evidence, literature, conservation."""
        from k_sites.gene_analysis.pleiotropy_scorer import rank_genes_by_specificity
        
        # Function should accept parameters for all factors
        import inspect
        sig = inspect.signature(rank_genes_by_specificity)
        params = sig.parameters
        
        self.assertIn('evidence_filter', params)
        self.assertIn('include_literature', params)
        self.assertIn('include_cross_species', params)
    
    def test_literature_support_is_real(self):
        """Test that literature support queries PubMed (not a stub)."""
        from k_sites.gene_analysis.pleiotropy_scorer import get_literature_support
        
        # Function should return actual data structure
        import inspect
        sig = inspect.signature(get_literature_support)
        
        # Should return dict with pubmed_count and literature_score
        # (We test structure, not actual API call)
        self.assertTrue(callable(get_literature_support))


class TestReportGeneration(unittest.TestCase):
    """Test that reports show correct scales."""
    
    def test_html_report_shows_0_1_specificity(self):
        """Test that HTML report shows specificity on 0-1 scale."""
        # Read the report generator to check the scale
        import re
        
        report_file = Path(__file__).parent.parent / "k_sites" / "reporting" / "report_generator.py"
        content = report_file.read_text()
        
        # Should NOT have "/10" for specificity display
        # The specificity should show as 0-1 scale
        self.assertIn("specificity_percentage = specificity_score * 100", content,
                     "Specificity should be calculated as 0-1 * 100 for percentage")


if __name__ == "__main__":
    # Run all tests
    unittest.main(verbosity=2)
