import json

import pytest

from mcard.config.config_constants import (
    COLLISION_TIME,
    CONTENT_SIZE,
    DUPLICATE_EVENT_TYPE,
    FIRST_G_TIME,
    HASH,
    TYPE,
    UPGRADED_FUNCTION,
)
from mcard.model.card import MCard
from mcard.model.event_producer import (
    compute_content_size,
    generate_collision_event,
    generate_duplication_event,
    next_hash_function,
)
from mcard.model.g_time import GTime
from mcard.model.hash.enums import HashAlgorithm


def test_next_hash_function_standard_case():
    """Test next_hash_function with standard transitions in the hierarchy"""
    # Test transitions between algorithms
    assert next_hash_function(HashAlgorithm.MD5.value) == HashAlgorithm.SHA1.value
    assert next_hash_function(HashAlgorithm.SHA1.value) == HashAlgorithm.SHA256.value
    assert next_hash_function(HashAlgorithm.SHA256.value) == HashAlgorithm.SHA512.value
    assert (
        next_hash_function(HashAlgorithm.SHA512.value) == HashAlgorithm.SHA1.value
    )  # Wraps around
    assert (
        next_hash_function(HashAlgorithm.CUSTOM.value) == HashAlgorithm.SHA1.value
    )  # Custom goes to default


def test_next_hash_function_invalid_input():
    """Test next_hash_function with invalid input"""
    AN_INVALID_HASH_FUNCTION_NAME = "invalid_hash_function_name"
    assert next_hash_function(AN_INVALID_HASH_FUNCTION_NAME) == HashAlgorithm.SHA1.value


@pytest.mark.parametrize(
    "content,expected_size",
    [
        (b"test content", 12),  # 11 characters + 1 null terminator
        (b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n", 15),  # 14 characters + 1 null terminator
        ("", 0),
    ],
)
def test_compute_content_size(content, expected_size):
    if isinstance(content, (str, bytes)) and len(content) == 0:
        with pytest.raises(ValueError, match="Content cannot be empty"):
            compute_content_size(MCard(content))
    else:
        card = MCard(content)
        assert compute_content_size(card) == expected_size


def test_generate_duplication_event():
    # Create a test card with known content
    test_content = b"test content"
    test_card = MCard(test_content)

    # Generate duplication event
    event_card = MCard(generate_duplication_event(test_card))

    # Parse the event data
    event_data = json.loads(event_card.content)

    # Verify event structure and content
    assert event_data[TYPE] == DUPLICATE_EVENT_TYPE
    assert event_data[HASH] == test_card.hash


def test_generate_duplication_event_with_binary():
    # Create a test card with binary content
    binary_content = b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n"
    test_card = MCard(binary_content)

    # Generate duplication event
    event_card = MCard(generate_duplication_event(test_card))

    # Parse the event data
    event_data = json.loads(event_card.content)

    # Verify event structure and content
    assert event_data[TYPE] == DUPLICATE_EVENT_TYPE
    assert event_data[HASH] == test_card.hash


def test_generate_collision_event():
    # Create a test card
    test_content = b"test content"
    test_card = MCard(test_content)
    current_hash_function = GTime.get_hash_function(test_card.g_time)

    # Generate collision event
    event_str = generate_collision_event(test_card)

    # Parse the event data
    event_data = json.loads(event_str)

    # Verify event structure
    assert event_data[HASH] == test_card.hash
    # Compare only hash function and timezone parts
    assert (
        event_data[FIRST_G_TIME].split("|")[0] == test_card.g_time.split("|")[0]
    )  # hash function
    assert (
        event_data[FIRST_G_TIME].split("|")[2] == test_card.g_time.split("|")[2]
    )  # timezone
    assert COLLISION_TIME in event_data
    assert event_data[UPGRADED_FUNCTION] == next_hash_function(current_hash_function)
    assert event_data[CONTENT_SIZE] == len(test_content)

    # Check that the collision time is in the expected format
    assert isinstance(event_data[COLLISION_TIME], str)
    assert len(event_data[COLLISION_TIME]) > 0
    assert "|" in event_data[COLLISION_TIME]


def test_generate_collision_event_with_binary():
    # Create a test card with binary content
    binary_content = b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n"
    test_card = MCard(binary_content)
    current_hash_function = GTime.get_hash_function(test_card.g_time)

    # Generate collision event
    event_str = generate_collision_event(test_card)

    # Parse the event data
    event_data = json.loads(event_str)

    # Verify event structure
    assert event_data[HASH] == test_card.hash
    # Compare only hash function and timezone parts
    assert (
        event_data[FIRST_G_TIME].split("|")[0] == test_card.g_time.split("|")[0]
    )  # hash function
    assert (
        event_data[FIRST_G_TIME].split("|")[2] == test_card.g_time.split("|")[2]
    )  # timezone
    assert COLLISION_TIME in event_data
    assert event_data[UPGRADED_FUNCTION] == next_hash_function(current_hash_function)
    assert event_data[CONTENT_SIZE] == len(binary_content)

    # Check that the collision time is in the expected format
    assert isinstance(event_data[COLLISION_TIME], str)
    assert len(event_data[COLLISION_TIME]) > 0
    assert "|" in event_data[COLLISION_TIME]
    assert event_data[COLLISION_TIME].count("|") == 2
