package de.gecheckt.pdf.umbenenner.application.service;

import java.util.Objects;

import de.gecheckt.pdf.umbenenner.domain.model.AiRequestRepresentation;
import de.gecheckt.pdf.umbenenner.domain.model.PromptIdentifier;

/**
 * Composes deterministic AI request representations from prompt and document text.
 * <p>
 * This service builds the exact request that will be sent to the AI service,
 * ensuring that the composition is deterministic and reproducible across batch runs.
 * The request is constructed from:
 * <ul>
 *   <li>The loaded prompt content (from the external prompt file)</li>
 *   <li>The stable prompt identifier (derived from the prompt source)</li>
 *   <li>The extracted document text (already limited to max characters if needed)</li>
 *   <li>An explicit JSON-only response format specification</li>
 *   <li>The exact character count that was sent (for traceability)</li>
 * </ul>
 * <p>
 * <strong>Composition order:</strong> The request follows a fixed, documented order:
 * <ol>
 *   <li>Prompt content (instruction)</li>
 *   <li>Prompt identifier marker</li>
 *   <li>Document text marker</li>
 *   <li>Document text content</li>
 *   <li>Response format expectation (JSON-only specification)</li>
 * </ol>
 * <p>
 * This fixed order ensures reproducibility and allows another implementation to
 * understand the request structure without guessing.
 * <p>
 * <strong>JSON-only response expectation:</strong> The actual composed request includes
 * an explicit specification that the AI must respond with a JSON object containing
 * exactly these fields:
 * <ul>
 *   <li>{@code title} — mandatory, up to the configured maximum length (base title)</li>
 *   <li>{@code reasoning} — mandatory, the AI's explanation</li>
 *   <li>{@code date} — optional, should be in YYYY-MM-DD format if present</li>
 * </ul>
 * <p>
 * This service is stateless and thread-safe. It performs no I/O and makes no external calls.
 */
public class AiRequestComposer {

    /**
     * Composes a deterministic AI request representation.
     * <p>
     * The composition order is fixed and documented to ensure determinism and
     * to make the structure obvious to any reader:
     * <ol>
     *   <li>Prompt content (instruction)</li>
     *   <li>Newline separator</li>
     *   <li>Prompt identifier marker (e.g., "--- Prompt-ID: filename ---")</li>
     *   <li>Newline separator</li>
     *   <li>Document text section marker</li>
     *   <li>Newline separator</li>
     *   <li>Document text content</li>
     *   <li>Newline separator</li>
     *   <li>Response format specification (JSON-only with required fields)</li>
     * </ol>
     * <p>
     * This fixed order ensures that:
     * <ul>
     *   <li>The prompt guides the AI</li>
     *   <li>The document text is clearly separated and identified</li>
     *   <li>The response format expectation is explicit in the actual request</li>
     *   <li>Another implementation knows exactly where each part begins and ends</li>
     *   <li>The composition is deterministic and reproducible across runs</li>
     * </ul>
     *
     * @param promptIdentifier the stable identifier for this prompt; must not be null
     * @param promptContent the prompt template content; must not be null
     * @param documentText the extracted document text; must not be null
     * @return an AiRequestRepresentation with sentCharacterCount set to documentText.length()
     * @throws NullPointerException if any parameter is null
     */
    public static AiRequestRepresentation compose(
            PromptIdentifier promptIdentifier,
            String promptContent,
            String documentText) {

        Objects.requireNonNull(promptIdentifier, "promptIdentifier must not be null");
        Objects.requireNonNull(promptContent, "promptContent must not be null");
        Objects.requireNonNull(documentText, "documentText must not be null");

        // Record the exact character count of the document text that was included.
        // This is the length of the document text (not the complete request).
        int sentCharacterCount = documentText.length();

        return new AiRequestRepresentation(
                promptIdentifier,
                promptContent,
                documentText,
                sentCharacterCount);
    }

    /**
     * Constructs the complete request text that will be sent to the AI.
     * <p>
     * This is a helper method that builds the exact string that would be included in the
     * HTTP request to the AI service. It follows the same deterministic order as
     * {@link #compose(PromptIdentifier, String, String)}, including the explicit
     * JSON-only response format specification with the configured maximum base title
     * length.
     *
     * @param promptIdentifier the stable identifier for this prompt; must not be null
     * @param promptContent the prompt template content; must not be null
     * @param documentText the extracted document text; must not be null
     * @param maxTitleLength the configured maximum base title length; must be &gt;= 1
     * @return the complete, deterministically-ordered request text for the AI (includes JSON format spec)
     * @throws NullPointerException if any parameter is null
     * @throws IllegalArgumentException if {@code maxTitleLength} is less than 1
     */
    public static String buildCompleteRequestText(
            PromptIdentifier promptIdentifier,
            String promptContent,
            String documentText,
            int maxTitleLength) {

        Objects.requireNonNull(promptIdentifier, "promptIdentifier must not be null");
        Objects.requireNonNull(promptContent, "promptContent must not be null");
        Objects.requireNonNull(documentText, "documentText must not be null");
        if (maxTitleLength < 1) {
            throw new IllegalArgumentException(
                    "maxTitleLength must be >= 1, but was: " + maxTitleLength);
        }

        StringBuilder requestBuilder = new StringBuilder();
        requestBuilder.append(promptContent);
        requestBuilder.append("\n");
        requestBuilder.append("--- Prompt-ID: ").append(promptIdentifier.identifier()).append(" ---");
        requestBuilder.append("\n");
        requestBuilder.append("--- Document Text ---");
        requestBuilder.append("\n");
        requestBuilder.append(documentText);
        requestBuilder.append("\n");
        appendJsonResponseFormat(requestBuilder, maxTitleLength);

        return requestBuilder.toString();
    }

    /**
     * Appends the explicit JSON-only response format specification to the request.
     * <p>
     * This ensures that the AI knows it must respond with a JSON object containing
     * exactly these fields: {@code title}, {@code reasoning}, and optionally {@code date}.
     * <p>
     * This specification is part of the deterministic composition and is included in
     * the actual request text sent to the AI service.
     *
     * @param requestBuilder  the StringBuilder to append the format specification to
     * @param maxTitleLength  the configured maximum base title length; must be &gt;= 1
     */
    private static void appendJsonResponseFormat(StringBuilder requestBuilder, int maxTitleLength) {
        requestBuilder.append("--- Response Format (JSON-only) ---");
        requestBuilder.append("\n");
        requestBuilder.append("Respond with a JSON object containing exactly:");
        requestBuilder.append("\n");
        requestBuilder.append("  \"title\": string (mandatory, max ")
                .append(maxTitleLength)
                .append(" characters, base title only)");
        requestBuilder.append("\n");
        requestBuilder.append("  \"reasoning\": string (mandatory, explanation of the decision)");
        requestBuilder.append("\n");
        requestBuilder.append("  \"date\": string (optional, YYYY-MM-DD format if provided)");
        requestBuilder.append("\n");
        requestBuilder.append("No text outside the JSON object.");
    }

    private AiRequestComposer() {
        // Static utility class – no instances
    }
}
