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

import java.time.Duration;
import java.time.LocalDate;
import java.util.Objects;

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

/**
 * Immutable event describing the outcome of processing exactly one candidate document.
 * <p>
 * Emitted by the application layer at every terminal resolution point of a candidate
 * (success, retryable failure, permanent failure, skip). Observers may use this event
 * to update a live progress view, write an audit record, or drive a UI list.
 * <p>
 * The event is deliberately decoupled from persistence types: it carries only what an
 * external observer needs to display or correlate a single candidate result.
 *
 * @param originalFileName    the source candidate's unique identifier (typically the source
 *                            filename); never {@code null} or blank
 * @param fingerprint         the content-based identity of the processed document;
 *                            never {@code null}
 * @param status              the aggregated outcome status; never {@code null}
 * @param finalFileName       the final target filename, including any duplicate suffix;
 *                            never {@code null} for {@link DocumentCompletionStatus#SUCCESS},
 *                            always {@code null} for all other statuses
 * @param resolvedDate        the resolved date of the naming proposal; never {@code null}
 *                            for {@link DocumentCompletionStatus#SUCCESS}, always {@code null}
 *                            for skip events. May be {@code null} for failure events.
 * @param aiReasoning         the AI reasoning text associated with the naming proposal, if
 *                            any is available for this candidate (may be present on success
 *                            and on some failure paths where an AI call had previously
 *                            produced a reasoning); {@code null} when no reasoning exists
 * @param failureMessage      a human-readable description of the failure when the status
 *                            indicates an error; {@code null} for successful or skipped
 *                            candidates
 * @param processingDuration  the wall-clock duration spent on this candidate in the current
 *                            run; never {@code null} and never negative
 */
public record DocumentCompletionEvent(
        String originalFileName,
        DocumentFingerprint fingerprint,
        DocumentCompletionStatus status,
        String finalFileName,
        LocalDate resolvedDate,
        String aiReasoning,
        String failureMessage,
        Duration processingDuration) {

    /**
     * Compact constructor validating mandatory fields.
     *
     * @throws NullPointerException     if {@code originalFileName}, {@code fingerprint},
     *                                  {@code status} or {@code processingDuration} is
     *                                  {@code null}
     * @throws IllegalArgumentException if {@code originalFileName} is blank or
     *                                  {@code processingDuration} is negative
     */
    public DocumentCompletionEvent {
        Objects.requireNonNull(originalFileName, "originalFileName must not be null");
        if (originalFileName.isBlank()) {
            throw new IllegalArgumentException("originalFileName must not be blank");
        }
        Objects.requireNonNull(fingerprint, "fingerprint must not be null");
        Objects.requireNonNull(status, "status must not be null");
        Objects.requireNonNull(processingDuration, "processingDuration must not be null");
        if (processingDuration.isNegative()) {
            throw new IllegalArgumentException("processingDuration must not be negative");
        }
    }
}
