"""
Test Suite for Relationship Mapper (Story KG-003)

Implements black-box and white-box testing as required by GLOBAL_GENESIS_RULES.md
"""

import pytest
import json
import os
import tempfile
from pathlib import Path
import sys

# Add parent directory to path
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from core.knowledge.relationship_mapper import (
    RelationshipMapper,
    store_relationships,
    load_relationships
)


# ============================================================================
# BLACK BOX TESTS - Testing from outside without implementation knowledge
# ============================================================================

class TestRelationshipMapperBlackBox:
    """Black box tests for RelationshipMapper"""

    def setup_method(self):
        """Setup for each test method"""
        self.mapper = RelationshipMapper(similarity_threshold=0.6)
        self.temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.jsonl')
        self.temp_file.close()

    def teardown_method(self):
        """Cleanup after each test"""
        if os.path.exists(self.temp_file.name):
            os.remove(self.temp_file.name)

    def test_basic_relationship_detection(self):
        """Test that basic relationships are detected from text"""
        entities = ["Service A", "Service B"]
        text = "Service A uses Service B for processing."

        relationships = self.mapper.map_relationships(entities, text)

        assert len(relationships) > 0, "Should detect at least one relationship"
        assert any(r['source'] == "Service A" and r['target'] == "Service B"
                  for r in relationships), "Should find Service A -> Service B relationship"

    def test_multiple_relationship_types(self):
        """Test detection of different relationship types"""
        entities = ["Module A", "Module B", "Module C"]
        text = """
        Module A depends on Module B.
        Module A uses Module C.
        """

        relationships = self.mapper.map_relationships(entities, text)

        relation_types = set(r['relation_type'] for r in relationships)
        assert 'depends_on' in relation_types, "Should detect depends_on relationship"
        assert 'uses' in relation_types, "Should detect uses relationship"

    def test_no_relationships_in_empty_text(self):
        """Test that empty text produces no relationships"""
        entities = ["Entity A", "Entity B"]
        text = ""

        relationships = self.mapper.map_relationships(entities, text)

        # Should have no pattern-based relationships, but might have similarity
        pattern_rels = [r for r in relationships if r['detection_method'] == 'pattern_match']
        assert len(pattern_rels) == 0, "Empty text should produce no pattern relationships"

    def test_store_and_load_relationships(self):
        """Test that relationships can be stored and loaded"""
        entities = ["Entity X", "Entity Y"]
        text = "Entity X references Entity Y in documentation."

        relationships = self.mapper.map_relationships(entities, text)
        count = store_relationships(relationships, self.temp_file.name)

        assert count > 0, "Should store at least one relationship"

        loaded = load_relationships(self.temp_file.name)
        assert len(loaded) == count, "Should load same number of relationships"
        assert loaded[0]['source'] is not None, "Loaded relationship should have source"
        assert loaded[0]['target'] is not None, "Loaded relationship should have target"

    def test_filtering_by_confidence(self):
        """Test loading relationships with confidence filter"""
        # Create test data with different confidence levels
        test_relationships = [
            {
                "source": "A", "target": "B", "relation_type": "uses",
                "confidence": 0.9, "detection_method": "test", "timestamp": "2026-01-25",
                "metadata": {}
            },
            {
                "source": "C", "target": "D", "relation_type": "uses",
                "confidence": 0.5, "detection_method": "test", "timestamp": "2026-01-25",
                "metadata": {}
            }
        ]

        store_relationships(test_relationships, self.temp_file.name)
        loaded = load_relationships(self.temp_file.name, min_confidence=0.8)

        assert len(loaded) == 1, "Should only load high confidence relationships"
        assert loaded[0]['confidence'] >= 0.8, "Loaded relationship should meet threshold"

    def test_filtering_by_type(self):
        """Test loading relationships filtered by type"""
        test_relationships = [
            {
                "source": "A", "target": "B", "relation_type": "depends_on",
                "confidence": 0.9, "detection_method": "test", "timestamp": "2026-01-25",
                "metadata": {}
            },
            {
                "source": "C", "target": "D", "relation_type": "uses",
                "confidence": 0.9, "detection_method": "test", "timestamp": "2026-01-25",
                "metadata": {}
            }
        ]

        store_relationships(test_relationships, self.temp_file.name)
        loaded = load_relationships(self.temp_file.name, relation_type="depends_on")

        assert len(loaded) == 1, "Should only load depends_on relationships"
        assert loaded[0]['relation_type'] == "depends_on", "Loaded relationship should be correct type"


