"""
Tests for Glossary Service
Story 3.10: Glossaires - Application lors Traduction LLM
"""

import pytest
from unittest.mock import Mock, patch, MagicMock
import uuid

from services.glossary_service import (
    get_glossary_terms,
    validate_glossary_access,
    format_glossary_for_prompt,
    build_full_prompt,
)
from utils.exceptions import GlossaryNotFoundError


class TestGetGlossaryTerms:
    """Tests for get_glossary_terms function."""

    def test_get_glossary_terms_success(self):
        """Test retrieving terms from an existing glossary."""
        glossary_id = str(uuid.uuid4())
        user_id = str(uuid.uuid4())
        
        # Mock the database session and models
        mock_glossary = Mock()
        mock_glossary.id = glossary_id
        mock_glossary.user_id = user_id
        
        mock_term1 = Mock()
        mock_term1.source = "cloud computing"
        mock_term1.target = "informatique en nuage"
        
        mock_term2 = Mock()
        mock_term2.source = "machine learning"
        mock_term2.target = "apprentissage automatique"
        
        with patch('services.glossary_service.get_sync_session') as mock_session:
            mock_context = MagicMock()
            mock_session.return_value.__enter__ = Mock(return_value=mock_context)
            mock_session.return_value.__exit__ = Mock(return_value=False)
            
            # First call: glossary query, Second call: terms query
            mock_glossary_query = MagicMock()
            mock_terms_query = MagicMock()
            mock_context.query.side_effect = [mock_glossary_query, mock_terms_query]
            
            mock_glossary_query.filter.return_value = mock_glossary_query
            mock_glossary_query.first.return_value = mock_glossary
            
            mock_terms_query.filter.return_value = mock_terms_query
            mock_terms_query.all.return_value = [mock_term1, mock_term2]
            
            result = get_glossary_terms(glossary_id, user_id)
            
            terms = result["terms"]
            assert len(terms) == 2
            assert terms[0]["source"] == "cloud computing"
            assert terms[0]["target"] == "informatique en nuage"
            assert terms[1]["source"] == "machine learning"
            assert terms[1]["target"] == "apprentissage automatique"

    def test_get_glossary_terms_not_found(self):
        """Test error when glossary doesn't exist."""
        glossary_id = str(uuid.uuid4())
        user_id = str(uuid.uuid4())
        
        with patch('services.glossary_service.get_sync_session') as mock_session:
            mock_context = MagicMock()
            mock_session.return_value.__enter__ = Mock(return_value=mock_context)
            mock_session.return_value.__exit__ = Mock(return_value=False)
            
            mock_query = MagicMock()
            mock_context.query.return_value = mock_query
            mock_query.filter.return_value = mock_query
            mock_query.first.return_value = None  # Glossary not found
            
            with pytest.raises(GlossaryNotFoundError) as exc_info:
                get_glossary_terms(glossary_id, user_id)
            
            assert exc_info.value.code == "GLOSSARY_NOT_FOUND"

    def test_get_glossary_terms_wrong_user(self):
        """Test error when glossary belongs to another user."""
        glossary_id = str(uuid.uuid4())
        user_id = str(uuid.uuid4())
        other_user_id = str(uuid.uuid4())
        
        with patch('services.glossary_service.get_sync_session') as mock_session:
            mock_context = MagicMock()
            mock_session.return_value.__enter__ = Mock(return_value=mock_context)
            mock_session.return_value.__exit__ = Mock(return_value=False)
            
            mock_query = MagicMock()
            mock_context.query.return_value = mock_query
            mock_query.filter.return_value = mock_query
            mock_query.first.return_value = None  # No match for this user
            
            with pytest.raises(GlossaryNotFoundError) as exc_info:
                get_glossary_terms(glossary_id, user_id)
            
            assert exc_info.value.code == "GLOSSARY_NOT_FOUND"

    def test_get_glossary_terms_empty(self):
        """Test retrieving terms from a glossary with no terms."""
        glossary_id = str(uuid.uuid4())
        user_id = str(uuid.uuid4())
        
        mock_glossary = Mock()
        mock_glossary.id = glossary_id
        mock_glossary.user_id = user_id
        
        with patch('services.glossary_service.get_sync_session') as mock_session:
            mock_context = MagicMock()
            mock_session.return_value.__enter__ = Mock(return_value=mock_context)
            mock_session.return_value.__exit__ = Mock(return_value=False)
            
            mock_query = MagicMock()
            mock_context.query.side_effect = [mock_query, MagicMock()]
            mock_query.filter.return_value = mock_query
            mock_query.first.return_value = mock_glossary
            
            # Empty terms list
            mock_terms_query = MagicMock()
            mock_terms_query.filter.return_value = mock_terms_query
            mock_terms_query.all.return_value = []
            
            result = get_glossary_terms(glossary_id, user_id)
            
            assert result["terms"] == []


