package de.gecheckt.pdf.umbenenner.application.port.out;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotEquals;

import org.junit.jupiter.api.Test;

import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import de.gecheckt.pdf.umbenenner.domain.model.RunId;

/**
 * Tests for the {@link DocumentLogCorrelation} sealed type and its two permitted implementations.
 * <p>
 * Verifies:
 * <ul>
 *   <li>{@link DocumentLogCorrelation.CandidateCorrelation} stores the run identifier and
 *       candidate description correctly (pre-fingerprint phase).</li>
 *   <li>{@link DocumentLogCorrelation.FingerprintCorrelation} stores the run identifier and
 *       fingerprint correctly (post-fingerprint phase).</li>
 *   <li>The sealed type contract: only the two permitted subtypes exist.</li>
 * </ul>
 */
class DocumentLogCorrelationTest {

    private static final String RUN_ID_VALUE = "run-correlation-test-001";
    private static final String CANDIDATE_DESCRIPTION = "invoice-2026-01-15.pdf";
    private static final String FINGERPRINT_HEX = "a".repeat(64);

    // -------------------------------------------------------------------------
    // CandidateCorrelation – pre-fingerprint phase
    // -------------------------------------------------------------------------

    @Test
    void candidateCorrelation_storesRunId() {
        RunId runId = new RunId(RUN_ID_VALUE);
        DocumentLogCorrelation.CandidateCorrelation correlation =
                new DocumentLogCorrelation.CandidateCorrelation(runId, CANDIDATE_DESCRIPTION);

        assertEquals(runId, correlation.runId());
    }

    @Test
    void candidateCorrelation_storesCandidateDescription() {
        RunId runId = new RunId(RUN_ID_VALUE);
        DocumentLogCorrelation.CandidateCorrelation correlation =
                new DocumentLogCorrelation.CandidateCorrelation(runId, CANDIDATE_DESCRIPTION);

        assertEquals(CANDIDATE_DESCRIPTION, correlation.candidateDescription());
    }

    @Test
    void candidateCorrelation_runIdAccessibleViaInterface() {
        RunId runId = new RunId(RUN_ID_VALUE);
        DocumentLogCorrelation correlation =
                new DocumentLogCorrelation.CandidateCorrelation(runId, CANDIDATE_DESCRIPTION);

        // runId() is declared on the sealed interface and must be accessible polymorphically
        assertEquals(runId, correlation.runId());
    }

    @Test
    void candidateCorrelation_twoInstancesWithSameDataAreEqual() {
        RunId runId = new RunId(RUN_ID_VALUE);
        DocumentLogCorrelation.CandidateCorrelation first =
                new DocumentLogCorrelation.CandidateCorrelation(runId, CANDIDATE_DESCRIPTION);
        DocumentLogCorrelation.CandidateCorrelation second =
                new DocumentLogCorrelation.CandidateCorrelation(runId, CANDIDATE_DESCRIPTION);

        assertEquals(first, second);
    }

    @Test
    void candidateCorrelation_implementsDocumentLogCorrelation() {
        RunId runId = new RunId(RUN_ID_VALUE);
        DocumentLogCorrelation.CandidateCorrelation correlation =
                new DocumentLogCorrelation.CandidateCorrelation(runId, CANDIDATE_DESCRIPTION);

        assertInstanceOf(DocumentLogCorrelation.class, correlation);
    }

    // -------------------------------------------------------------------------
    // FingerprintCorrelation – post-fingerprint phase
    // -------------------------------------------------------------------------

    @Test
    void fingerprintCorrelation_storesRunId() {
        RunId runId = new RunId(RUN_ID_VALUE);
        DocumentFingerprint fingerprint = new DocumentFingerprint(FINGERPRINT_HEX);
        DocumentLogCorrelation.FingerprintCorrelation correlation =
                new DocumentLogCorrelation.FingerprintCorrelation(runId, fingerprint);

        assertEquals(runId, correlation.runId());
    }

    @Test
    void fingerprintCorrelation_storesFingerprint() {
        RunId runId = new RunId(RUN_ID_VALUE);
        DocumentFingerprint fingerprint = new DocumentFingerprint(FINGERPRINT_HEX);
        DocumentLogCorrelation.FingerprintCorrelation correlation =
                new DocumentLogCorrelation.FingerprintCorrelation(runId, fingerprint);

        assertEquals(fingerprint, correlation.fingerprint());
    }

