#!/usr/bin/env python3
"""
Tests for Federation Module (Phase 14)
======================================
Tests for distributed artifact synchronization.
"""

# Module-level skip if pytest not available
import unittest
try:
    import pytest
except ImportError:
    raise unittest.SkipTest("pytest not available - skipping federation tests")

import json
import tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock

# Import federation module
from tools.federation import (
    create_remote_ref,
    update_remote_ref_status,
    get_remote_refs,
    federation_status,
    sync_ref,
    sync_refs,
    _detect_artifact_type,
    _build_artifact_url,
    FederationError,
    ConflictError,
    REMOTE_REF_DIR
)
from tools.factory import create_remote_ref, persist_artifact, generate_uuid


class TestRemoteRefCreation:
    """Test RemoteRef artifact creation."""

    def test_create_remote_ref_basic(self):
        """Test basic RemoteRef creation."""
        ref = create_remote_ref(
            remote_url="https://remote.novyx.ai/api/artifacts/urn:uuid:test-123",
            remote_artifact_id="urn:uuid:test-123",
            remote_instance="https://remote.novyx.ai"
        )

        assert ref["@type"] == ["RemoteRef", "DigitalDocument"]
        assert ref["remote_url"] == "https://remote.novyx.ai/api/artifacts/urn:uuid:test-123"
        assert ref["remote_artifact_id"] == "urn:uuid:test-123"
        assert ref["remote_instance"] == "https://remote.novyx.ai"
        assert ref["sync_status"] == "pending"
        assert ref["direction"] == "pull"

    def test_create_remote_ref_with_all_options(self):
        """Test RemoteRef creation with all options."""
        ref = create_remote_ref(
            remote_url="https://remote.novyx.ai/api/artifacts/urn:uuid:test-456",
            remote_artifact_id="urn:uuid:test-456",
            remote_instance="https://remote.novyx.ai",
            sync_status="synced",
            direction="bidirectional",
            local_artifact_id="urn:uuid:local-456",
            remote_hash="abc123def456",
            auto_sync=True,
            conflict_resolution="local_wins",
            metadata={"name": "Test Artifact", "type": "Decision"},
            tenant_id="acme-corp"
        )

        assert ref["sync_status"] == "synced"
        assert ref["direction"] == "bidirectional"
        assert ref["local_artifact_id"] == "urn:uuid:local-456"
        assert ref["remote_hash"] == "abc123def456"
        assert ref["auto_sync"] is True
        assert ref["conflict_resolution"] == "local_wins"
        assert ref["metadata"]["name"] == "Test Artifact"
        assert ref.get("novyx:tenantId") == "acme-corp"
        # synced status should set last_synced_at
        assert "last_synced_at" in ref

    def test_create_remote_ref_generates_uuid(self):
        """Test that RemoteRef gets a unique UUID."""
        ref1 = create_remote_ref(
            remote_url="https://test.ai/api/1",
            remote_artifact_id="urn:uuid:r1",
            remote_instance="https://test.ai"
        )
        ref2 = create_remote_ref(
            remote_url="https://test.ai/api/2",
            remote_artifact_id="urn:uuid:r2",
            remote_instance="https://test.ai"
        )

        assert ref1["uuid"] != ref2["uuid"]
        assert ref1["uuid"].startswith("urn:uuid:")


class TestRemoteRefStatusUpdate:
    """Test RemoteRef status updates."""

    def test_update_status_to_synced(self):
        """Test updating status to synced."""
        ref = create_remote_ref(
            remote_url="https://test.ai/api/1",
            remote_artifact_id="urn:uuid:test",
            remote_instance="https://test.ai",
            sync_status="pending"
        )

        updated = update_remote_ref_status(
            ref, "synced",
            local_artifact_id="urn:uuid:local",
            remote_hash="newhashabc"
        )

        assert updated["sync_status"] == "synced"
        assert updated["local_artifact_id"] == "urn:uuid:local"
        assert updated["remote_hash"] == "newhashabc"
        assert "last_synced_at" in updated
        # Hash should be invalidated for recalculation
        assert updated["novyx:integrityHash"] == "pending_calculation"

    def test_update_status_to_conflict(self):
        """Test updating status to conflict."""
        ref = create_remote_ref(
            remote_url="https://test.ai/api/1",
            remote_artifact_id="urn:uuid:test",
            remote_instance="https://test.ai",
            sync_status="synced"
        )

        updated = update_remote_ref_status(ref, "conflict")

        assert updated["sync_status"] == "conflict"
        # conflict shouldn't set last_synced_at
        assert "last_synced_at" not in updated or updated.get("last_synced_at") is None


