"""Test for the Fingerprint class."""

import json
import uuid
from datetime import datetime, timedelta

import pytest
from pydantic import ValidationError

from crewai.security import Fingerprint


def test_fingerprint_creation_with_defaults():
    """Test creating a Fingerprint with default values."""
    fingerprint = Fingerprint()

    # Check that a UUID was generated
    assert fingerprint.uuid_str is not None
    # Check that it's a valid UUID
    uuid_obj = uuid.UUID(fingerprint.uuid_str)
    assert isinstance(uuid_obj, uuid.UUID)

    # Check that creation time was set
    assert isinstance(fingerprint.created_at, datetime)

    # Check that metadata is an empty dict
    assert fingerprint.metadata == {}


def test_fingerprint_creation_with_metadata():
    """Test creating a Fingerprint with custom metadata only."""
    metadata = {"version": "1.0", "author": "Test Author"}

    fingerprint = Fingerprint(metadata=metadata)

    # UUID and created_at should be auto-generated
    assert fingerprint.uuid_str is not None
    assert isinstance(fingerprint.created_at, datetime)
    # Only metadata should be settable
    assert fingerprint.metadata == metadata


def test_fingerprint_uuid_cannot_be_set():
    """Test that uuid_str cannot be manually set."""
    original_uuid = "b723c6ff-95de-5e87-860b-467b72282bd8"

    # Attempt to set uuid_str
    fingerprint = Fingerprint(uuid_str=original_uuid)

    # UUID should be generated, not set to our value
    assert fingerprint.uuid_str != original_uuid
    assert uuid.UUID(fingerprint.uuid_str)  # Should be a valid UUID


def test_fingerprint_created_at_cannot_be_set():
    """Test that created_at cannot be manually set."""
    original_time = datetime.now() - timedelta(days=1)

    # Attempt to set created_at
    fingerprint = Fingerprint(created_at=original_time)

    # created_at should be auto-generated, not set to our value
    assert fingerprint.created_at != original_time
    assert fingerprint.created_at > original_time  # Should be more recent


def test_fingerprint_uuid_property():
    """Test the uuid property returns a UUID object."""
    fingerprint = Fingerprint()

    assert isinstance(fingerprint.uuid, uuid.UUID)
    assert str(fingerprint.uuid) == fingerprint.uuid_str


def test_fingerprint_deterministic_generation():
    """Test that the same seed string always generates the same fingerprint using generate method."""
    seed = "test-seed"

    # Use the generate method which supports deterministic generation
    fingerprint1 = Fingerprint.generate(seed)
    fingerprint2 = Fingerprint.generate(seed)

    assert fingerprint1.uuid_str == fingerprint2.uuid_str

    # Also test with _generate_uuid method directly
    uuid_str1 = Fingerprint._generate_uuid(seed)
    uuid_str2 = Fingerprint._generate_uuid(seed)
    assert uuid_str1 == uuid_str2


def test_fingerprint_generate_classmethod():
    """Test the generate class method."""
    # Without seed
    fingerprint1 = Fingerprint.generate()
    assert isinstance(fingerprint1, Fingerprint)

    # With seed
    seed = "test-seed"
    metadata = {"version": "1.0"}
    fingerprint2 = Fingerprint.generate(seed, metadata)

    assert isinstance(fingerprint2, Fingerprint)
    assert fingerprint2.metadata == metadata

    # Same seed should generate same UUID
    fingerprint3 = Fingerprint.generate(seed)
    assert fingerprint2.uuid_str == fingerprint3.uuid_str


def test_fingerprint_string_representation():
    """Test the string representation of Fingerprint."""
    fingerprint = Fingerprint()
    uuid_str = fingerprint.uuid_str

    string_repr = str(fingerprint)
    assert uuid_str in string_repr


def test_fingerprint_equality():
    """Test fingerprint equality comparison."""
    # Using generate with the same seed to get consistent UUIDs
    seed = "test-equality"

    fingerprint1 = Fingerprint.generate(seed)
    fingerprint2 = Fingerprint.generate(seed)
    fingerprint3 = Fingerprint()

    assert fingerprint1 == fingerprint2
    assert fingerprint1 != fingerprint3


