#!/usr/bin/env python3
"""
Tests for Phase 11: Export/Import Formats
==========================================
Comprehensive test suite covering:
- RDF/Turtle export and import
- Neo4j Cypher export
- JSON-LD export and import
- Round-trip integrity verification
- Multi-tenant support
"""

import json
import tempfile
import unittest
from pathlib import Path
import sys

# Add project root to path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))

from tools.factory import create_artifact, persist_artifact
from tools.export import (
    export_artifacts, ExportFormat, check_dependencies,
    export_to_neo4j_cypher, SCHEMA_PROPERTY_MAP, TYPE_MAP,
    get_artifact_id, get_artifact_types, stream_artifacts
)
from tools.import_data import (
    import_artifacts, ImportFormat, check_dependencies as import_check_dependencies,
    _detect_artifact_type, _ensure_defaults
)


class TestExportJsonLD(unittest.TestCase):
    """Test JSON-LD export functionality."""

    def setUp(self):
        """Create temporary directory with test artifacts."""
        self.temp_dir = tempfile.TemporaryDirectory()
        self.test_dir = Path(self.temp_dir.name)
        self.output_dir = tempfile.TemporaryDirectory()
        self.output_path = Path(self.output_dir.name)

        # Create test artifacts
        for i in range(3):
            payload = {
                "title": f"Export Test Decision {i}",
                "context": "Testing export functionality",
                "options_considered": ["A", "B", "C"],
                "chosen_option": "A",
                "reasoning": f"Reason {i}",
                "constraints": ["Budget"],
                "assumptions": ["Growth"],
                "confidence": 0.85,
                "expected_outcome": "Success",
                "category": "export-test"
            }
            artifact = create_artifact("decision", payload)
            persist_artifact(artifact, self.test_dir)

    def tearDown(self):
        """Clean up temporary directories."""
        self.temp_dir.cleanup()
        self.output_dir.cleanup()

    def test_export_jsonld_basic(self):
        """Test basic JSON-LD export."""
        output_file = self.output_path / "export.jsonld"

        stats = export_artifacts(
            self.test_dir,
            ExportFormat.JSONLD,
            output_file
        )

        self.assertEqual(stats['total_artifacts'], 3)
        self.assertEqual(stats['exported'], 3)
        self.assertEqual(stats['failed'], 0)
        self.assertTrue(output_file.exists())

        # Verify output structure
        with open(output_file, 'r') as f:
            data = json.load(f)

        self.assertIn('@context', data)
        self.assertIn('@graph', data)
        self.assertEqual(len(data['@graph']), 3)

    def test_export_jsonld_excludes_embeddings(self):
        """Test that embeddings are excluded by default."""
        output_file = self.output_path / "export_no_emb.jsonld"

        stats = export_artifacts(
            self.test_dir,
            ExportFormat.JSONLD,
            output_file,
            include_embeddings=False
        )

        with open(output_file, 'r') as f:
            data = json.load(f)

        # Verify no embeddings in output
        for artifact in data['@graph']:
            self.assertNotIn('novyx:embedding', artifact)
            self.assertNotIn('embedding', artifact)


class TestExportNeo4jCypher(unittest.TestCase):
    """Test Neo4j Cypher export functionality."""

    def setUp(self):
        """Create temporary directory with test artifacts."""
        self.temp_dir = tempfile.TemporaryDirectory()
        self.test_dir = Path(self.temp_dir.name)
        self.output_dir = tempfile.TemporaryDirectory()
        self.output_path = Path(self.output_dir.name)

        # Create test artifacts
        payload = {
            "title": "Neo4j Export Test",
            "context": "Testing Neo4j Cypher export",
            "options_considered": ["X", "Y"],
            "chosen_option": "X",
            "reasoning": "Test",
            "constraints": [],
            "assumptions": [],
            "confidence": 0.9,
            "expected_outcome": "Cypher generated",
            "category": "neo4j-test"
        }
        artifact = create_artifact("decision", payload)
        persist_artifact(artifact, self.test_dir)

    def tearDown(self):
        """Clean up temporary directories."""
        self.temp_dir.cleanup()
        self.output_dir.cleanup()

    def test_export_neo4j_cypher(self):
        """Test Neo4j Cypher export."""
        output_file = self.output_path / "export.cypher"

        stats = export_artifacts(
            self.test_dir,
            ExportFormat.NEO4J,
            output_file
        )

        self.assertEqual(stats['exported'], 1)
        self.assertIn('nodes', stats)
        self.assertTrue(output_file.exists())

        # Verify Cypher content
        with open(output_file, 'r') as f:
            content = f.read()

        self.assertIn('CREATE', content)
        self.assertIn(':Artifact', content)
        self.assertIn(':Decision', content)

    def test_export_cypher_constraints(self):
        """Test that Cypher includes constraint creation."""
        output_file = self.output_path / "export_constraints.cypher"

        export_to_neo4j_cypher(
            self.test_dir,
            output_file
        )

        with open(output_file, 'r') as f:
            content = f.read()

        self.assertIn('CREATE CONSTRAINT', content)
        self.assertIn('REQUIRE n.uuid IS UNIQUE', content)