class TestFederationStatus:
    """Test federation status retrieval."""

    def test_federation_status_returns_counts(self):
        """Test that federation_status returns status counts."""
        status = federation_status()

        assert "total" in status
        assert "by_status" in status
        assert "by_direction" in status
        assert "remote_instances" in status
        assert "requests_available" in status

        # Check status keys exist
        assert "synced" in status["by_status"]
        assert "pending" in status["by_status"]
        assert "conflict" in status["by_status"]
        assert "stale" in status["by_status"]

        # Check direction keys exist
        assert "pull" in status["by_direction"]
        assert "push" in status["by_direction"]
        assert "bidirectional" in status["by_direction"]


class TestGetRemoteRefs:
    """Test RemoteRef retrieval with filters."""

    def test_get_all_refs(self):
        """Test getting all RemoteRefs."""
        refs = get_remote_refs()
        # Should return list (may be empty or have test data)
        assert isinstance(refs, list)

    def test_get_refs_by_status(self):
        """Test filtering refs by status."""
        synced_refs = get_remote_refs(status="synced")
        for ref in synced_refs:
            assert ref.get("sync_status") == "synced"

    def test_get_refs_by_direction(self):
        """Test filtering refs by direction."""
        pull_refs = get_remote_refs(direction="pull")
        for ref in pull_refs:
            assert ref.get("direction") == "pull"


class TestArtifactTypeDetection:
    """Test artifact type detection."""

    def test_detect_decision(self):
        """Test detecting Decision type."""
        artifact = {"@type": ["Decision", "DigitalDocument"]}
        assert _detect_artifact_type(artifact) == "decision"

    def test_detect_apiendpoint(self):
        """Test detecting APIEndpoint type."""
        artifact = {"@type": ["APIEndpoint", "Thing"]}
        assert _detect_artifact_type(artifact) == "apiendpoint"

    def test_detect_with_prefix(self):
        """Test detecting type with novyx: prefix."""
        artifact = {"@type": ["novyx:GraphQLOperation"]}
        assert _detect_artifact_type(artifact) == "graphqloperation"

    def test_detect_unknown_defaults_to_decision(self):
        """Test unknown type defaults to decision."""
        artifact = {"@type": ["UnknownType"]}
        assert _detect_artifact_type(artifact) == "decision"


class TestBuildArtifactUrl:
    """Test URL building for remote artifacts."""

    def test_build_url_basic(self):
        """Test basic URL building."""
        url = _build_artifact_url(
            "https://remote.novyx.ai",
            "urn:uuid:test-123"
        )
        assert url == "https://remote.novyx.ai/api/artifacts/urn:uuid:test-123"

    def test_build_url_strips_trailing_slash(self):
        """Test URL building strips trailing slash."""
        url = _build_artifact_url(
            "https://remote.novyx.ai/",
            "urn:uuid:test-123"
        )
        assert url == "https://remote.novyx.ai/api/artifacts/urn:uuid:test-123"


class TestConflictResolution:
    """Test conflict detection and resolution."""

    def test_conflict_error_contains_hashes(self):
        """Test ConflictError contains both hashes."""
        error = ConflictError(
            "Test conflict",
            local_hash="localhash123",
            remote_hash="remotehash456"
        )

        assert error.local_hash == "localhash123"
        assert error.remote_hash == "remotehash456"
        assert "Test conflict" in str(error)


class TestSyncRefs:
    """Test sync operations."""

    def test_sync_refs_returns_results_dict(self):
        """Test sync_refs returns proper results structure."""
        # This tests the structure without actual network calls
        results = sync_refs(status="nonexistent_status")

        assert "synced" in results
        assert "failed" in results
        assert "conflicts" in results
        assert "errors" in results
        assert isinstance(results["errors"], list)

    def test_sync_refs_filters_by_status(self):
        """Test sync_refs respects status filter."""
        # Sync only 'pending' refs - should not error
        results = sync_refs(status="pending")
        assert isinstance(results, dict)


class TestFederationErrors:
    """Test federation error handling."""

    def test_federation_error_base(self):
        """Test base FederationError."""
        error = FederationError("Test error")
        assert str(error) == "Test error"

    def test_conflict_error_inheritance(self):
        """Test ConflictError inherits from FederationError."""
        error = ConflictError("Conflict", "hash1", "hash2")
        assert isinstance(error, FederationError)


# Run tests
if __name__ == "__main__":
    pytest.main([__file__, "-v"])
