package de.gecheckt.pdf.umbenenner.domain.model;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;

/**
 * Unit tests for {@link RunId} value object.
 * <p>
 * Verifies immutability, value semantics, validation, and correct behavior
 * as a unique identifier for batch runs.
 */
class RunIdTest {

    @Test
    void constructor_createsRunIdWithValidValue() {
        RunId runId = new RunId("test-run-123");
        assertNotNull(runId);
        assertEquals("test-run-123", runId.value());
    }

    @Test
    void constructor_throwsNullPointerExceptionWhenValueIsNull() {
        assertThrows(NullPointerException.class, () -> new RunId(null),
                "Constructor should throw NullPointerException for null value");
    }

    @Test
    void constructor_throwsIllegalArgumentExceptionWhenValueIsEmpty() {
        assertThrows(IllegalArgumentException.class, () -> new RunId(""),
                "Constructor should throw IllegalArgumentException for empty value");
    }

    @Test
    void constructor_throwsIllegalArgumentExceptionWhenValueIsOnlyWhitespace() {
        assertThrows(IllegalArgumentException.class, () -> new RunId("   "),
                "Constructor should throw IllegalArgumentException for whitespace-only value");
    }

    @Test
    void value_returnsTheConstructorValue() {
        String expectedValue = "run-id-456";
        RunId runId = new RunId(expectedValue);
        assertEquals(expectedValue, runId.value());
    }

    @Test
    void equals_returnsTrueForIdenticalValues() {
        RunId runId1 = new RunId("same-id");
        RunId runId2 = new RunId("same-id");
        assertEquals(runId1, runId2);
    }

    @Test
    void equals_returnsFalseForDifferentValues() {
        RunId runId1 = new RunId("id-1");
        RunId runId2 = new RunId("id-2");
        assertNotEquals(runId1, runId2);
    }

    @Test
    void equals_returnsFalseWhenComparedWithNull() {
        RunId runId = new RunId("test-id");
        assertNotEquals(null, runId);
    }

    @Test
    void equals_returnsFalseWhenComparedWithDifferentType() {
        RunId runId = new RunId("test-id");
        assertNotEquals("test-id", runId);
    }

    @Test
    void equals_returnsFalseWhenComparedWithDifferentClass() {
        RunId runId = new RunId("test-id");
        Object other = new Object();
        assertNotEquals(runId, other);
    }

    @Test
    void hashCode_isSameForIdenticalValues() {
        RunId runId1 = new RunId("same-id");
        RunId runId2 = new RunId("same-id");
        assertEquals(runId1.hashCode(), runId2.hashCode());
    }

    @Test
    void hashCode_isDifferentForDifferentValues() {
        RunId runId1 = new RunId("id-1");
        RunId runId2 = new RunId("id-2");
        // Note: this is not guaranteed for different values, but likely
        assertNotEquals(runId1.hashCode(), runId2.hashCode());
    }

    @Test
    void toString_returnsTheValue() {
        String value = "run-id-789";
        RunId runId = new RunId(value);
        assertEquals(value, runId.toString());
    }

    @Test
    void runIdCanBeUsedInCollections() {
        RunId runId1 = new RunId("id-1");
        RunId runId2 = new RunId("id-1");
        RunId runId3 = new RunId("id-2");

        var set = new java.util.HashSet<RunId>();
        set.add(runId1);
        set.add(runId2); // Should not add duplicate
        set.add(runId3);

        // runId1 and runId2 are equal, so set should have 2 elements
        assertEquals(2, set.size());
    }

    @Test
    void runIdCanBeMapKey() {
        RunId runId1 = new RunId("id-1");
        RunId runId2 = new RunId("id-1");

        var map = new java.util.HashMap<RunId, String>();
        map.put(runId1, "first");
        map.put(runId2, "second");

        // runId1 and runId2 are equal, so second put should overwrite
        assertEquals(1, map.size());
        assertEquals("second", map.get(runId1));
    }

    @Test
    void constructor_acceptsUuidFormatValue() {
        String uuidValue = "550e8400-e29b-41d4-a716-446655440000";
        RunId runId = new RunId(uuidValue);
        assertEquals(uuidValue, runId.value());
    }

    @Test
    void constructor_acceptsTimestampFormatValue() {
        String timestampValue = "2026-03-31T21:41:48.000Z";
        RunId runId = new RunId(timestampValue);
        assertEquals(timestampValue, runId.value());
    }

    @Test
    void constructor_acceptsSequentialNumberValue() {
        String sequentialValue = "00001";
        RunId runId = new RunId(sequentialValue);
        assertEquals(sequentialValue, runId.value());
    }
}
