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

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import org.junit.jupiter.api.Test;

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

/**
 * Unit tests for {@link AiRequestComposer}.
 */
class AiRequestComposerTest {

    private static final int TEST_MAX_TITLE_LENGTH = 60;

    @Test
    void compose_shouldCreateAiRequestRepresentation() {
        // Given
        PromptIdentifier promptId = new PromptIdentifier("prompt_v1.txt");
        String promptContent = "You are a helpful assistant.";
        String documentText = "This is the document content.";

        // When
        AiRequestRepresentation result = AiRequestComposer.compose(promptId, promptContent, documentText);

        // Then
        assertThat(result.promptIdentifier()).isEqualTo(promptId);
        assertThat(result.promptContent()).isEqualTo(promptContent);
        assertThat(result.documentText()).isEqualTo(documentText);
        assertThat(result.sentCharacterCount()).isEqualTo(documentText.length());
    }

    @Test
    void compose_shouldSetSentCharacterCountToDocumentTextLength() {
        // Given
        PromptIdentifier promptId = new PromptIdentifier("prompt.txt");
        String promptContent = "Prompt";
        String documentText = "Document text with exactly 26 characters";

        // When
        AiRequestRepresentation result = AiRequestComposer.compose(promptId, promptContent, documentText);

        // Then
        assertThat(result.sentCharacterCount()).isEqualTo(documentText.length());
        assertThat(result.sentCharacterCount()).isEqualTo(40); // "Document text with exactly 26 characters" has 40 chars
    }

    @Test
    void compose_shouldHandleEmptyDocumentText() {
        // Given
        PromptIdentifier promptId = new PromptIdentifier("prompt.txt");
        String promptContent = "Prompt";
        String documentText = "";

        // When
        AiRequestRepresentation result = AiRequestComposer.compose(promptId, promptContent, documentText);

        // Then
        assertThat(result.documentText()).isEmpty();
        assertThat(result.sentCharacterCount()).isZero();
    }

    @Test
    void compose_shouldThrowNullPointerException_whenPromptIdentifierIsNull() {
        // When & Then
        assertThatThrownBy(
            () -> AiRequestComposer.compose(null, "Prompt", "Document"))
            .isInstanceOf(NullPointerException.class)
            .hasMessage("promptIdentifier must not be null");
    }

    @Test
    void compose_shouldThrowNullPointerException_whenPromptContentIsNull() {
        // When & Then
        assertThatThrownBy(
            () -> AiRequestComposer.compose(new PromptIdentifier("id"), null, "Document"))
            .isInstanceOf(NullPointerException.class)
            .hasMessage("promptContent must not be null");
    }

    @Test
    void compose_shouldThrowNullPointerException_whenDocumentTextIsNull() {
        // When & Then
        assertThatThrownBy(
            () -> AiRequestComposer.compose(new PromptIdentifier("id"), "Prompt", null))
            .isInstanceOf(NullPointerException.class)
            .hasMessage("documentText must not be null");
    }

    @Test
    void buildCompleteRequestText_shouldBuildDeterministicOrder() {
        // Given
        PromptIdentifier promptId = new PromptIdentifier("prompt_v2.txt");
        String promptContent = "Analyze this document";
        String documentText = "Document content here";

        // When
        String result = AiRequestComposer.buildCompleteRequestText(promptId, promptContent, documentText, TEST_MAX_TITLE_LENGTH);

        // Then
        // Verify deterministic order: prompt, then identifier, then document text
        assertThat(result)
            .contains(promptContent)
            .contains("Prompt-ID: prompt_v2.txt")
            .contains("Document Text")
            .contains(documentText);

        // Verify order: prompt comes before identifier
        int promptIndex = result.indexOf(promptContent);
        int identifierIndex = result.indexOf("Prompt-ID:");
        assertThat(promptIndex).isLessThan(identifierIndex);

        // Verify order: identifier comes before document marker
        int documentMarkerIndex = result.indexOf("Document Text");
        assertThat(identifierIndex).isLessThan(documentMarkerIndex);

        // Verify order: document marker comes before document text
        int docTextIndex = result.indexOf(documentText);
        assertThat(documentMarkerIndex).isLessThan(docTextIndex);
    }

    @Test
    void buildCompleteRequestText_shouldIncludeStructuralMarkers() {
        // Given
        PromptIdentifier promptId = new PromptIdentifier("prompt.txt");
        String promptContent = "Prompt";
        String documentText = "Document";

        // When
        String result = AiRequestComposer.buildCompleteRequestText(promptId, promptContent, documentText, TEST_MAX_TITLE_LENGTH);

        // Then
        assertThat(result).contains("--- Prompt-ID:");
        assertThat(result).contains("---");
        assertThat(result).contains("--- Document Text ---");
    }

    @Test
    void buildCompleteRequestText_shouldThrowNullPointerException_whenPromptIdentifierIsNull() {
        // When & Then
        assertThatThrownBy(
            () -> AiRequestComposer.buildCompleteRequestText(null, "Prompt", "Document", TEST_MAX_TITLE_LENGTH))
            .isInstanceOf(NullPointerException.class)
            .hasMessage("promptIdentifier must not be null");
    }

    @Test
    void buildCompleteRequestText_shouldThrowNullPointerException_whenPromptContentIsNull() {
        // When & Then
        assertThatThrownBy(
            () -> AiRequestComposer.buildCompleteRequestText(new PromptIdentifier("id"), null, "Document", TEST_MAX_TITLE_LENGTH))
            .isInstanceOf(NullPointerException.class)
            .hasMessage("promptContent must not be null");
    }