class TestImportJsonLD(unittest.TestCase):
    """Test JSON-LD import functionality."""

    def setUp(self):
        """Create temporary directories."""
        self.source_dir = tempfile.TemporaryDirectory()
        self.import_dir = tempfile.TemporaryDirectory()
        self.source_path = Path(self.source_dir.name)
        self.import_path = Path(self.import_dir.name)

        # Create source artifacts
        for i in range(2):
            payload = {
                "title": f"Import Source {i}",
                "context": "Source for import test",
                "options_considered": ["P", "Q"],
                "chosen_option": "P",
                "reasoning": "Import test",
                "constraints": [],
                "assumptions": [],
                "confidence": 0.75,
                "expected_outcome": "Imported",
                "category": "import-test"
            }
            artifact = create_artifact("decision", payload)
            persist_artifact(artifact, self.source_path)

    def tearDown(self):
        """Clean up temporary directories."""
        self.source_dir.cleanup()
        self.import_dir.cleanup()

    def test_import_jsonld_basic(self):
        """Test basic JSON-LD import."""
        # First export
        export_file = self.source_path / "export.jsonld"
        export_artifacts(
            self.source_path,
            ExportFormat.JSONLD,
            export_file
        )

        # Then import
        stats = import_artifacts(
            export_file,
            ImportFormat.JSONLD,
            self.import_path
        )

        self.assertEqual(stats['imported'], 2)
        self.assertEqual(stats['failed'], 0)

        # Verify imported files exist
        imported_files = list(self.import_path.rglob("*.jsonld"))
        self.assertEqual(len(imported_files), 2)

    def test_import_regenerates_hashes(self):
        """Test that import regenerates integrity hashes by default."""
        # Export
        export_file = self.source_path / "export.jsonld"
        export_artifacts(
            self.source_path,
            ExportFormat.JSONLD,
            export_file
        )

        # Import with regenerate_hashes=True (default)
        import_artifacts(
            export_file,
            ImportFormat.JSONLD,
            self.import_path,
            regenerate_hashes=True
        )

        # Verify imported artifacts have hashes
        for path in self.import_path.rglob("*.jsonld"):
            with open(path, 'r') as f:
                artifact = json.load(f)
            self.assertIn('novyx:integrityHash', artifact)
            self.assertTrue(len(artifact['novyx:integrityHash']) == 64)


class TestRoundTrip(unittest.TestCase):
    """Test round-trip export/import integrity."""

    def setUp(self):
        """Create temporary directories."""
        self.source_dir = tempfile.TemporaryDirectory()
        self.export_dir = tempfile.TemporaryDirectory()
        self.import_dir = tempfile.TemporaryDirectory()
        self.source_path = Path(self.source_dir.name)
        self.export_path = Path(self.export_dir.name)
        self.import_path = Path(self.import_dir.name)

    def tearDown(self):
        """Clean up temporary directories."""
        self.source_dir.cleanup()
        self.export_dir.cleanup()
        self.import_dir.cleanup()

    def test_round_trip_jsonld(self):
        """Test JSON-LD export then import preserves data."""
        # Create original artifact
        payload = {
            "title": "Round Trip Test",
            "context": "Testing round-trip integrity",
            "options_considered": ["Alpha", "Beta", "Gamma"],
            "chosen_option": "Beta",
            "reasoning": "Best overall balance",
            "constraints": ["Time", "Budget"],
            "assumptions": ["Stable requirements"],
            "confidence": 0.92,
            "expected_outcome": "Preserved data",
            "category": "round-trip"
        }
        original = create_artifact("decision", payload)
        persist_artifact(original, self.source_path)

        # Export
        export_file = self.export_path / "roundtrip.jsonld"
        export_artifacts(
            self.source_path,
            ExportFormat.JSONLD,
            export_file
        )

        # Import
        import_artifacts(
            export_file,
            ImportFormat.JSONLD,
            self.import_path
        )

        # Verify imported data matches
        imported_files = list(self.import_path.rglob("*.jsonld"))
        self.assertEqual(len(imported_files), 1)

        with open(imported_files[0], 'r') as f:
            imported = json.load(f)

        # Core fields should match
        self.assertEqual(imported.get('title') or imported.get('name'), "Round Trip Test")
        self.assertEqual(imported['context'], "Testing round-trip integrity")
        self.assertEqual(imported['chosen_option'], "Beta")
        self.assertEqual(imported['confidence'], 0.92)