# ============================================================================
# WHITE BOX TESTS - Testing with knowledge of internal implementation
# ============================================================================

class TestRelationshipMapperWhiteBox:
    """White box tests for RelationshipMapper internal methods"""

    def setup_method(self):
        """Setup for each test method"""
        self.mapper = RelationshipMapper(similarity_threshold=0.6)

    def test_detect_code_imports_python(self):
        """Test Python import detection"""
        entities = ["Database", "UserService", "Authentication"]
        text = """
import Database
from UserService import get_user
from Authentication import verify
        """

        relationships = self.mapper._detect_code_imports(text, entities)

        assert len(relationships) > 0, "Should detect import relationships"
        assert any(r['relation_type'] == 'depends_on' for r in relationships), \
            "Should create depends_on relationships"
        assert any(r['detection_method'] == 'code_import' for r in relationships), \
            "Should mark as code_import"

    def test_detect_contains_relationships(self):
        """Test file structure containment detection"""
        # Create a temporary file structure
        with tempfile.TemporaryDirectory() as tmpdir:
            service_dir = Path(tmpdir) / "UserService"
            service_dir.mkdir()
            test_file = service_dir / "handler.py"
            test_file.touch()

            entities = ["UserService", "handler"]
            relationships = self.mapper._detect_contains_relationships(
                str(test_file), entities
            )

            assert len(relationships) > 0, "Should detect containment relationship"
            assert any(r['relation_type'] == 'contains' for r in relationships), \
                "Should create contains relationships"

    def test_detect_references_markdown_links(self):
        """Test documentation reference detection via markdown links"""
        entities = ["API Documentation", "UserGuide"]
        text = """
        See the [API Documentation](./api.md) for details.
        Refer to [UserGuide](#user-guide) for instructions.
        """

        relationships = self.mapper._detect_references(text, entities)

        assert len(relationships) > 0, "Should detect reference relationships"
        assert any(r['relation_type'] == 'references' for r in relationships), \
            "Should create references relationships"
        assert any('link_url' in r['metadata'] for r in relationships), \
            "Should include link metadata"

    def test_detect_references_textual(self):
        """Test textual reference detection"""
        entities = ["Configuration", "Setup Guide"]
        text = """
        See Configuration for more details.
        Documented in Setup Guide.
        """

        relationships = self.mapper._detect_references(text, entities)

        assert len(relationships) > 0, "Should detect textual references"
        assert any(r['detection_method'] == 'textual_reference' for r in relationships), \
            "Should mark as textual reference"

    def test_detect_similarity_high(self):
        """Test similarity detection for similar entities"""
        entities = ["UserService", "UserServiceImpl", "DataService"]

        relationships = self.mapper._detect_similarity(entities, None)

        # UserService and UserServiceImpl should be similar
        similar_pairs = [(r['source'], r['target']) for r in relationships
                        if r['relation_type'] == 'similar_to']

        assert len(similar_pairs) > 0, "Should detect similar entities"

        # Check for UserService <-> UserServiceImpl similarity
        assert any(
            ('UserService' in pair and 'UserServiceImpl' in pair)
            for pair in similar_pairs
        ), "Should detect UserService and UserServiceImpl as similar"

    def test_detect_similarity_low(self):
        """Test that dissimilar entities are not marked as similar"""
        entities = ["Apple", "Zebra"]

        relationships = self.mapper._detect_similarity(entities, None)

        # These should not be similar
        assert len(relationships) == 0, "Dissimilar entities should not be related"

    def test_calculate_similarity_identical(self):
        """Test similarity calculation for identical strings"""
        similarity = self.mapper._calculate_similarity("test", "test")
        assert similarity == 1.0, "Identical strings should have similarity of 1.0"

    def test_calculate_similarity_different(self):
        """Test similarity calculation for different strings"""
        similarity = self.mapper._calculate_similarity("apple", "orange")
        assert similarity < 0.5, "Different strings should have low similarity"

    def test_create_relationship_structure(self):
        """Test relationship creation has correct structure"""
        rel = self.mapper._create_relationship(
            source="A",
            relation_type="uses",
            target="B",
            confidence=0.85,
            source_type="test",
            metadata={"key": "value"}
        )

        assert rel['source'] == "A", "Should have correct source"
        assert rel['target'] == "B", "Should have correct target"
        assert rel['relation_type'] == "uses", "Should have correct relation type"
        assert rel['confidence'] == 0.85, "Should have correct confidence"
        assert rel['detection_method'] == "test", "Should have correct detection method"
        assert 'timestamp' in rel, "Should have timestamp"
        assert rel['metadata']['key'] == "value", "Should preserve metadata"

    def test_patterns_coverage(self):
        """Test that all defined patterns are used"""
        entities = ["A", "B"]

        # Test each pattern type
        test_cases = [
            ("A uses B", "uses"),
            ("A depends on B", "depends_on"),
            ("A configures B", "configures"),
            ("A generates B", "generates"),
            ("A monitors B", "monitors"),
            ("A contains B", "contains"),
            ("A references B", "references"),
        ]

        for text, expected_type in test_cases:
            relationships = self.mapper.map_relationships(entities, text)
            pattern_rels = [r for r in relationships if r['detection_method'] == 'pattern_match']

            assert any(r['relation_type'] == expected_type for r in pattern_rels), \
                f"Should detect {expected_type} pattern in '{text}'"


