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

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

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

/**
 * Application-facing representation of exactly one historised processing attempt
 * (Versuchshistorie-Eintrag) for an identified document.
 * <p>
 * <strong>Historisation boundary:</strong> Only attempts for documents whose
 * {@link DocumentFingerprint} was successfully computed are historised. Failures that
 * occur <em>before</em> the fingerprint is available (e.g. the source file is
 * unreadable before hashing) are <em>not</em> represented by a {@code ProcessingAttempt}
 * and are <em>not</em> written to SQLite.
 * <p>
 * <strong>Attempt number semantics:</strong> The attempt number starts at&nbsp;1 for the
 * first historised attempt per fingerprint and increases monotonically by 1 for every
 * subsequent attempt, including skip attempts
 * ({@link ProcessingStatus#SKIPPED_ALREADY_PROCESSED},
 * {@link ProcessingStatus#SKIPPED_FINAL_FAILURE}).
 * <p>
 * <strong>Token- und Preis-Felder:</strong>
 * <ul>
 *   <li>{@link #inputTokens()}, {@link #outputTokens()} – Standard-Token-Counts; {@code null} bei Versuchen ohne KI-Aufruf oder ohne Token-Daten in der Provider-Antwort.</li>
 *   <li>{@link #cacheCreationInputTokens()}, {@link #cacheReadInputTokens()} – Anthropic-Cache-Token-Counts; {@code null} bei OpenAI-Adapter oder fehlenden Werten.</li>
 *   <li>{@link #priceInputPerTokenNanoUsd()}, {@link #priceOutputPerTokenNanoUsd()} – Preis-Snapshot zum Aufrufzeitpunkt in Nano-USD pro Token; {@code null}, wenn fuer das Modell kein Preis hinterlegt war oder der Lookup fehlschlug.</li>
 * </ul>
 *
 * @param fingerprint                    content-based document identity; never null
 * @param runId                          identifier of the batch run; never null
 * @param attemptNumber                  monotonic sequence number per fingerprint; must be &gt;= 1
 * @param startedAt                      start of this processing attempt; never null
 * @param endedAt                        end of this processing attempt; never null
 * @param status                         outcome status of this attempt; never null
 * @param failureClass                   failure classification, or {@code null}
 * @param failureMessage                 failure description, or {@code null}
 * @param retryable                      whether this failure should be retried later
 * @param aiProvider                     opaque AI provider identifier, or {@code null}
 * @param modelName                      AI model name, or {@code null}
 * @param promptIdentifier               prompt identifier, or {@code null}
 * @param processedPageCount             number of PDF pages processed, or {@code null}
 * @param sentCharacterCount             number of characters sent to AI, or {@code null}
 * @param aiRawResponse                  full raw AI response, or {@code null}
 * @param aiReasoning                    AI reasoning text, or {@code null}
 * @param resolvedDate                   resolved date for naming proposal, or {@code null}
 * @param dateSource                     origin of resolved date, or {@code null}
 * @param validatedTitle                 validated title, or {@code null}
 * @param finalTargetFileName            filename written to the target folder for SUCCESS attempts, or {@code null}
 * @param inputTokens                    Standard-Input-Tokens, oder {@code null}
 * @param outputTokens                   Standard-Output-Tokens, oder {@code null}
 * @param cacheCreationInputTokens       Anthropic-Cache-Schreib-Tokens, oder {@code null}
 * @param cacheReadInputTokens           Anthropic-Cache-Lese-Tokens, oder {@code null}
 * @param priceInputPerTokenNanoUsd      Snapshot Input-Preis (Nano-USD/Token), oder {@code null}
 * @param priceOutputPerTokenNanoUsd     Snapshot Output-Preis (Nano-USD/Token), oder {@code null}
 */
