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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
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 SourceDocumentCandidate} value object.
 * <p>
 * Verifies immutability, value semantics, and correct validation of source document candidates.
 */
class SourceDocumentCandidateTest {

    private static final String VALID_IDENTIFIER = "document.pdf";
    private static final String VALID_IDENTIFIER_2 = "another_file.PDF";
    private static final long VALID_FILE_SIZE = 1024L;
    private static final long ZERO_FILE_SIZE = 0L;
    private static final String VALID_LOCATOR_VALUE = "/home/user/docs/document.pdf";

    @Test
    void constructor_createsSourceDocumentCandidateWithValidValues() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentCandidate candidate = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator);

        assertNotNull(candidate);
        assertEquals(VALID_IDENTIFIER, candidate.uniqueIdentifier());
        assertEquals(VALID_FILE_SIZE, candidate.fileSizeBytes());
        assertEquals(locator, candidate.locator());
    }

    @Test
    void constructor_throwsNullPointerExceptionWhenUniqueIdentifierIsNull() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        assertThrows(NullPointerException.class,
                () -> new SourceDocumentCandidate(null, VALID_FILE_SIZE, locator),
                "Constructor should throw NullPointerException for null uniqueIdentifier");
    }

    @Test
    void constructor_throwsIllegalArgumentExceptionWhenUniqueIdentifierIsEmpty() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        assertThrows(IllegalArgumentException.class,
                () -> new SourceDocumentCandidate("", VALID_FILE_SIZE, locator),
                "Constructor should throw IllegalArgumentException for empty uniqueIdentifier");
    }

    @Test
    void constructor_throwsIllegalArgumentExceptionWhenFileSizeIsNegative() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        assertThrows(IllegalArgumentException.class,
                () -> new SourceDocumentCandidate(VALID_IDENTIFIER, -1L, locator),
                "Constructor should throw IllegalArgumentException for negative fileSizeBytes");
    }

    @Test
    void constructor_throwsNullPointerExceptionWhenLocatorIsNull() {
        assertThrows(NullPointerException.class,
                () -> new SourceDocumentCandidate(VALID_IDENTIFIER, VALID_FILE_SIZE, null),
                "Constructor should throw NullPointerException for null locator");
    }

    @Test
    void constructor_acceptsZeroFileSize() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentCandidate candidate = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                ZERO_FILE_SIZE,
                locator);

        assertEquals(ZERO_FILE_SIZE, candidate.fileSizeBytes());
    }

    @Test
    void constructor_acceptsVeryLargeFileSize() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        long largeSize = Long.MAX_VALUE;
        SourceDocumentCandidate candidate = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                largeSize,
                locator);

        assertEquals(largeSize, candidate.fileSizeBytes());
    }

    @Test
    void uniqueIdentifier_returnsTheConstructorValue() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentCandidate candidate = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator);

        assertEquals(VALID_IDENTIFIER, candidate.uniqueIdentifier());
    }

    @Test
    void fileSizeBytes_returnsTheConstructorValue() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentCandidate candidate = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator);

        assertEquals(VALID_FILE_SIZE, candidate.fileSizeBytes());
    }

    @Test
    void locator_returnsTheConstructorValue() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentCandidate candidate = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator);

        assertEquals(locator, candidate.locator());
    }

    @Test
    void equals_returnsTrueForIdenticalValues() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentCandidate candidate1 = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator);
        SourceDocumentCandidate candidate2 = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator);

        assertEquals(candidate1, candidate2);
    }

    @Test
    void equals_returnsFalseForDifferentUniqueIdentifiers() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentCandidate candidate1 = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator);
        SourceDocumentCandidate candidate2 = new SourceDocumentCandidate(
                VALID_IDENTIFIER_2,
                VALID_FILE_SIZE,
                locator);

        assertNotEquals(candidate1, candidate2);
    }

    @Test
    void equals_returnsFalseForDifferentFileSizes() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentCandidate candidate1 = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator);
        SourceDocumentCandidate candidate2 = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                2048L,
                locator);

        assertNotEquals(candidate1, candidate2);
    }

    @Test
    void equals_returnsFalseForDifferentLocators() {
        SourceDocumentLocator locator1 = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentLocator locator2 = new SourceDocumentLocator("/other/path/document.pdf");
        SourceDocumentCandidate candidate1 = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator1);
        SourceDocumentCandidate candidate2 = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator2);

        assertNotEquals(candidate1, candidate2);
    }

    @Test
    void equals_returnsFalseWhenComparedWithNull() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentCandidate candidate = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator);

        assertNotEquals(null, candidate);
    }

    @Test
    void equals_returnsFalseWhenComparedWithDifferentType() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentCandidate candidate = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator);

        assertNotEquals(VALID_IDENTIFIER, candidate);
    }

    @Test
    void hashCode_isSameForIdenticalValues() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentCandidate candidate1 = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator);
        SourceDocumentCandidate candidate2 = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator);

        assertEquals(candidate1.hashCode(), candidate2.hashCode());
    }

    @Test
    void hashCode_isDifferentForDifferentValues() {
        SourceDocumentLocator locator1 = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentLocator locator2 = new SourceDocumentLocator("/other/path/document.pdf");
        SourceDocumentCandidate candidate1 = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator1);
        SourceDocumentCandidate candidate2 = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator2);

        assertNotEquals(candidate1.hashCode(), candidate2.hashCode());
    }

    @Test
    void toString_containsAllFields() {
        SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentCandidate candidate = new SourceDocumentCandidate(
                VALID_IDENTIFIER,
                VALID_FILE_SIZE,
                locator);

        String str = candidate.toString();
        assertNotNull(str);
        assertFalse(str.isEmpty());
    }

    @Test
    void sourceDocumentCandidateCanBeUsedInCollections() {
        SourceDocumentLocator locator1 = new SourceDocumentLocator(VALID_LOCATOR_VALUE);
        SourceDocumentLocator locator2 = new SourceDocumentLocator("/other/path/document.pdf");

        SourceDocumentCandidate candidate1 = new SourceDocumentCandidate(VALID_IDENTIFIER, VALID_FILE_SIZE, locator1);
        SourceDocumentCandidate candidate2 = new SourceDocumentCandidate(VALID_IDENTIFIER, VALID_FILE_SIZE, locator1);
        SourceDocumentCandidate candidate3 = new SourceDocumentCandidate(VALID_IDENTIFIER_2, VALID_FILE_SIZE, locator2);

        var set = new java.util.HashSet<SourceDocumentCandidate>();
        set.add(candidate1);
        set.add(candidate2); // Should not add duplicate
        set.add(candidate3);

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