class TestMultiTenantExport(unittest.TestCase):
    """Test multi-tenant export functionality."""

    def setUp(self):
        """Create multi-tenant test structure."""
        self.temp_dir = tempfile.TemporaryDirectory()
        self.test_dir = Path(self.temp_dir.name)
        self.output_dir = tempfile.TemporaryDirectory()
        self.output_path = Path(self.output_dir.name)

        # Create artifacts for tenant-a
        for i in range(2):
            payload = {
                "title": f"Tenant A Decision {i}",
                "context": "Tenant A test",
                "options_considered": ["A"],
                "chosen_option": "A",
                "reasoning": "Tenant A",
                "constraints": [],
                "assumptions": [],
                "confidence": 0.9,
                "expected_outcome": "Pass",
                "category": "tenant-export"
            }
            artifact = create_artifact("decision", payload, tenant_id="tenant-a")
            persist_artifact(artifact, self.test_dir)

        # Create artifacts for tenant-b
        for i in range(3):
            payload = {
                "title": f"Tenant B Decision {i}",
                "context": "Tenant B test",
                "options_considered": ["B"],
                "chosen_option": "B",
                "reasoning": "Tenant B",
                "constraints": [],
                "assumptions": [],
                "confidence": 0.85,
                "expected_outcome": "Pass",
                "category": "tenant-export"
            }
            artifact = create_artifact("decision", payload, tenant_id="tenant-b")
            persist_artifact(artifact, self.test_dir)

    def tearDown(self):
        """Clean up temporary directories."""
        self.temp_dir.cleanup()
        self.output_dir.cleanup()

    def test_export_tenant_filter(self):
        """Test exporting specific tenant's artifacts."""
        output_file = self.output_path / "tenant_a.jsonld"

        stats = export_artifacts(
            self.test_dir,
            ExportFormat.JSONLD,
            output_file,
            tenant_id="tenant-a"
        )

        self.assertEqual(stats['exported'], 2)

        with open(output_file, 'r') as f:
            data = json.load(f)

        self.assertEqual(len(data['@graph']), 2)

    def test_export_all_tenants(self):
        """Test exporting all tenants when no filter specified."""
        output_file = self.output_path / "all_tenants.jsonld"

        stats = export_artifacts(
            self.test_dir,
            ExportFormat.JSONLD,
            output_file
        )

        self.assertEqual(stats['exported'], 5)


class TestUtilityFunctions(unittest.TestCase):
    """Test utility functions in export/import modules."""

    def test_get_artifact_id(self):
        """Test artifact ID extraction."""
        artifact = {"uuid": "urn:uuid:abc123"}
        self.assertEqual(get_artifact_id(artifact), "urn:uuid:abc123")

        artifact = {"@id": "urn:uuid:def456"}
        self.assertEqual(get_artifact_id(artifact), "urn:uuid:def456")

        artifact = {"Persistent_ID": "urn:uuid:ghi789"}
        self.assertEqual(get_artifact_id(artifact), "urn:uuid:ghi789")

    def test_get_artifact_types(self):
        """Test artifact type extraction."""
        artifact = {"@type": "Decision"}
        self.assertEqual(get_artifact_types(artifact), ["Decision"])

        artifact = {"@type": ["Decision", "DigitalDocument"]}
        self.assertEqual(get_artifact_types(artifact), ["Decision", "DigitalDocument"])

    def test_detect_artifact_type(self):
        """Test artifact type detection for import."""
        artifact = {"@type": "Decision"}
        self.assertEqual(_detect_artifact_type(artifact), "decision")

        # APIEndpoint type should be detected
        artifact = {"@type": ["novyx:APIEndpoint", "schema:Thing"]}
        self.assertEqual(_detect_artifact_type(artifact), "apiendpoint")

        # DigitalDocument maps to decision (fallback)
        artifact = {"@type": "schema:DigitalDocument"}
        self.assertEqual(_detect_artifact_type(artifact), "decision")

    def test_ensure_defaults_decision(self):
        """Test default values for decision import."""
        payload = {}
        _ensure_defaults(payload, "decision")

        self.assertIn("title", payload)
        self.assertIn("context", payload)
        self.assertIn("confidence", payload)
        self.assertEqual(payload["confidence"], 0.5)

    def test_schema_property_map(self):
        """Test CORE_SCHEMA property mappings are defined."""
        self.assertIn("uuid", SCHEMA_PROPERTY_MAP)
        self.assertIn("title", SCHEMA_PROPERTY_MAP)
        self.assertIn("context", SCHEMA_PROPERTY_MAP)
        self.assertIn("confidence", SCHEMA_PROPERTY_MAP)
        self.assertIn("sameAs", SCHEMA_PROPERTY_MAP)

    def test_type_map(self):
        """Test type mappings for RDF export."""
        self.assertIn("Decision", TYPE_MAP)
        self.assertIn("APIEndpoint", TYPE_MAP)
        self.assertIn("Tenant", TYPE_MAP)
        self.assertIn("Version", TYPE_MAP)


class TestDependencyChecks(unittest.TestCase):
    """Test dependency checking functions."""

    def test_check_export_dependencies_jsonld(self):
        """Test dependency check for JSON-LD (always available)."""
        available, msg = check_dependencies(ExportFormat.JSONLD)
        self.assertTrue(available)
        self.assertEqual(msg, "")

    def test_check_import_dependencies_jsonld(self):
        """Test import dependency check for JSON-LD."""
        available, msg = import_check_dependencies(ImportFormat.JSONLD)
        self.assertTrue(available)


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