class TestValidateGlossaryAccess:
    """Tests for validate_glossary_access function."""

    def test_validate_glossary_access_success(self):
        """Test validating access to an existing glossary."""
        glossary_id = str(uuid.uuid4())
        user_id = str(uuid.uuid4())
        
        mock_glossary = Mock()
        mock_glossary.id = glossary_id
        mock_glossary.user_id = user_id
        
        with patch('services.glossary_service.get_sync_session') as mock_session:
            mock_context = MagicMock()
            mock_session.return_value.__enter__ = Mock(return_value=mock_context)
            mock_session.return_value.__exit__ = Mock(return_value=False)
            
            mock_query = MagicMock()
            mock_context.query.return_value = mock_query
            mock_query.filter.return_value = mock_query
            mock_query.first.return_value = mock_glossary
            
            result = validate_glossary_access(glossary_id, user_id)
            
            assert result is True

    def test_validate_glossary_access_not_found(self):
        """Test error when glossary doesn't exist."""
        glossary_id = str(uuid.uuid4())
        user_id = str(uuid.uuid4())
        
        with patch('services.glossary_service.get_sync_session') as mock_session:
            mock_context = MagicMock()
            mock_session.return_value.__enter__ = Mock(return_value=mock_context)
            mock_session.return_value.__exit__ = Mock(return_value=False)
            
            mock_query = MagicMock()
            mock_context.query.return_value = mock_query
            mock_query.filter.return_value = mock_query
            mock_query.first.return_value = None
            
            with pytest.raises(GlossaryNotFoundError):
                validate_glossary_access(glossary_id, user_id)


class TestFormatGlossaryForPrompt:
    """Tests for format_glossary_for_prompt function."""

    def test_format_glossary_basic(self):
        """Test basic glossary formatting."""
        terms = [
            {"source": "cloud computing", "target": "informatique en nuage"},
            {"source": "API", "target": "interface de programmation"},
        ]
        
        result = format_glossary_for_prompt(terms)
        
        assert "TERMINOLOGY GLOSSARY" in result
        assert "'cloud computing' → 'informatique en nuage'" in result
        assert "'API' → 'interface de programmation'" in result
        assert "IMPORTANT: Always use these translations" in result

    def test_format_glossary_sorted_by_length(self):
        """Test that terms are sorted by length (longest first)."""
        terms = [
            {"source": "API", "target": "interface"},
            {"source": "machine learning", "target": "apprentissage automatique"},
            {"source": "cloud", "target": "nuage"},
        ]
        
        result = format_glossary_for_prompt(terms)
        
        # "machine learning" should appear before "cloud" and "API"
        ml_pos = result.index("machine learning")
        cloud_pos = result.index("'cloud'")
        api_pos = result.index("'API'")
        
        assert ml_pos < cloud_pos
        assert ml_pos < api_pos

    def test_format_glossary_empty(self):
        """Test formatting an empty glossary."""
        result = format_glossary_for_prompt([])
        
        assert result == ""

    def test_format_glossary_special_characters(self):
        """Test formatting terms with special characters."""
        terms = [
            {"source": "it's", "target": "c'est"},
            {"source": "user's guide", "target": "guide de l'utilisateur"},
        ]
        
        result = format_glossary_for_prompt(terms)
        
        # Single quotes should be escaped
        assert "it\\'s" in result
        assert "c\\'est" in result

    def test_format_glossary_empty_source_target(self):
        """Test that empty source or target are skipped."""
        terms = [
            {"source": "valid", "target": "valide"},
            {"source": "", "target": "empty_source"},
            {"source": "empty_target", "target": ""},
        ]
        
        result = format_glossary_for_prompt(terms)
        
        assert "'valid' → 'valide'" in result
        assert "empty_source" not in result
        assert "empty_target" not in result


