package de.gecheckt.pdf.umbenenner.adapter.out.prompt;

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

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingFailure;
import de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingResult;
import de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingSuccess;
import de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult;

/**
 * Unit tests for {@link FilesystemPromptPortAdapter}.
 */
class FilesystemPromptPortAdapterTest {

    private FilesystemPromptPortAdapter adapter;

    @TempDir
    Path tempDir;

    @BeforeEach
    void setUp() {
        // Adapter will be created with a specific prompt file path in each test
    }

    @Test
    void loadPrompt_shouldReturnSuccess_whenPromptFileExists() throws IOException {
        // Given
        String promptContent = "You are a helpful AI assistant that renames documents.";
        Path promptFile = tempDir.resolve("prompt.txt");
        Files.writeString(promptFile, promptContent, StandardCharsets.UTF_8);
        adapter = new FilesystemPromptPortAdapter(promptFile);

        // When
        PromptLoadingResult result = adapter.loadPrompt();

        // Then
        assertThat(result).isInstanceOf(PromptLoadingSuccess.class);
        PromptLoadingSuccess success = (PromptLoadingSuccess) result;
        assertThat(success.promptContent()).isEqualTo(promptContent);
        assertThat(success.promptIdentifier().identifier()).isEqualTo("prompt.txt");
    }

    @Test
    void loadPrompt_shouldTrimWhitespace_whenPromptContainsLeadingTrailingWhitespace() throws IOException {
        // Given
        String promptContent = "  \n  Helpful AI assistant  \n  ";
        Path promptFile = tempDir.resolve("prompt_whitespace.txt");
        Files.writeString(promptFile, promptContent, StandardCharsets.UTF_8);
        adapter = new FilesystemPromptPortAdapter(promptFile);

        // When
        PromptLoadingResult result = adapter.loadPrompt();

        // Then
        assertThat(result).isInstanceOf(PromptLoadingSuccess.class);
        PromptLoadingSuccess success = (PromptLoadingSuccess) result;
        assertThat(success.promptContent()).isEqualTo("Helpful AI assistant");
    }

    @Test
    void loadPrompt_shouldDeriveIdentifierFromFilename() throws IOException {
        // Given
        String promptContent = "Test prompt";
        Path promptFile = tempDir.resolve("prompt_v2_de.txt");
        Files.writeString(promptFile, promptContent, StandardCharsets.UTF_8);
        adapter = new FilesystemPromptPortAdapter(promptFile);

        // When
        PromptLoadingResult result = adapter.loadPrompt();

        // Then
        assertThat(result).isInstanceOf(PromptLoadingSuccess.class);
        PromptLoadingSuccess success = (PromptLoadingSuccess) result;
        assertThat(success.promptIdentifier().identifier()).isEqualTo("prompt_v2_de.txt");
    }

    @Test
    void loadPrompt_shouldReturnFailure_whenFileDoesNotExist() {
        // Given
        Path nonExistentFile = tempDir.resolve("nonexistent_prompt.txt");
        adapter = new FilesystemPromptPortAdapter(nonExistentFile);

        // When
        PromptLoadingResult result = adapter.loadPrompt();

        // Then
        assertThat(result).isInstanceOf(PromptLoadingFailure.class);
        PromptLoadingFailure failure = (PromptLoadingFailure) result;
        assertThat(failure.failureReason()).isEqualTo("FILE_NOT_FOUND");
        assertThat(failure.failureMessage()).contains("nonexistent_prompt.txt");
    }

    @Test
    void loadPrompt_shouldReturnFailure_whenPromptIsEmpty() throws IOException {
        // Given
        Path promptFile = tempDir.resolve("empty_prompt.txt");
        Files.writeString(promptFile, "", StandardCharsets.UTF_8);
        adapter = new FilesystemPromptPortAdapter(promptFile);

        // When
        PromptLoadingResult result = adapter.loadPrompt();

        // Then
        assertThat(result).isInstanceOf(PromptLoadingFailure.class);
        PromptLoadingFailure failure = (PromptLoadingFailure) result;
        assertThat(failure.failureReason()).isEqualTo("EMPTY_CONTENT");
        assertThat(failure.failureMessage()).contains("empty or contains only whitespace");
    }

