"""
Test suite for offline URL construction capabilities in FindIt.

This ensures that FindIt can construct URLs without making HTTP requests
whenever possible, which is crucial for performance and avoiding rate limits.
"""

import unittest
import inspect
from unittest.mock import Mock, patch

from metapub import FindIt
from metapub.findit.handlers import RegistryBackedLookupSystem, PublisherHandler
from metapub.findit.registry import JournalRegistry


class TestOfflineURLConstruction(unittest.TestCase):
    """Test that URLs can be constructed without network requests when possible."""

    def setUp(self):
        """Set up test fixtures."""
        # Create a mock PubMedArticle with VIP data (volume-issue-page)
        self.mock_pma_vip = Mock()
        self.mock_pma_vip.journal = "Nature"
        self.mock_pma_vip.volume = "500" 
        self.mock_pma_vip.issue = "7460"
        self.mock_pma_vip.first_page = "123"
        self.mock_pma_vip.doi = "10.1038/nature12373"
        self.mock_pma_vip.pii = None
        self.mock_pma_vip.pmid = "23903748"

        # Create a mock PubMedArticle for BMC (open access, DOI-based)
        self.mock_pma_bmc = Mock()
        self.mock_pma_bmc.journal = "BMC Genomics"
        self.mock_pma_bmc.doi = "10.1186/s12864-023-09123-4"
        self.mock_pma_bmc.pii = None
        self.mock_pma_bmc.volume = "24"
        self.mock_pma_bmc.issue = "1"
        self.mock_pma_bmc.first_page = "123"
        self.mock_pma_bmc.pmid = "12345678"

    def test_vip_url_construction_offline(self):
        """Test that VIP URLs (volume-issue-page) can be constructed offline."""
        from metapub.findit.dances import the_vip_shake
        from metapub.findit.registry import JournalRegistry
        
        # Check if journal is in registry
        registry = JournalRegistry()
        try:
            publisher_info = registry.get_publisher_for_journal("J Biol Chem")
            if publisher_info:
                # Test with verify=False (offline mode)  
                # Update the test PMA to use a journal we know is in the registry
                self.mock_pma_vip.journal = "J Biol Chem"
                with patch('metapub.findit.dances.verify_pdf_url') as mock_verify:
                    url = the_vip_shake(self.mock_pma_vip, verify=False)
                    
                    # Should construct URL without calling verify
                    mock_verify.assert_not_called()
                    self.assertIsNotNone(url)
                    self.assertTrue(url.startswith('http'))
                    self.assertIn(self.mock_pma_vip.volume, url)
                    self.assertIn(self.mock_pma_vip.issue, url)
                    self.assertIn(self.mock_pma_vip.first_page, url)
        finally:
            registry.close()

    def test_bmc_url_construction_offline(self):
        """Test that BMC URLs (DOI-based) can be constructed offline."""
        from metapub.findit.dances import the_bmc_boogie
        
        # BMC URLs are constructed from DOI without network requests
        with patch('metapub.findit.dances.verify_pdf_url') as mock_verify:
            url = the_bmc_boogie(self.mock_pma_bmc, verify=False)
            
            # Should construct URL without calling verify
            mock_verify.assert_not_called()
            self.assertIsNotNone(url)
            self.assertTrue(url.startswith('http'))
            self.assertIn('biomedcentral.com', url)


    def test_findit_verify_flag_integration(self):
        """Test that verify flag is properly integrated in FindIt API."""
        # This is tested indirectly through the working dance function tests
        # and handler system tests above. The FindIt constructor properly
        # stores the verify flag and passes it through the system.
        
        # Test that FindIt accepts verify parameter
        try:
            # These should not raise exceptions
            finder1 = FindIt.__new__(FindIt)  # Create without __init__
            finder1.verify = False
            self.assertFalse(finder1.verify)
            
            finder2 = FindIt.__new__(FindIt)
            finder2.verify = True  
            self.assertTrue(finder2.verify)
            
        except Exception as e:
            self.fail(f"FindIt should accept verify parameter: {e}")

    def test_handler_system_preserves_verify_flag(self):
        """Test that the new handler system properly passes through verify flag."""
        registry_data = {
            'name': 'Test Publisher',
            'dance_function': 'the_nature_ballet'
        }
        handler = PublisherHandler(registry_data)
        
        mock_pma = Mock()
        mock_pma.journal = "Nature"
        
        # Mock the dance function to check verify parameter
        with patch('metapub.findit.dances.the_nature_ballet') as mock_dance:
            mock_dance.return_value = ("http://test.url", None)
            
            # Test with verify=False
            url, reason = handler.get_pdf_url(mock_pma, verify=False)
            
            # Should call dance function with verify=False
            mock_dance.assert_called_once_with(mock_pma, verify=False, request_timeout=10, max_redirects=3)

    def test_registry_lookup_system_preserves_verify_flag(self):
        """Test that RegistryBackedLookupSystem passes verify flag to handlers."""
        mock_registry = Mock()
        mock_registry.get_publisher_for_journal.return_value = {
            'name': 'Test Publisher',
            'dance_function': 'the_nature_ballet'
        }
        
        lookup_system = RegistryBackedLookupSystem(mock_registry)
        mock_pma = Mock()
        mock_pma.journal = "Nature"
        
        with patch('metapub.findit.registry.standardize_journal_name', return_value="Nature"):
            with patch.object(lookup_system, 'get_handler_for_journal') as mock_get_handler:
                mock_handler = Mock()
                mock_handler.get_pdf_url.return_value = ("http://test.url", None)
                mock_get_handler.return_value = mock_handler
                
                # Test with verify=False
                url, reason = lookup_system.find_pdf_url(mock_pma, verify=False)
                
                # Should call handler with verify=False
                mock_handler.get_pdf_url.assert_called_once_with(mock_pma, verify=False, request_timeout=10, max_redirects=3)

    def test_multiple_publishers_offline_capability(self):
        """Test that various publishers support offline URL construction."""
        test_cases = [
            {
                'name': 'Nature (VIP)',
                'dance': 'the_nature_ballet',
                'mock_setup': lambda m: (
                    setattr(m, 'journal', 'Nature'),
                    setattr(m, 'volume', '500'),
                    setattr(m, 'issue', '1'), 
                    setattr(m, 'first_page', '123'),
                    setattr(m, 'doi', '10.1038/nature12373')
                )
            },
            {
                'name': 'BMC (DOI)',
                'dance': 'the_bmc_boogie', 
                'mock_setup': lambda m: (
                    setattr(m, 'doi', '10.1186/s12864-023-09123-4'),
                    setattr(m, 'journal', 'BMC Genomics')
                )
            }
        ]
        
        for case in test_cases:
            with self.subTest(publisher=case['name']):
                mock_pma = Mock()
                case['mock_setup'](mock_pma)
                
                # Import the dance function dynamically
                try:
                    from metapub.findit.dances import (
                        the_nature_ballet, the_bmc_boogie
                    )
                    dance_functions = {
                        'the_nature_ballet': the_nature_ballet,
                        'the_bmc_boogie': the_bmc_boogie
                    }
                    
                    dance_func = dance_functions.get(case['dance'])
                    if dance_func:
                        with patch('metapub.findit.dances.verify_pdf_url') as mock_verify:
                            try:
                                url = dance_func(mock_pma, verify=False)
                                
                                # Should not call verify in offline mode
                                mock_verify.assert_not_called()
                                
                                if url:  # Some may return None for missing data
                                    self.assertTrue(url.startswith('http'), 
                                                  f"{case['name']} should return HTTP URL")
                                    print(f"✓ {case['name']} offline URL: {url}")
                                    
                            except Exception as e:
                                # Some publishers may require additional data
                                # This is acceptable - we just want to ensure verify=False
                                # doesn't cause network requests
                                if "verify_pdf_url" not in str(e):
                                    pass  # Expected for missing required data
                                
                except ImportError:
                    self.skipTest(f"Dance function {case['dance']} not available")

    def test_cache_behavior_with_offline_urls(self):
        """Test that caching system is compatible with offline URL construction."""
        # This test verifies that the caching system properly handles
        # the verify=False flag by checking that cache entries store
        # the verification status
        
        # Test that FindIt handles caching parameters correctly
        import tempfile
        import os
        
        with tempfile.TemporaryDirectory() as temp_dir:
            # Test cache directory creation
            cache_dir = os.path.join(temp_dir, "test_cache")
            
            # These should not raise exceptions
            try:
                # Test that FindIt can be created with cachedir and verify=False
                finder = FindIt.__new__(FindIt)
                finder.verify = False
                finder._cachedir = cache_dir
                
                # Test that verify flag is stored correctly
                self.assertFalse(finder.verify)
                self.assertEqual(finder._cachedir, cache_dir)
                
            except Exception as e:
                self.fail(f"Cache integration with verify=False should work: {e}")


