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 static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

/**
 * Unit tests for {@link DocumentFingerprint} value object.
 * <p>
 * Verifies immutability, value semantics, validation, and correct behavior
 * as the stable identity for a document based on its content hash.
 */
class DocumentFingerprintTest {

    private static final String VALID_SHA256 = "0000000000000000000000000000000000000000000000000000000000000000";
    private static final String VALID_SHA256_2 = "a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3";
    private static final String VALID_SHA256_3 = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9";

    @Test
    void constructor_createsDocumentFingerprintWithValidSha256() {
        DocumentFingerprint fingerprint = new DocumentFingerprint(VALID_SHA256);
        assertNotNull(fingerprint);
        assertEquals(VALID_SHA256, fingerprint.sha256Hex());
    }

    @Test
    void constructor_throwsNullPointerExceptionWhenSha256IsNull() {
        assertThrows(NullPointerException.class, () -> new DocumentFingerprint(null),
                "Constructor should throw NullPointerException for null sha256Hex");
    }

    @Test
    void constructor_throwsIllegalArgumentExceptionWhenSha256IsTooShort() {
        String tooShort = "356a192b7913b04c54574d18c28d46e6395428a"; // 39 chars
        assertThrows(IllegalArgumentException.class, () -> new DocumentFingerprint(tooShort),
                "Constructor should throw IllegalArgumentException for sha256Hex that is too short");
    }

    @Test
    void constructor_throwsIllegalArgumentExceptionWhenSha256IsTooLong() {
        String tooLong = "356a192b7913b04c54574d18c28d46e6395428abXX"; // 66 chars
        assertThrows(IllegalArgumentException.class, () -> new DocumentFingerprint(tooLong),
                "Constructor should throw IllegalArgumentException for sha256Hex that is too long");
    }

    @Test
    void constructor_throwsIllegalArgumentExceptionWhenSha256ContainsInvalidCharacters() {
        String invalid = "356a192b7913b04c54574d18c28d46e6395428ab" + "ZZZZZZZZZZZZZZZZZZZZZZ"; // Has uppercase letters
        assertThrows(IllegalArgumentException.class, () -> new DocumentFingerprint(invalid),
                "Constructor should throw IllegalArgumentException for sha256Hex with non-hex characters");
    }

    @Test
    void constructor_throwsIllegalArgumentExceptionWhenSha256HasUppercaseLetters() {
        String uppercase = "356A192B7913B04C54574D18C28D46E6395428AB";
        assertThrows(IllegalArgumentException.class, () -> new DocumentFingerprint(uppercase),
                "Constructor should throw IllegalArgumentException for sha256Hex with uppercase letters");
    }

    @Test
    void constructor_acceptsValidLowercaseHexadecimal() {
        DocumentFingerprint fingerprint1 = new DocumentFingerprint(VALID_SHA256);
        DocumentFingerprint fingerprint2 = new DocumentFingerprint(VALID_SHA256_2);
        DocumentFingerprint fingerprint3 = new DocumentFingerprint(VALID_SHA256_3);

        assertEquals(VALID_SHA256, fingerprint1.sha256Hex());
        assertEquals(VALID_SHA256_2, fingerprint2.sha256Hex());
        assertEquals(VALID_SHA256_3, fingerprint3.sha256Hex());
    }

    @Test
    void constructor_acceptsAllHexDigits() {
        String allDigits = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
        DocumentFingerprint fingerprint = new DocumentFingerprint(allDigits);
        assertEquals(allDigits, fingerprint.sha256Hex());
    }

    @Test
    void equals_returnsTrueForIdenticalSha256Values() {
        DocumentFingerprint fingerprint1 = new DocumentFingerprint(VALID_SHA256);
        DocumentFingerprint fingerprint2 = new DocumentFingerprint(VALID_SHA256);
        assertEquals(fingerprint1, fingerprint2);
    }

    @Test
    void equals_returnsFalseForDifferentSha256Values() {
        DocumentFingerprint fingerprint1 = new DocumentFingerprint(VALID_SHA256);
        DocumentFingerprint fingerprint2 = new DocumentFingerprint(VALID_SHA256_2);
        assertNotEquals(fingerprint1, fingerprint2);
    }

    @Test
    void equals_returnsFalseWhenComparedWithNull() {
        DocumentFingerprint fingerprint = new DocumentFingerprint(VALID_SHA256);
        assertNotEquals(null, fingerprint);
    }

    @Test
    void equals_returnsFalseWhenComparedWithDifferentType() {
        DocumentFingerprint fingerprint = new DocumentFingerprint(VALID_SHA256);
        assertNotEquals(VALID_SHA256, fingerprint);
    }

    @Test
    void hashCode_isSameForIdenticalValues() {
        DocumentFingerprint fingerprint1 = new DocumentFingerprint(VALID_SHA256);
        DocumentFingerprint fingerprint2 = new DocumentFingerprint(VALID_SHA256);
        assertEquals(fingerprint1.hashCode(), fingerprint2.hashCode());
    }

    @Test
    void hashCode_isDifferentForDifferentValues() {
        DocumentFingerprint fingerprint1 = new DocumentFingerprint(VALID_SHA256);
        DocumentFingerprint fingerprint2 = new DocumentFingerprint(VALID_SHA256_2);
        assertNotEquals(fingerprint1.hashCode(), fingerprint2.hashCode());
    }

    @Test
    void toString_containsTheSha256HexValue() {
        DocumentFingerprint fingerprint = new DocumentFingerprint(VALID_SHA256);
        assertTrue(fingerprint.toString().contains(VALID_SHA256));
    }

    @Test
    void documentFingerprintCanBeUsedAsMapKey() {
        DocumentFingerprint fingerprint1 = new DocumentFingerprint(VALID_SHA256);
        DocumentFingerprint fingerprint2 = new DocumentFingerprint(VALID_SHA256);
        DocumentFingerprint fingerprint3 = new DocumentFingerprint(VALID_SHA256_2);

        var map = new java.util.HashMap<DocumentFingerprint, String>();
        map.put(fingerprint1, "file1");
        map.put(fingerprint2, "file2"); // Should overwrite since equal
        map.put(fingerprint3, "file3");

        assertEquals(2, map.size());
        assertEquals("file2", map.get(fingerprint1));
    }

    @Test
    void documentFingerprintCanBeUsedInSets() {
        DocumentFingerprint fingerprint1 = new DocumentFingerprint(VALID_SHA256);
        DocumentFingerprint fingerprint2 = new DocumentFingerprint(VALID_SHA256);
        DocumentFingerprint fingerprint3 = new DocumentFingerprint(VALID_SHA256_2);

        var set = new java.util.HashSet<DocumentFingerprint>();
        set.add(fingerprint1);
        set.add(fingerprint2); // Should not add duplicate
        set.add(fingerprint3);

        assertEquals(2, set.size());
    }
}