def test_fingerprint_hash():
    """Test that fingerprints can be used as dictionary keys."""
    # Using generate with the same seed to get consistent UUIDs
    seed = "test-hash"

    fingerprint1 = Fingerprint.generate(seed)
    fingerprint2 = Fingerprint.generate(seed)

    # Hash should be consistent for same UUID
    assert hash(fingerprint1) == hash(fingerprint2)

    # Can be used as dict keys
    fingerprint_dict = {fingerprint1: "value"}
    assert fingerprint_dict[fingerprint2] == "value"


def test_fingerprint_to_dict():
    """Test converting fingerprint to dictionary."""
    metadata = {"version": "1.0"}
    fingerprint = Fingerprint(metadata=metadata)

    uuid_str = fingerprint.uuid_str
    created_at = fingerprint.created_at

    fingerprint_dict = fingerprint.to_dict()

    assert fingerprint_dict["uuid_str"] == uuid_str
    assert fingerprint_dict["created_at"] == created_at.isoformat()
    assert fingerprint_dict["metadata"] == metadata


def test_fingerprint_from_dict():
    """Test creating fingerprint from dictionary."""
    uuid_str = "b723c6ff-95de-5e87-860b-467b72282bd8"
    created_at = datetime.now()
    created_at_iso = created_at.isoformat()
    metadata = {"version": "1.0"}

    fingerprint_dict = {
        "uuid_str": uuid_str,
        "created_at": created_at_iso,
        "metadata": metadata
    }

    fingerprint = Fingerprint.from_dict(fingerprint_dict)

    assert fingerprint.uuid_str == uuid_str
    assert fingerprint.created_at.isoformat() == created_at_iso
    assert fingerprint.metadata == metadata


def test_fingerprint_json_serialization():
    """Test that Fingerprint can be JSON serialized and deserialized."""
    # Create a fingerprint, get its values
    metadata = {"version": "1.0"}
    fingerprint = Fingerprint(metadata=metadata)

    uuid_str = fingerprint.uuid_str
    created_at = fingerprint.created_at

    # Convert to dict and then JSON
    fingerprint_dict = fingerprint.to_dict()
    json_str = json.dumps(fingerprint_dict)

    # Parse JSON and create new fingerprint
    parsed_dict = json.loads(json_str)
    new_fingerprint = Fingerprint.from_dict(parsed_dict)

    assert new_fingerprint.uuid_str == uuid_str
    assert new_fingerprint.created_at.isoformat() == created_at.isoformat()
    assert new_fingerprint.metadata == metadata


def test_invalid_uuid_str():
    """Test handling of invalid UUID strings."""
    uuid_str = "not-a-valid-uuid"
    created_at = datetime.now().isoformat()

    fingerprint_dict = {
        "uuid_str": uuid_str,
        "created_at": created_at,
        "metadata": {}
    }

    # The Fingerprint.from_dict method accepts even invalid UUIDs
    # This seems to be the current behavior
    fingerprint = Fingerprint.from_dict(fingerprint_dict)

    # Verify it uses the provided UUID string, even if invalid
    # This might not be ideal behavior, but it's the current implementation
    assert fingerprint.uuid_str == uuid_str

    # But this will raise an exception when we try to access the uuid property
    with pytest.raises(ValueError):
        uuid_obj = fingerprint.uuid


def test_fingerprint_metadata_mutation():
    """Test that metadata can be modified after fingerprint creation."""
    # Create a fingerprint with initial metadata
    initial_metadata = {"version": "1.0", "status": "draft"}
    fingerprint = Fingerprint(metadata=initial_metadata)

    # Verify initial metadata
    assert fingerprint.metadata == initial_metadata

    # Modify the metadata
    fingerprint.metadata["status"] = "published"
    fingerprint.metadata["author"] = "Test Author"

    # Verify the modifications
    expected_metadata = {
        "version": "1.0",
        "status": "published",
        "author": "Test Author"
    }
    assert fingerprint.metadata == expected_metadata

    # Make sure the UUID and creation time remain unchanged
    uuid_str = fingerprint.uuid_str
    created_at = fingerprint.created_at

    # Completely replace the metadata
    new_metadata = {"version": "2.0", "environment": "production"}
    fingerprint.metadata = new_metadata

    # Verify the replacement
    assert fingerprint.metadata == new_metadata

    # Ensure immutable fields remain unchanged
    assert fingerprint.uuid_str == uuid_str
    assert fingerprint.created_at == created_at