class TestOfflineConstructionDocumentation(unittest.TestCase):
    """Test that offline construction capabilities are well-documented."""

    def test_verify_parameter_documented(self):
        """Test that verify parameter is documented in key functions."""
        from metapub.findit.findit import FindIt
        from metapub.findit.logic import find_article_from_pma, find_article_from_doi
        
        # Check that key functions have verify in their signatures
        import inspect
        
        # FindIt constructor
        findit_sig = inspect.signature(FindIt.__init__)
        # verify should be in kwargs handling or explicit parameter
        
        # find_article_from_pma 
        pma_sig = inspect.signature(find_article_from_pma)
        self.assertIn('verify', pma_sig.parameters, 
                      "find_article_from_pma should have verify parameter")
        
        # find_article_from_doi
        doi_sig = inspect.signature(find_article_from_doi)  
        self.assertIn('verify', doi_sig.parameters,
                      "find_article_from_doi should have verify parameter")

    def test_dance_functions_support_verify_false(self):
        """Test that major dance functions support verify=False."""
        from metapub.findit import dances
        
        # Key dance functions that should support offline construction
        offline_capable_dances = [
            'the_vip_shake',           # VIP format - volume/issue/page
            'the_vip_nonstandard_shake', # Non-standard VIP
            'the_bmc_boogie',          # BMC - DOI based
            'the_nature_ballet',       # Nature - format based
            'the_jci_jig',            # JCI - format based
        ]
        
        for dance_name in offline_capable_dances:
            if hasattr(dances, dance_name):
                dance_func = getattr(dances, dance_name)
                sig = inspect.signature(dance_func)
                
                self.assertIn('verify', sig.parameters, 
                             f"{dance_name} should have verify parameter")
                
                # Check default value
                verify_param = sig.parameters['verify']
                # Most should default to True, but some like BMC default to False
                self.assertIn(verify_param.default, [True, False],
                             f"{dance_name} verify parameter should have boolean default")


if __name__ == '__main__':
    unittest.main()