    @Test
    void fingerprintCorrelation_runIdAccessibleViaInterface() {
        RunId runId = new RunId(RUN_ID_VALUE);
        DocumentFingerprint fingerprint = new DocumentFingerprint(FINGERPRINT_HEX);
        DocumentLogCorrelation correlation =
                new DocumentLogCorrelation.FingerprintCorrelation(runId, fingerprint);

        // runId() is declared on the sealed interface and must be accessible polymorphically
        assertEquals(runId, correlation.runId());
    }

    @Test
    void fingerprintCorrelation_twoInstancesWithSameDataAreEqual() {
        RunId runId = new RunId(RUN_ID_VALUE);
        DocumentFingerprint fingerprint = new DocumentFingerprint(FINGERPRINT_HEX);
        DocumentLogCorrelation.FingerprintCorrelation first =
                new DocumentLogCorrelation.FingerprintCorrelation(runId, fingerprint);
        DocumentLogCorrelation.FingerprintCorrelation second =
                new DocumentLogCorrelation.FingerprintCorrelation(runId, fingerprint);

        assertEquals(first, second);
    }

    @Test
    void fingerprintCorrelation_implementsDocumentLogCorrelation() {
        RunId runId = new RunId(RUN_ID_VALUE);
        DocumentFingerprint fingerprint = new DocumentFingerprint(FINGERPRINT_HEX);
        DocumentLogCorrelation.FingerprintCorrelation correlation =
                new DocumentLogCorrelation.FingerprintCorrelation(runId, fingerprint);

        assertInstanceOf(DocumentLogCorrelation.class, correlation);
    }

    // -------------------------------------------------------------------------
    // Sealed type structural contract
    // -------------------------------------------------------------------------

    @Test
    void sealedType_patternMatchExhaustsAllPermittedSubtypes() {
        RunId runId = new RunId(RUN_ID_VALUE);

        DocumentLogCorrelation candidatePhase =
                new DocumentLogCorrelation.CandidateCorrelation(runId, CANDIDATE_DESCRIPTION);
        DocumentLogCorrelation fingerprintPhase =
                new DocumentLogCorrelation.FingerprintCorrelation(runId, new DocumentFingerprint(FINGERPRINT_HEX));

        // Pattern match on the sealed type must compile exhaustively for exactly these two cases
        String candidatePhaseResult = describe(candidatePhase);
        String fingerprintPhaseResult = describe(fingerprintPhase);

        assertEquals("candidate", candidatePhaseResult);
        assertEquals("fingerprint", fingerprintPhaseResult);
    }

    /** Helper method using an exhaustive switch over the sealed type. */
    private static String describe(DocumentLogCorrelation correlation) {
        return switch (correlation) {
            case DocumentLogCorrelation.CandidateCorrelation ignored -> "candidate";
            case DocumentLogCorrelation.FingerprintCorrelation ignored -> "fingerprint";
        };
    }

    @Test
    void candidateCorrelation_differentDescriptions_areNotEqual() {
        RunId runId = new RunId(RUN_ID_VALUE);
        DocumentLogCorrelation.CandidateCorrelation withFirst =
                new DocumentLogCorrelation.CandidateCorrelation(runId, "first.pdf");
        DocumentLogCorrelation.CandidateCorrelation withSecond =
                new DocumentLogCorrelation.CandidateCorrelation(runId, "second.pdf");

        assertNotEquals(withFirst, withSecond);
    }

    @Test
    void fingerprintCorrelation_differentFingerprints_areNotEqual() {
        RunId runId = new RunId(RUN_ID_VALUE);
        DocumentFingerprint first = new DocumentFingerprint("a".repeat(64));
        DocumentFingerprint second = new DocumentFingerprint("b".repeat(64));
        DocumentLogCorrelation.FingerprintCorrelation withFirst =
                new DocumentLogCorrelation.FingerprintCorrelation(runId, first);
        DocumentLogCorrelation.FingerprintCorrelation withSecond =
                new DocumentLogCorrelation.FingerprintCorrelation(runId, second);

        assertNotEquals(withFirst, withSecond);
    }
}