public record ProcessingAttempt(
        DocumentFingerprint fingerprint,
        RunId runId,
        int attemptNumber,
        Instant startedAt,
        Instant endedAt,
        ProcessingStatus status,
        String failureClass,
        String failureMessage,
        boolean retryable,
        // AI traceability fields (null for non-AI attempts)
        String aiProvider,
        String modelName,
        String promptIdentifier,
        Integer processedPageCount,
        Integer sentCharacterCount,
        String aiRawResponse,
        String aiReasoning,
        LocalDate resolvedDate,
        DateSource dateSource,
        String validatedTitle,
        // Target copy traceability (null for non-SUCCESS attempts)
        String finalTargetFileName,
        // Token- und Preis-Snapshot-Felder (null fuer Versuche ohne KI-Aufruf
        // bzw. fuer V3.2-Bestand)
        Long inputTokens,
        Long outputTokens,
        Long cacheCreationInputTokens,
        Long cacheReadInputTokens,
        Long priceInputPerTokenNanoUsd,
        Long priceOutputPerTokenNanoUsd) {

    /**
     * Compact constructor validating mandatory non-null fields and numeric constraints.
     *
     * @throws NullPointerException     if any mandatory field is null
     * @throws IllegalArgumentException if {@code attemptNumber} is less than 1
     */
    public ProcessingAttempt {
        Objects.requireNonNull(fingerprint, "fingerprint must not be null");
        Objects.requireNonNull(runId, "runId must not be null");
        if (attemptNumber < 1) {
            throw new IllegalArgumentException(
                    "attemptNumber must be >= 1, but was: " + attemptNumber);
        }
        Objects.requireNonNull(startedAt, "startedAt must not be null");
        Objects.requireNonNull(endedAt, "endedAt must not be null");
        Objects.requireNonNull(status, "status must not be null");
    }

    /**
     * Convenience-Konstruktor ohne die neuen Token- und Preis-Felder.
     *
     * <p>Setzt alle sechs Token- und Preis-Snapshot-Felder auf {@code null}.
     * Wird von Aufrufern verwendet, die noch keine Token-Daten beistellen
     * (typisch fuer Skip-/Pre-Check-Pfade) oder fuer Tests, die das
     * Token-Tracking nicht beruehren.
     *
     * @param fingerprint         document identity; never null
     * @param runId               batch run identifier; never null
     * @param attemptNumber       monotonic attempt number; must be &gt;= 1
     * @param startedAt           start instant; never null
     * @param endedAt             end instant; never null
     * @param status              outcome status; never null
     * @param failureClass        failure class, or {@code null}
     * @param failureMessage      failure description, or {@code null}
     * @param retryable           whether retryable in a later run
     * @param aiProvider          opaque AI provider identifier, or {@code null}
     * @param modelName           AI model name, or {@code null}
     * @param promptIdentifier    prompt identifier, or {@code null}
     * @param processedPageCount  number of PDF pages processed, or {@code null}
     * @param sentCharacterCount  number of characters sent to AI, or {@code null}
     * @param aiRawResponse       full raw AI response, or {@code null}
     * @param aiReasoning         AI reasoning text, or {@code null}
     * @param resolvedDate        resolved date for naming proposal, or {@code null}
     * @param dateSource          origin of resolved date, or {@code null}
     * @param validatedTitle      validated title, or {@code null}
     * @param finalTargetFileName filename written to the target folder, or {@code null}
     */
    public ProcessingAttempt(
            DocumentFingerprint fingerprint,
            RunId runId,
            int attemptNumber,
            Instant startedAt,
            Instant endedAt,
            ProcessingStatus status,
            String failureClass,
            String failureMessage,
            boolean retryable,
            String aiProvider,
            String modelName,
            String promptIdentifier,
            Integer processedPageCount,
            Integer sentCharacterCount,
            String aiRawResponse,
            String aiReasoning,
            LocalDate resolvedDate,
            DateSource dateSource,
            String validatedTitle,
            String finalTargetFileName) {
        this(fingerprint, runId, attemptNumber, startedAt, endedAt, status,
                failureClass, failureMessage, retryable,
                aiProvider, modelName, promptIdentifier,
                processedPageCount, sentCharacterCount,
                aiRawResponse, aiReasoning,
                resolvedDate, dateSource, validatedTitle, finalTargetFileName,
                null, null, null, null, null, null);
    }

    /**
     * Creates a {@link ProcessingAttempt} with no AI traceability fields set.
     * <p>
     * Convenience factory for pre-check failures, skip events, and any attempt
     * that does not involve an AI call. The {@link #aiProvider()} field and all
     * Token- und Preis-Snapshot-Felder werden auf {@code null} gesetzt.
     *
     * @param fingerprint    document identity; must not be null
     * @param runId          batch run identifier; must not be null
     * @param attemptNumber  monotonic attempt number; must be &gt;= 1
     * @param startedAt      start instant; must not be null
     * @param endedAt        end instant; must not be null
     * @param status         outcome status; must not be null
     * @param failureClass   failure class name, or {@code null}
     * @param failureMessage failure description, or {@code null}
     * @param retryable      whether retryable in a later run
     * @return a new attempt with all AI fields set to {@code null}
     */
    public static ProcessingAttempt withoutAiFields(
            DocumentFingerprint fingerprint,
            RunId runId,
            int attemptNumber,
            Instant startedAt,
            Instant endedAt,
            ProcessingStatus status,
            String failureClass,
            String failureMessage,
            boolean retryable) {
        return new ProcessingAttempt(
                fingerprint, runId, attemptNumber, startedAt, endedAt,
                status, failureClass, failureMessage, retryable,
                null, null, null, null, null, null, null, null, null, null, null,
                null, null, null, null, null, null);
    }
}