    @Test
    void loadPrompt_shouldReturnFailure_whenPromptContainsOnlyWhitespace() throws IOException {
        // Given
        Path promptFile = tempDir.resolve("whitespace_only_prompt.txt");
        Files.writeString(promptFile, "   \n\n  \t  \n", StandardCharsets.UTF_8);
        adapter = new FilesystemPromptPortAdapter(promptFile);

        // When
        PromptLoadingResult result = adapter.loadPrompt();

        // Then
        assertThat(result).isInstanceOf(PromptLoadingFailure.class);
        PromptLoadingFailure failure = (PromptLoadingFailure) result;
        assertThat(failure.failureReason()).isEqualTo("EMPTY_CONTENT");
        assertThat(failure.failureMessage()).contains("empty or contains only whitespace");
    }

    @Test
    void loadPrompt_shouldReturnFailure_withIOError_whenFileCannotBeRead() throws IOException {
        // Given
        Path promptFile = tempDir.resolve("readable_prompt.txt");
        Files.writeString(promptFile, "Test prompt content", StandardCharsets.UTF_8);
        adapter = new FilesystemPromptPortAdapter(promptFile);

        // Delete the file before calling loadPrompt to simulate a file disappearing
        Files.delete(promptFile);

        // When
        PromptLoadingResult result = adapter.loadPrompt();

        // Then
        assertThat(result).isInstanceOf(PromptLoadingFailure.class);
        PromptLoadingFailure failure = (PromptLoadingFailure) result;
        assertThat(failure.failureReason()).isEqualTo("FILE_NOT_FOUND");
    }

    @Test
    void loadPrompt_shouldThrowNullPointerException_whenPromptFilePathIsNull() {
        // When & Then
        assertThatThrownBy(() -> new FilesystemPromptPortAdapter(null))
            .isInstanceOf(NullPointerException.class)
            .hasMessage("promptFilePath must not be null");
    }

    @Test
    void loadPrompt_shouldPreserveMultilineContent() throws IOException {
        // Given
        String promptContent = "Line 1\nLine 2\nLine 3";
        Path promptFile = tempDir.resolve("multiline_prompt.txt");
        Files.writeString(promptFile, promptContent, StandardCharsets.UTF_8);
        adapter = new FilesystemPromptPortAdapter(promptFile);

        // When
        PromptLoadingResult result = adapter.loadPrompt();

        // Then
        assertThat(result).isInstanceOf(PromptLoadingSuccess.class);
        PromptLoadingSuccess success = (PromptLoadingSuccess) result;
        assertThat(success.promptContent()).isEqualTo(promptContent);
    }

    @Test
    void loadPrompt_shouldBeDeterministic_whenCalledMultipleTimes() throws IOException {
        // Given
        String promptContent = "Deterministic prompt content";
        Path promptFile = tempDir.resolve("stable_prompt.txt");
        Files.writeString(promptFile, promptContent, StandardCharsets.UTF_8);
        adapter = new FilesystemPromptPortAdapter(promptFile);

        // When
        PromptLoadingResult result1 = adapter.loadPrompt();
        PromptLoadingResult result2 = adapter.loadPrompt();

        // Then
        assertThat(result1).isInstanceOf(PromptLoadingSuccess.class);
        assertThat(result2).isInstanceOf(PromptLoadingSuccess.class);

        PromptLoadingSuccess success1 = (PromptLoadingSuccess) result1;
        PromptLoadingSuccess success2 = (PromptLoadingSuccess) result2;

        assertThat(success1.promptContent()).isEqualTo(success2.promptContent());
        assertThat(success1.promptIdentifier()).isEqualTo(success2.promptIdentifier());
    }

    // -------------------------------------------------------------------------
    // savePrompt tests
    // -------------------------------------------------------------------------

    @Test
    void savePrompt_shouldReturnSaved_whenTargetDirExistsAndWriteSucceeds() throws IOException {
        // Given
        Path promptFile = tempDir.resolve("prompt_save.txt");
        adapter = new FilesystemPromptPortAdapter(promptFile);
        String content = "Mein Prompt-Inhalt";

        // When
        PromptSaveResult result = adapter.savePrompt(content);

        // Then
        assertThat(result).isInstanceOf(PromptSaveResult.Saved.class);
        PromptSaveResult.Saved saved = (PromptSaveResult.Saved) result;
        assertThat(saved.absolutePath()).contains("prompt_save.txt");
        assertThat(Files.readString(promptFile, StandardCharsets.UTF_8)).isEqualTo(content);
    }