    @Test
    void buildCompleteRequestText_shouldThrowNullPointerException_whenDocumentTextIsNull() {
        // When & Then
        assertThatThrownBy(
            () -> AiRequestComposer.buildCompleteRequestText(new PromptIdentifier("id"), "Prompt", null, TEST_MAX_TITLE_LENGTH))
            .isInstanceOf(NullPointerException.class)
            .hasMessage("documentText must not be null");
    }

    @Test
    void compose_shouldProduceValidRepresentation_withMultilineContent() {
        // Given
        PromptIdentifier promptId = new PromptIdentifier("prompt.txt");
        String promptContent = "Line 1\nLine 2\nLine 3";
        String documentText = "Doc line 1\nDoc line 2";

        // When
        AiRequestRepresentation result = AiRequestComposer.compose(promptId, promptContent, documentText);

        // Then
        assertThat(result.promptContent()).isEqualTo(promptContent);
        assertThat(result.documentText()).isEqualTo(documentText);
        assertThat(result.sentCharacterCount()).isEqualTo(documentText.length());
    }

    @Test
    void buildCompleteRequestText_shouldPreserveNewlines() {
        // Given
        PromptIdentifier promptId = new PromptIdentifier("id");
        String promptContent = "Prompt\nwith\nnewlines";
        String documentText = "Document\ntext";

        // When
        String result = AiRequestComposer.buildCompleteRequestText(promptId, promptContent, documentText, TEST_MAX_TITLE_LENGTH);

        // Then
        assertThat(result).contains("Prompt\nwith\nnewlines");
        assertThat(result).contains("Document\ntext");
    }

    @Test
    void buildCompleteRequestText_shouldIncludeConfiguredMaxTitleLength() {
        // Given
        PromptIdentifier promptId = new PromptIdentifier("prompt.txt");
        String promptContent = "Analyze the document.";
        String documentText = "Sample document content";

        // When
        String result = AiRequestComposer.buildCompleteRequestText(promptId, promptContent, documentText, 42);

        // Then
        assertThat(result).contains("max 42 characters");
    }

    @Test
    void buildCompleteRequestText_shouldRejectZeroMaxTitleLength() {
        PromptIdentifier promptId = new PromptIdentifier("prompt.txt");
        assertThatThrownBy(
            () -> AiRequestComposer.buildCompleteRequestText(promptId, "Prompt", "Document", 0))
            .isInstanceOf(IllegalArgumentException.class)
            .hasMessageContaining("maxTitleLength must be >= 1");
    }

    @Test
    void buildCompleteRequestText_shouldIncludeJsonResponseFormat() {
        // Given
        PromptIdentifier promptId = new PromptIdentifier("prompt.txt");
        String promptContent = "Analyze the document.";
        String documentText = "Sample document content";

        // When
        String result = AiRequestComposer.buildCompleteRequestText(promptId, promptContent, documentText, TEST_MAX_TITLE_LENGTH);

        // Then
        // Verify that the actual composed request includes JSON response format specification
        assertThat(result).contains("Response Format (JSON-only)");
        assertThat(result).contains("\"title\"");
        assertThat(result).contains("\"reasoning\"");
        assertThat(result).contains("\"date\"");
        assertThat(result).contains("mandatory");
        assertThat(result).contains("optional");
        assertThat(result).contains("No text outside the JSON object");
    }

    @Test
    void buildCompleteRequestText_shouldHaveDeterministicJsonFormatOrder() {
        // Given
        PromptIdentifier promptId = new PromptIdentifier("prompt.txt");
        String promptContent = "Process this.";
        String documentText = "Document to process";

        // When
        String result = AiRequestComposer.buildCompleteRequestText(promptId, promptContent, documentText, TEST_MAX_TITLE_LENGTH);

        // Then
        // Verify deterministic order: document text comes before JSON response format
        int documentTextIndex = result.indexOf(documentText);
        int jsonFormatIndex = result.indexOf("Response Format (JSON-only)");
        assertThat(documentTextIndex).isLessThan(jsonFormatIndex);

        // Verify JSON field order: title before reasoning, reasoning before date
        int titleIndex = result.indexOf("\"title\"");
        int reasoningIndex = result.indexOf("\"reasoning\"");
        int dateIndex = result.indexOf("\"date\"");
        assertThat(titleIndex).isLessThan(reasoningIndex);
        assertThat(reasoningIndex).isLessThan(dateIndex);
    }

    @Test
    void buildCompleteRequestText_shouldProduceCompleteRequest_withAllSections() {
        // Given
        PromptIdentifier promptId = new PromptIdentifier("prompt_v3.txt");
        String promptContent = "Classify the document.";
        String documentText = "Content to classify";

        // When
        String result = AiRequestComposer.buildCompleteRequestText(promptId, promptContent, documentText, TEST_MAX_TITLE_LENGTH);

        // Then
        // Verify all sections are present in the actual composed request
        assertThat(result)
            .contains("Classify the document.") // prompt
            .contains("--- Prompt-ID: prompt_v3.txt ---") // identifier
            .contains("--- Document Text ---") // document marker
            .contains("Content to classify") // document text
            .contains("--- Response Format (JSON-only) ---") // JSON format marker
            .contains("Respond with a JSON object containing exactly:"); // JSON instruction
    }
}
