"""
Test suite for Knowledge Graph Visualizer (Story KG-007)

Black-box and white-box tests for graph_visualizer.py
"""

import json
import os
import sys
import tempfile
from pathlib import Path

# Add project root to path
sys.path.insert(0, str(Path(__file__).parent.parent))

from core.knowledge.graph_visualizer import KnowledgeGraphVisualizer


class TestGraphVisualizerBlackBox:
    """Black-box tests - test from external API perspective"""

    def setup_method(self):
        """Setup test fixtures"""
        self.visualizer = KnowledgeGraphVisualizer()

    def test_load_entities_returns_list(self):
        """BB-1: Loading entities returns a list"""
        entities = self.visualizer.load_entities()
        assert isinstance(entities, list), "load_entities should return a list"
        assert len(entities) > 0, "Should load at least one entity"

    def test_generate_d3_json_has_required_keys(self):
        """BB-2: Generated JSON has required keys"""
        self.visualizer.load_entities()
        self.visualizer.infer_relationships()
        data = self.visualizer.generate_d3_json()

        assert "nodes" in data, "JSON must have 'nodes' key"
        assert "links" in data, "JSON must have 'links' key"
        assert "metadata" in data, "JSON must have 'metadata' key"

    def test_node_structure(self):
        """BB-3: Each node has required fields"""
        self.visualizer.load_entities()
        self.visualizer.infer_relationships()
        data = self.visualizer.generate_d3_json()

        if data["nodes"]:
            node = data["nodes"][0]
            required_fields = ["id", "type", "label", "confidence", "metadata"]
            for field in required_fields:
                assert field in node, f"Node must have '{field}' field"

    def test_link_structure(self):
        """BB-4: Each link has required fields"""
        self.visualizer.load_entities()
        self.visualizer.infer_relationships()
        data = self.visualizer.generate_d3_json()

        if data["links"]:
            link = data["links"][0]
            required_fields = ["source", "target", "type"]
            for field in required_fields:
                assert field in link, f"Link must have '{field}' field"

    def test_export_json_creates_file(self):
        """BB-5: Export JSON creates valid file"""
        self.visualizer.load_entities()
        self.visualizer.infer_relationships()

        with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
            output_path = f.name

        try:
            result_path = self.visualizer.export_json(output_path)
            assert os.path.exists(result_path), "Output file should exist"

            # Verify it's valid JSON
            with open(result_path, 'r') as f:
                data = json.load(f)
                assert "nodes" in data, "Exported JSON should have nodes"
        finally:
            if os.path.exists(output_path):
                os.remove(output_path)

    def test_get_entity_types_returns_list(self):
        """BB-6: Getting entity types returns sorted list"""
        self.visualizer.load_entities()
        types = self.visualizer.get_entity_types()

        assert isinstance(types, list), "Should return a list"
        assert len(types) > 0, "Should have at least one type"
        # Check if sorted
        assert types == sorted(types), "Types should be sorted"

    def test_statistics_calculation(self):
        """BB-7: Statistics are calculated correctly"""
        self.visualizer.load_entities()
        self.visualizer.infer_relationships()
        stats = self.visualizer.get_statistics()

        assert "total_entities" in stats
        assert "total_relationships" in stats
        assert "average_confidence" in stats
        assert stats["total_entities"] == len(self.visualizer.entities)
        assert stats["total_relationships"] == len(self.visualizer.relationships)

    def test_confidence_scores_in_range(self):
        """BB-8: All confidence scores are between 0 and 1"""
        self.visualizer.load_entities()
        self.visualizer.infer_relationships()
        data = self.visualizer.generate_d3_json()

        for node in data["nodes"]:
            confidence = node["confidence"]
            assert 0.0 <= confidence <= 1.0, f"Confidence {confidence} out of range [0,1]"


