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 java.time.LocalDate;

import org.junit.jupiter.api.Test;

/**
 * Unit tests for {@link NamingProposal} value object.
 * <p>
 * Verifies immutability, value semantics, and correct validation of naming proposals.
 */
class NamingProposalTest {

    private static final LocalDate SAMPLE_DATE = LocalDate.of(2026, 3, 31);
    private static final LocalDate DIFFERENT_DATE = LocalDate.of(2025, 12, 25);
    private static final String SAMPLE_TITLE = "Stromabrechnung";
    private static final String DIFFERENT_TITLE = "Gasabrechnung";
    private static final String SAMPLE_REASONING = "Found electricity bill dated 2026-03-31 in document text.";
    private static final String EMPTY_REASONING = "";

    @Test
    void constructor_createsNamingProposalWithValidValues() {
        NamingProposal proposal = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);

        assertNotNull(proposal);
        assertEquals(SAMPLE_DATE, proposal.resolvedDate());
        assertEquals(DateSource.AI_PROVIDED, proposal.dateSource());
        assertEquals(SAMPLE_TITLE, proposal.validatedTitle());
        assertEquals(SAMPLE_REASONING, proposal.aiReasoning());
    }

    @Test
    void constructor_throwsNullPointerExceptionWhenResolvedDateIsNull() {
        assertThrows(NullPointerException.class,
                () -> new NamingProposal(null, DateSource.AI_PROVIDED, SAMPLE_TITLE, SAMPLE_REASONING),
                "Constructor should throw NullPointerException for null resolvedDate");
    }

    @Test
    void constructor_throwsNullPointerExceptionWhenDateSourceIsNull() {
        assertThrows(NullPointerException.class,
                () -> new NamingProposal(SAMPLE_DATE, null, SAMPLE_TITLE, SAMPLE_REASONING),
                "Constructor should throw NullPointerException for null dateSource");
    }

    @Test
    void constructor_throwsNullPointerExceptionWhenValidatedTitleIsNull() {
        assertThrows(NullPointerException.class,
                () -> new NamingProposal(SAMPLE_DATE, DateSource.AI_PROVIDED, null, SAMPLE_REASONING),
                "Constructor should throw NullPointerException for null validatedTitle");
    }

    @Test
    void constructor_throwsIllegalArgumentExceptionWhenValidatedTitleIsEmpty() {
        assertThrows(IllegalArgumentException.class,
                () -> new NamingProposal(SAMPLE_DATE, DateSource.AI_PROVIDED, "", SAMPLE_REASONING),
                "Constructor should throw IllegalArgumentException for empty validatedTitle");
    }

    @Test
    void constructor_throwsNullPointerExceptionWhenAiReasoningIsNull() {
        assertThrows(NullPointerException.class,
                () -> new NamingProposal(SAMPLE_DATE, DateSource.AI_PROVIDED, SAMPLE_TITLE, null),
                "Constructor should throw NullPointerException for null aiReasoning");
    }

    @Test
    void constructor_acceptsEmptyAiReasoning() {
        NamingProposal proposal = new NamingProposal(
                SAMPLE_DATE,
                DateSource.FALLBACK_CURRENT,
                SAMPLE_TITLE,
                EMPTY_REASONING);

        assertEquals(EMPTY_REASONING, proposal.aiReasoning());
    }

    @Test
    void constructor_acceptsFallbackCurrentDateSource() {
        NamingProposal proposal = new NamingProposal(
                SAMPLE_DATE,
                DateSource.FALLBACK_CURRENT,
                SAMPLE_TITLE,
                SAMPLE_REASONING);

        assertEquals(DateSource.FALLBACK_CURRENT, proposal.dateSource());
    }

    @Test
    void resolvedDate_returnsTheConstructorValue() {
        NamingProposal proposal = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);

        assertEquals(SAMPLE_DATE, proposal.resolvedDate());
    }

    @Test
    void dateSource_returnsTheConstructorValue() {
        NamingProposal proposal = new NamingProposal(
                SAMPLE_DATE,
                DateSource.FALLBACK_CURRENT,
                SAMPLE_TITLE,
                SAMPLE_REASONING);

        assertEquals(DateSource.FALLBACK_CURRENT, proposal.dateSource());
    }

    @Test
    void validatedTitle_returnsTheConstructorValue() {
        NamingProposal proposal = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);

        assertEquals(SAMPLE_TITLE, proposal.validatedTitle());
    }

    @Test
    void aiReasoning_returnsTheConstructorValue() {
        NamingProposal proposal = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);

        assertEquals(SAMPLE_REASONING, proposal.aiReasoning());
    }

    @Test
    void equals_returnsTrueForIdenticalValues() {
        NamingProposal proposal1 = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);
        NamingProposal proposal2 = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);

        assertEquals(proposal1, proposal2);
    }

    @Test
    void equals_returnsFalseForDifferentDates() {
        NamingProposal proposal1 = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);
        NamingProposal proposal2 = new NamingProposal(
                DIFFERENT_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);

        assertNotEquals(proposal1, proposal2);
    }

    @Test
    void equals_returnsFalseForDifferentDateSources() {
        NamingProposal proposal1 = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);
        NamingProposal proposal2 = new NamingProposal(
                SAMPLE_DATE,
                DateSource.FALLBACK_CURRENT,
                SAMPLE_TITLE,
                SAMPLE_REASONING);

        assertNotEquals(proposal1, proposal2);
    }

    @Test
    void equals_returnsFalseForDifferentTitles() {
        NamingProposal proposal1 = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);
        NamingProposal proposal2 = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                DIFFERENT_TITLE,
                SAMPLE_REASONING);

        assertNotEquals(proposal1, proposal2);
    }

    @Test
    void equals_returnsFalseForDifferentReasoning() {
        NamingProposal proposal1 = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);
        NamingProposal proposal2 = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                "Different reasoning");

        assertNotEquals(proposal1, proposal2);
    }

    @Test
    void equals_returnsFalseWhenComparedWithNull() {
        NamingProposal proposal = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);

        assertNotEquals(null, proposal);
    }

    @Test
    void equals_returnsFalseWhenComparedWithDifferentType() {
        NamingProposal proposal = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);

        assertNotEquals(SAMPLE_TITLE, proposal);
    }

    @Test
    void hashCode_isSameForIdenticalValues() {
        NamingProposal proposal1 = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);
        NamingProposal proposal2 = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);

        assertEquals(proposal1.hashCode(), proposal2.hashCode());
    }

    @Test
    void hashCode_isDifferentForDifferentValues() {
        NamingProposal proposal1 = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);
        NamingProposal proposal2 = new NamingProposal(
                DIFFERENT_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);

        assertNotEquals(proposal1.hashCode(), proposal2.hashCode());
    }

    @Test
    void toString_containsAllFields() {
        NamingProposal proposal = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                SAMPLE_TITLE,
                SAMPLE_REASONING);

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

    @Test
    void constructor_acceptsTitleWithSpecialCharacters() {
        String titleWithUmlauts = "Stromabrechnung Üben";
        NamingProposal proposal = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                titleWithUmlauts,
                SAMPLE_REASONING);

        assertEquals(titleWithUmlauts, proposal.validatedTitle());
    }

    @Test
    void constructor_acceptsTitleWithSpaces() {
        String titleWithSpaces = "Rechnung für März";
        NamingProposal proposal = new NamingProposal(
                SAMPLE_DATE,
                DateSource.AI_PROVIDED,
                titleWithSpaces,
                SAMPLE_REASONING);

        assertEquals(titleWithSpaces, proposal.validatedTitle());
    }
}