# ============================================================================
# INTEGRATION TESTS
# ============================================================================

class TestRelationshipMapperIntegration:
    """Integration tests combining multiple features"""

    def test_full_pipeline(self):
        """Test complete pipeline: detect -> store -> load -> filter"""
        mapper = RelationshipMapper(similarity_threshold=0.6)

        entities = ["ServiceA", "ServiceB", "ServiceC"]
        text = """
        ServiceA uses ServiceB for authentication.
        ServiceB depends on ServiceC.
        See [ServiceA Documentation](./docs/servicea.md) for details.

        import ServiceB
        from ServiceC import authenticate
        """

        # Create temporary file
        with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.jsonl') as f:
            temp_file = f.name

        try:
            # Detect relationships
            relationships = mapper.map_relationships(entities, text)
            assert len(relationships) > 0, "Should detect relationships"

            # Store relationships
            count = store_relationships(relationships, temp_file)
            assert count == len(relationships), "Should store all relationships"

            # Load all relationships
            loaded = load_relationships(temp_file)
            assert len(loaded) == count, "Should load all relationships"

            # Load with filters
            high_conf = load_relationships(temp_file, min_confidence=0.8)
            assert len(high_conf) <= count, "Filtered results should be <= total"

            depends_on = load_relationships(temp_file, relation_type="depends_on")
            assert all(r['relation_type'] == "depends_on" for r in depends_on), \
                "Should only return depends_on relationships"

        finally:
            if os.path.exists(temp_file):
                os.remove(temp_file)

    def test_edge_case_special_characters(self):
        """Test handling of entities with special characters"""
        mapper = RelationshipMapper()
        entities = ["Service-A", "Service.B", "Service_C"]
        text = "Service-A uses Service.B and Service_C."

        relationships = mapper.map_relationships(entities, text)

        # Should handle special characters in entity names
        assert len(relationships) >= 0, "Should not crash on special characters"

    def test_edge_case_very_long_text(self):
        """Test handling of very long text"""
        mapper = RelationshipMapper()
        entities = ["EntityA", "EntityB"]

        # Generate long text
        text = "EntityA uses EntityB. " * 1000

        relationships = mapper.map_relationships(entities, text)

        # Should handle long text without crashing
        assert len(relationships) > 0, "Should detect relationships in long text"

    def test_edge_case_no_entities(self):
        """Test handling of empty entity list"""
        mapper = RelationshipMapper()
        entities = []
        text = "Some text without entities."

        relationships = mapper.map_relationships(entities, text)

        assert len(relationships) == 0, "Should return empty list for no entities"

    def test_edge_case_duplicate_relationships(self):
        """Test that duplicate relationships can be stored"""
        test_rels = [
            {
                "source": "A", "target": "B", "relation_type": "uses",
                "confidence": 0.9, "detection_method": "test",
                "timestamp": "2026-01-25", "metadata": {}
            },
            {
                "source": "A", "target": "B", "relation_type": "uses",
                "confidence": 0.9, "detection_method": "test",
                "timestamp": "2026-01-25", "metadata": {}
            }
        ]

        with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.jsonl') as f:
            temp_file = f.name

        try:
            count = store_relationships(test_rels, temp_file)
            assert count == 2, "Should store both relationships even if duplicate"

            loaded = load_relationships(temp_file)
            assert len(loaded) == 2, "Should load both relationships"

        finally:
            if os.path.exists(temp_file):
                os.remove(temp_file)


