"""
Unit tests for artifact models.

Tests Artifact, ArtifactMetadata, ArtifactReference, and content digest
calculation functions.
"""

import pytest
from datetime import datetime

from markitect.prompts.models import (
    Artifact,
    ArtifactMetadata,
    ArtifactReference,
    ArtifactType,
    calculate_content_digest,
    calculate_bundle_digest,
)


class TestArtifactMetadata:
    """Tests for ArtifactMetadata."""

    def test_create_empty_metadata(self):
        """Test creating metadata with defaults."""
        metadata = ArtifactMetadata()
        assert metadata.description is None
        assert metadata.tags == []
        assert metadata.author is None
        assert metadata.version is None
        assert metadata.custom == {}

    def test_create_metadata_with_values(self):
        """Test creating metadata with values."""
        metadata = ArtifactMetadata(
            description="Test artifact",
            tags=["test", "example"],
            author="test-author",
            version="1.0.0",
            custom={"key": "value"}
        )
        assert metadata.description == "Test artifact"
        assert metadata.tags == ["test", "example"]
        assert metadata.author == "test-author"
        assert metadata.version == "1.0.0"
        assert metadata.custom == {"key": "value"}

    def test_metadata_to_dict(self):
        """Test metadata serialization to dict."""
        metadata = ArtifactMetadata(
            description="Test",
            tags=["tag1"],
            author="author",
        )
        data = metadata.to_dict()
        assert data["description"] == "Test"
        assert data["tags"] == ["tag1"]
        assert data["author"] == "author"
        assert data["version"] is None
        assert data["custom"] == {}

    def test_metadata_from_dict(self):
        """Test metadata deserialization from dict."""
        data = {
            "description": "Test",
            "tags": ["tag1", "tag2"],
            "author": "author",
            "version": "2.0",
            "custom": {"extra": "data"}
        }
        metadata = ArtifactMetadata.from_dict(data)
        assert metadata.description == "Test"
        assert metadata.tags == ["tag1", "tag2"]
        assert metadata.author == "author"
        assert metadata.version == "2.0"
        assert metadata.custom == {"extra": "data"}


class TestArtifact:
    """Tests for Artifact model."""

    def test_create_artifact(self):
        """Test artifact creation with automatic digest calculation."""
        content = "# Test Content\n\nThis is a test."
        artifact = Artifact.create(
            space_id="space-1",
            name="test-artifact",
            content=content,
        )

        assert artifact.id  # UUID generated
        assert artifact.space_id == "space-1"
        assert artifact.name == "test-artifact"
        assert artifact.artifact_type == ArtifactType.CONTENT
        assert artifact.content_digest == calculate_content_digest(content)
        assert artifact.content_size == len(content.encode('utf-8'))
        assert isinstance(artifact.metadata, ArtifactMetadata)
        assert isinstance(artifact.created_at, datetime)
        assert isinstance(artifact.updated_at, datetime)

    def test_create_artifact_with_type(self):
        """Test creating artifact with specific type."""
        artifact = Artifact.create(
            space_id="space-1",
            name="my-template",
            content="Template content",
            artifact_type=ArtifactType.TEMPLATE,
        )
        assert artifact.artifact_type == ArtifactType.TEMPLATE

    def test_create_artifact_with_metadata(self):
        """Test creating artifact with metadata."""
        metadata = ArtifactMetadata(description="Test", tags=["tag1"])
        artifact = Artifact.create(
            space_id="space-1",
            name="test",
            content="content",
            metadata=metadata,
        )
        assert artifact.metadata.description == "Test"
        assert artifact.metadata.tags == ["tag1"]

    def test_update_content(self):
        """Test updating artifact content."""
        artifact = Artifact.create(
            space_id="space-1",
            name="test",
            content="original content",
        )
        original_digest = artifact.content_digest
        original_updated_at = artifact.updated_at

        # Small delay to ensure timestamp difference
        import time
        time.sleep(0.01)

        new_content = "updated content"
        artifact.update_content(new_content)

        assert artifact.content_digest != original_digest
        assert artifact.content_digest == calculate_content_digest(new_content)
        assert artifact.content_size == len(new_content.encode('utf-8'))
        assert artifact.updated_at > original_updated_at

    def test_has_changed(self):
        """Test change detection."""
        content = "original"
        artifact = Artifact.create(
            space_id="space-1",
            name="test",
            content=content,
        )

        # Same content - no change
        assert not artifact.has_changed(calculate_content_digest(content))

        # Different content - changed
        assert artifact.has_changed(calculate_content_digest("different"))

    def test_artifact_to_dict(self):
        """Test artifact serialization."""
        artifact = Artifact.create(
            space_id="space-1",
            name="test",
            content="content",
            artifact_type=ArtifactType.TEMPLATE,
        )
        data = artifact.to_dict()

        assert data["id"] == artifact.id
        assert data["space_id"] == "space-1"
        assert data["name"] == "test"
        assert data["artifact_type"] == "template"
        assert data["content_digest"] == artifact.content_digest
        assert data["content_size"] == artifact.content_size
        assert "metadata" in data
        assert "created_at" in data
        assert "updated_at" in data

    def test_artifact_from_dict(self):
        """Test artifact deserialization."""
        original = Artifact.create(
            space_id="space-1",
            name="test",
            content="content",
        )
        data = original.to_dict()
        restored = Artifact.from_dict(data)

        assert restored.id == original.id
        assert restored.space_id == original.space_id
        assert restored.name == original.name
        assert restored.artifact_type == original.artifact_type
        assert restored.content_digest == original.content_digest
        assert restored.content_size == original.content_size