class TestBuildFullPrompt:
    """Tests for build_full_prompt function."""

    def test_build_full_prompt_both(self):
        """Test building prompt with both custom prompt and glossary."""
        custom_prompt = "Translate technical documents accurately."
        glossary_terms = [
            {"source": "API", "target": "interface de programmation"},
        ]
        
        result = build_full_prompt(custom_prompt, glossary_terms)
        
        assert "Translate technical documents accurately." in result
        assert "TERMINOLOGY GLOSSARY" in result
        assert "'API' → 'interface de programmation'" in result

    def test_build_full_prompt_only_custom(self):
        """Test building prompt with only custom prompt."""
        custom_prompt = "Translate technical documents accurately."
        
        result = build_full_prompt(custom_prompt, None)
        
        assert result == "Translate technical documents accurately."

    def test_build_full_prompt_only_glossary(self):
        """Test building prompt with only glossary."""
        glossary_terms = [
            {"source": "API", "target": "interface de programmation"},
        ]
        
        result = build_full_prompt(None, glossary_terms)
        
        assert "TERMINOLOGY GLOSSARY" in result
        assert "'API' → 'interface de programmation'" in result

    def test_build_full_prompt_empty(self):
        """Test building prompt with neither custom prompt nor glossary."""
        result = build_full_prompt(None, None)
        
        assert result == ""

    def test_build_full_prompt_empty_glossary_list(self):
        """Test building prompt with empty glossary list."""
        custom_prompt = "Translate accurately."
        
        result = build_full_prompt(custom_prompt, [])
        
        assert result == "Translate accurately."


class TestGetGlossaryTermsDatabaseErrors:
    """Tests for database error handling in get_glossary_terms."""

    def test_get_glossary_terms_database_error(self):
        """Test that database errors are wrapped in GlossaryNotFoundError."""
        glossary_id = str(uuid.uuid4())
        user_id = str(uuid.uuid4())
        
        with patch('services.glossary_service.get_sync_session') as mock_session:
            # Simulate a database connection error
            mock_session.side_effect = Exception("Database connection failed")
            
            with pytest.raises(GlossaryNotFoundError) as exc_info:
                get_glossary_terms(glossary_id, user_id)
            
            assert exc_info.value.code == "GLOSSARY_NOT_FOUND"
            assert "Erreur lors de la récupération" in str(exc_info.value.message)

    def test_validate_glossary_access_database_error(self):
        """Test that database errors are wrapped in GlossaryNotFoundError."""
        glossary_id = str(uuid.uuid4())
        user_id = str(uuid.uuid4())
        
        with patch('services.glossary_service.get_sync_session') as mock_session:
            # Simulate a database connection error
            mock_session.side_effect = Exception("Database connection failed")
            
            with pytest.raises(GlossaryNotFoundError) as exc_info:
                validate_glossary_access(glossary_id, user_id)
            
            assert exc_info.value.code == "GLOSSARY_NOT_FOUND"


class TestGlossaryIntegration:
    """Integration-style tests for glossary in translation flow."""

    def test_empty_glossary_terms_returns_empty_list(self):
        """Test that a glossary with no terms returns empty list."""
        glossary_id = str(uuid.uuid4())
        user_id = str(uuid.uuid4())
        
        mock_glossary = Mock()
        mock_glossary.id = glossary_id
        mock_glossary.user_id = user_id
        
        with patch('services.glossary_service.get_sync_session') as mock_session:
            mock_context = MagicMock()
            mock_session.return_value.__enter__ = Mock(return_value=mock_context)
            mock_session.return_value.__exit__ = Mock(return_value=False)
            
            mock_glossary_query = MagicMock()
            mock_terms_query = MagicMock()
            mock_context.query.side_effect = [mock_glossary_query, mock_terms_query]
            
            mock_glossary_query.filter.return_value = mock_glossary_query
            mock_glossary_query.first.return_value = mock_glossary
            
            mock_terms_query.filter.return_value = mock_terms_query
            mock_terms_query.all.return_value = []  # Empty terms
            
            result = get_glossary_terms(glossary_id, user_id)
            
            assert result["terms"] == []

    def test_build_full_prompt_with_empty_glossary_terms(self):
        """Test that empty glossary terms don't add content to prompt."""
        custom_prompt = "Translate accurately."
        
        result = build_full_prompt(custom_prompt, [])
        
        # Should only contain custom prompt, no glossary section
        assert result == "Translate accurately."
        assert "TERMINOLOGY GLOSSARY" not in result

    def test_format_glossary_with_unicode_characters(self):
        """Test formatting terms with unicode characters."""
        terms = [
            {"source": "café", "target": "coffee"},
            {"source": "naïve", "target": "naive"},
            {"source": "日本語", "target": "Japanese"},
        ]
        
        result = format_glossary_for_prompt(terms)
        
        assert "'café' → 'coffee'" in result
        assert "'naïve' → 'naive'" in result
        assert "'日本語' → 'Japanese'" in result