# ============================================================================
# PERFORMANCE TESTS
# ============================================================================

class TestRelationshipMapperPerformance:
    """Performance baseline tests"""

    def test_performance_large_entity_list(self):
        """Test performance with large number of entities"""
        import time

        mapper = RelationshipMapper(similarity_threshold=0.7)

        # Create 100 entities
        entities = [f"Entity_{i}" for i in range(100)]
        text = "Entity_0 uses Entity_1. Entity_1 depends on Entity_2."

        start_time = time.time()
        relationships = mapper.map_relationships(entities, text)
        elapsed = time.time() - start_time

        # Should complete in reasonable time (< 5 seconds for 100 entities)
        assert elapsed < 5.0, f"Processing took too long: {elapsed:.2f}s"
        assert len(relationships) >= 0, "Should return results"


# ============================================================================
# REGRESSION TESTS
# ============================================================================

class TestRelationshipMapperRegression:
    """Regression tests to ensure existing functionality works"""

    def test_original_example_still_works(self):
        """Test that the original example from the code still works"""
        mapper = RelationshipMapper()
        entities = ["User Service", "Database", "Authentication Module", "Monitoring Tool"]
        text = """
        The User Service uses the Database to store user information.
        The Authentication Module depends on the User Service for authentication.
        The Monitoring Tool monitors the User Service and the Database.
        """

        relationships = mapper.map_relationships(entities, text)

        # Should detect the same relationships as before
        assert len(relationships) > 0, "Should detect relationships from original example"

        # Check for specific relationships
        uses_rels = [r for r in relationships
                    if r['relation_type'] == 'uses' and r['detection_method'] == 'pattern_match']
        depends_rels = [r for r in relationships
                       if r['relation_type'] == 'depends_on' and r['detection_method'] == 'pattern_match']
        monitors_rels = [r for r in relationships
                        if r['relation_type'] == 'monitors' and r['detection_method'] == 'pattern_match']

        assert len(uses_rels) > 0, "Should detect 'uses' relationships"
        assert len(depends_rels) > 0, "Should detect 'depends_on' relationships"
        assert len(monitors_rels) > 0, "Should detect 'monitors' relationships"


# Run tests if executed directly
if __name__ == '__main__':
    pytest.main([__file__, '-v', '--tb=short'])