class TestArtifactReference:
    """Tests for ArtifactReference."""

    def test_simple_reference(self):
        """Test simple artifact name reference."""
        ref = ArtifactReference(name="my-artifact")
        assert ref.name == "my-artifact"
        assert ref.space_id is None
        assert ref.version is None
        assert str(ref) == "my-artifact"

    def test_reference_with_space(self):
        """Test reference with space ID."""
        ref = ArtifactReference(name="artifact", space_id="space-1")
        assert ref.name == "artifact"
        assert ref.space_id == "space-1"
        assert str(ref) == "space-1:artifact"

    def test_reference_with_version(self):
        """Test reference with version."""
        ref = ArtifactReference(name="artifact", version="1.0.0")
        assert ref.name == "artifact"
        assert ref.version == "1.0.0"
        assert str(ref) == "artifact@1.0.0"

    def test_reference_with_space_and_version(self):
        """Test fully qualified reference."""
        ref = ArtifactReference(
            name="artifact",
            space_id="space-1",
            version="2.0.0"
        )
        assert str(ref) == "space-1:artifact@2.0.0"

    def test_parse_simple_reference(self):
        """Test parsing simple reference."""
        ref = ArtifactReference.parse("my-artifact")
        assert ref.name == "my-artifact"
        assert ref.space_id is None
        assert ref.version is None

    def test_parse_space_reference(self):
        """Test parsing space:artifact reference."""
        ref = ArtifactReference.parse("space-1:my-artifact")
        assert ref.name == "my-artifact"
        assert ref.space_id == "space-1"
        assert ref.version is None

    def test_parse_version_reference(self):
        """Test parsing artifact@version reference."""
        ref = ArtifactReference.parse("my-artifact@1.0.0")
        assert ref.name == "my-artifact"
        assert ref.space_id is None
        assert ref.version == "1.0.0"

    def test_parse_full_reference(self):
        """Test parsing space:artifact@version reference."""
        ref = ArtifactReference.parse("space-1:my-artifact@2.0.0")
        assert ref.name == "my-artifact"
        assert ref.space_id == "space-1"
        assert ref.version == "2.0.0"

    def test_parse_invalid_reference(self):
        """Test parsing invalid reference raises error."""
        with pytest.raises(ValueError, match="Invalid artifact reference"):
            ArtifactReference.parse("space-1:")

        with pytest.raises(ValueError, match="Invalid artifact reference"):
            ArtifactReference.parse("@1.0.0")


class TestContentDigest:
    """Tests for content digest functions."""

    def test_calculate_content_digest(self):
        """Test SHA-256 digest calculation."""
        content = "Hello, World!"
        digest = calculate_content_digest(content)
        # SHA-256 produces 64 hex characters
        assert len(digest) == 64
        assert digest.isalnum()

    def test_digest_consistency(self):
        """Test digest is consistent for same content."""
        content = "Test content"
        digest1 = calculate_content_digest(content)
        digest2 = calculate_content_digest(content)
        assert digest1 == digest2

    def test_digest_changes_with_content(self):
        """Test digest changes when content changes."""
        digest1 = calculate_content_digest("content 1")
        digest2 = calculate_content_digest("content 2")
        assert digest1 != digest2

    def test_calculate_bundle_digest(self):
        """Test bundle digest calculation."""
        components = {
            "template": "abc123",
            "dep1": "def456",
            "dep2": "ghi789",
        }
        digest = calculate_bundle_digest(components)
        assert len(digest) == 64

    def test_bundle_digest_order_independent(self):
        """Test bundle digest is deterministic regardless of dict order."""
        components1 = {"a": "1", "b": "2", "c": "3"}
        components2 = {"c": "3", "a": "1", "b": "2"}
        assert calculate_bundle_digest(components1) == calculate_bundle_digest(components2)

    def test_bundle_digest_sensitive_to_values(self):
        """Test bundle digest changes when values change."""
        digest1 = calculate_bundle_digest({"a": "1", "b": "2"})
        digest2 = calculate_bundle_digest({"a": "1", "b": "3"})
        assert digest1 != digest2