    @Test
    void savePrompt_shouldPreserveUtf8Content_includingUmlauts() throws IOException {
        // Given
        Path promptFile = tempDir.resolve("prompt_umlaut.txt");
        adapter = new FilesystemPromptPortAdapter(promptFile);
        String content = "Ärger mit Überschriften und Schluß";

        // When
        PromptSaveResult result = adapter.savePrompt(content);

        // Then
        assertThat(result).isInstanceOf(PromptSaveResult.Saved.class);
        assertThat(Files.readString(promptFile, StandardCharsets.UTF_8)).isEqualTo(content);
    }

    @Test
    void savePrompt_shouldPreserveLineEndings_withoutNormalization() throws IOException {
        // Given
        Path promptFile = tempDir.resolve("prompt_lineendings.txt");
        adapter = new FilesystemPromptPortAdapter(promptFile);
        String content = "Zeile 1\r\nZeile 2\nZeile 3\r\n";

        // When
        PromptSaveResult result = adapter.savePrompt(content);

        // Then
        assertThat(result).isInstanceOf(PromptSaveResult.Saved.class);
        byte[] raw = Files.readAllBytes(promptFile);
        assertThat(new String(raw, StandardCharsets.UTF_8)).isEqualTo(content);
    }

    @Test
    void savePrompt_shouldOverwriteExistingFile_atomically() throws IOException {
        // Given
        Path promptFile = tempDir.resolve("prompt_overwrite.txt");
        Files.writeString(promptFile, "Alter Inhalt", StandardCharsets.UTF_8);
        adapter = new FilesystemPromptPortAdapter(promptFile);
        String newContent = "Neuer Inhalt";

        // When
        PromptSaveResult result = adapter.savePrompt(newContent);

        // Then
        assertThat(result).isInstanceOf(PromptSaveResult.Saved.class);
        assertThat(Files.readString(promptFile, StandardCharsets.UTF_8)).isEqualTo(newContent);
    }

    @Test
    void savePrompt_shouldReturnTargetDirectoryMissing_whenDirectoryDoesNotExist() {
        // Given
        Path nonExistentDir = tempDir.resolve("missing-subdir");
        Path promptFile = nonExistentDir.resolve("prompt.txt");
        adapter = new FilesystemPromptPortAdapter(promptFile);

        // When
        PromptSaveResult result = adapter.savePrompt("Inhalt");

        // Then
        assertThat(result).isInstanceOf(PromptSaveResult.TargetDirectoryMissing.class);
        PromptSaveResult.TargetDirectoryMissing missing = (PromptSaveResult.TargetDirectoryMissing) result;
        assertThat(missing.message()).contains("missing-subdir");
    }

    @Test
    void savePrompt_shouldThrowNullPointerException_whenContentIsNull() throws IOException {
        // Given
        Path promptFile = tempDir.resolve("prompt_null.txt");
        adapter = new FilesystemPromptPortAdapter(promptFile);

        // When & Then
        assertThatThrownBy(() -> adapter.savePrompt(null))
                .isInstanceOf(NullPointerException.class)
                .hasMessage("content must not be null");
    }

    @Test
    void savePrompt_shouldLeaveDirClean_whenTargetDirectoryIsMissing() {
        // Given – Verzeichnis existiert nicht; keine Temp-Datei soll zurückbleiben
        Path nonExistentDir = tempDir.resolve("ghost-dir");
        Path promptFile = nonExistentDir.resolve("prompt.txt");
        adapter = new FilesystemPromptPortAdapter(promptFile);

        // When
        PromptSaveResult result = adapter.savePrompt("Inhalt");

        // Then
        assertThat(result).isInstanceOf(PromptSaveResult.TargetDirectoryMissing.class);
        // Verzeichnis wurde nicht angelegt (da Directory-Check fehlschlug)
        assertThat(nonExistentDir).doesNotExist();
    }

    @Test
    void savePrompt_roundTrip_loadAfterSaveReturnsSameContent() throws IOException {
        // Given
        Path promptFile = tempDir.resolve("prompt_roundtrip.txt");
        adapter = new FilesystemPromptPortAdapter(promptFile);
        String content = "Runde-Trip-Inhalt\nMit mehreren Zeilen.";

        // When
        PromptSaveResult saveResult = adapter.savePrompt(content);
        PromptLoadingResult loadResult = adapter.loadPrompt();

        // Then
        assertThat(saveResult).isInstanceOf(PromptSaveResult.Saved.class);
        assertThat(loadResult).isInstanceOf(PromptLoadingSuccess.class);
        PromptLoadingSuccess success = (PromptLoadingSuccess) loadResult;
        // loadPrompt trims the content; trim the expected too
        assertThat(success.promptContent()).isEqualTo(content.trim());
    }
}