class TestGraphVisualizerWhiteBox:
    """White-box tests - test internal implementation details"""

    def setup_method(self):
        """Setup test fixtures"""
        self.visualizer = KnowledgeGraphVisualizer()

    def test_infer_confidence_high_relevance(self):
        """WB-1: High relevance entities get 0.9 confidence"""
        entity = {"id": "test1", "relevance": "high"}
        confidence = self.visualizer.infer_confidence(entity)
        assert confidence >= 0.9, "High relevance should give >= 0.9 confidence"

    def test_infer_confidence_validated_status(self):
        """WB-2: VALIDATED status gets 0.95 confidence"""
        entity = {"id": "test2", "status": "VALIDATED"}
        confidence = self.visualizer.infer_confidence(entity)
        assert confidence >= 0.95, "VALIDATED status should give >= 0.95 confidence"

    def test_infer_confidence_default(self):
        """WB-3: Entities with no signals get default 0.5"""
        entity = {"id": "test3"}
        confidence = self.visualizer.infer_confidence(entity)
        assert confidence == 0.5, "Default confidence should be 0.5"

    def test_extract_entity_label_priority(self):
        """WB-4: Label extraction follows priority: title > name > id"""
        # Test title priority
        entity1 = {"id": "test1", "title": "Test Title", "name": "Test Name"}
        label1 = self.visualizer.extract_entity_label(entity1)
        assert label1 == "Test Title", "Should prefer title over name"

        # Test name priority
        entity2 = {"id": "test2", "name": "Test Name"}
        label2 = self.visualizer.extract_entity_label(entity2)
        assert label2 == "Test Name", "Should use name when title absent"

        # Test id fallback
        entity3 = {"id": "test3"}
        label3 = self.visualizer.extract_entity_label(entity3)
        assert label3 == "test3", "Should use id when title and name absent"

    def test_patent_synergy_relationships(self):
        """WB-5: Patent synergy creates correct relationships"""
        # Create test entities with patent synergy
        self.visualizer.entities = [
            {"id": "e1", "type": "test", "patent_synergy": "P4, P7"},
            {"id": "e2", "type": "test", "patent_synergy": "P4"},
            {"id": "e3", "type": "test", "patent_synergy": "P7"}
        ]

        relationships = self.visualizer.infer_relationships()

        # Should create links between entities sharing patents
        patent_links = [r for r in relationships if r["type"] == "patent_synergy"]
        assert len(patent_links) > 0, "Should create patent synergy links"

        # Verify link structure
        for link in patent_links:
            assert "source" in link
            assert "target" in link
            assert "label" in link

    def test_type_clustering_relationships(self):
        """WB-6: Type clustering creates star patterns"""
        # Create test entities of same type
        self.visualizer.entities = [
            {"id": "t1", "type": "test_type"},
            {"id": "t2", "type": "test_type"},
            {"id": "t3", "type": "test_type"}
        ]

        relationships = self.visualizer.infer_relationships()
        type_links = [r for r in relationships if r["type"] == "same_type"]

        # Should create type clustering links
        assert len(type_links) > 0, "Should create type clustering links"

    def test_entity_id_normalization(self):
        """WB-7: Entities without id get normalized"""
        # Test data without id
        test_file = tempfile.NamedTemporaryFile(mode='w', suffix='.jsonl', delete=False)
        test_file.write(json.dumps({"video_id": "abc123", "type": "test"}) + "\n")
        test_file.write(json.dumps({"type": "test2"}) + "\n")  # No id or video_id
        test_file.close()

        try:
            visualizer = KnowledgeGraphVisualizer()
            visualizer.entities_path = Path(test_file.name)
            entities = visualizer.load_entities()

            # First entity should get YT_ prefix
            assert entities[0]["id"] == "YT_abc123", "video_id should be normalized to id"

            # Second entity should get entity_N id
            assert entities[1]["id"].startswith("entity_"), "Missing id should get entity_N"
        finally:
            os.remove(test_file.name)

    def test_generate_d3_json_without_relationships(self):
        """WB-8: Can generate JSON without relationships"""
        self.visualizer.load_entities()
        # Don't infer relationships
        data = self.visualizer.generate_d3_json(include_relationships=False)

        assert len(data["links"]) == 0, "Should have no links when include_relationships=False"
        assert len(data["nodes"]) > 0, "Should still have nodes"

    def test_statistics_type_distribution(self):
        """WB-9: Statistics include type distribution"""
        self.visualizer.load_entities()
        self.visualizer.infer_relationships()
        stats = self.visualizer.get_statistics()

        assert "type_distribution" in stats
        assert isinstance(stats["type_distribution"], dict)

        # Verify counts add up
        total_from_distribution = sum(stats["type_distribution"].values())
        assert total_from_distribution == stats["total_entities"]


def run_all_tests():
    """Run all tests and report results"""
    import traceback

    black_box_tests = TestGraphVisualizerBlackBox()
    white_box_tests = TestGraphVisualizerWhiteBox()

    all_tests = []

    # Collect black-box tests
    for method_name in dir(black_box_tests):
        if method_name.startswith("test_"):
            all_tests.append(("BLACK-BOX", method_name, getattr(black_box_tests, method_name)))

    # Collect white-box tests
    for method_name in dir(white_box_tests):
        if method_name.startswith("test_"):
            all_tests.append(("WHITE-BOX", method_name, getattr(white_box_tests, method_name)))

    passed = 0
    failed = 0
    results = []

    for test_type, test_name, test_method in all_tests:
        try:
            # Setup
            if test_type == "BLACK-BOX":
                black_box_tests.setup_method()
            else:
                white_box_tests.setup_method()

            # Run test
            test_method()
            passed += 1
            results.append(f"✓ {test_type}: {test_name}")
        except AssertionError as e:
            failed += 1
            results.append(f"✗ {test_type}: {test_name} - {str(e)}")
        except Exception as e:
            failed += 1
            results.append(f"✗ {test_type}: {test_name} - ERROR: {str(e)}")
            traceback.print_exc()

    # Print results
    print("\n" + "="*80)
    print("TEST RESULTS: Knowledge Graph Visualizer (Story KG-007)")
    print("="*80)
    for result in results:
        print(result)

    print("\n" + "-"*80)
    print(f"TOTAL: {len(all_tests)} tests")
    print(f"PASSED: {passed}")
    print(f"FAILED: {failed}")
    print(f"COVERAGE: {(passed/len(all_tests)*100):.1f}%")
    print("-"*80)

    if failed == 0:
        print("\n✓ ALL TESTS PASSED")
        return True
    else:
        print(f"\n✗ {failed} TESTS FAILED")
        return False


if __name__ == "__main__":
    success = run_all_tests()
    sys.exit(0 if success else 1)
