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

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

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

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.PromptPort;
import de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult;
import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome;
import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionSuggestion;
import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort;
import de.gecheckt.pdf.umbenenner.domain.model.PromptIdentifier;

/**
 * Unit-Tests für {@link DefaultPromptEditorUseCase}.
 * <p>
 * Prüft die Delegation an {@link PromptPort} und {@link ResourceCreationPort}
 * sowie die Null-Prüfungen am Konstruktor und an den Methoden.
 */
class DefaultPromptEditorUseCaseTest {

    private static final String STUB_CONTENT = "Mein Test-Prompt";
    private static final PromptIdentifier STUB_IDENTIFIER = new PromptIdentifier("test-prompt.txt");

    private StubPromptPort stubPromptPort;
    private StubResourceCreationPort stubResourceCreationPort;
    private DefaultPromptEditorUseCase useCase;

    @BeforeEach
    void setUp() {
        stubPromptPort = new StubPromptPort();
        stubResourceCreationPort = new StubResourceCreationPort();
        useCase = new DefaultPromptEditorUseCase(stubPromptPort, stubResourceCreationPort);
    }

    // -------------------------------------------------------------------------
    // Konstruktor
    // -------------------------------------------------------------------------

    @Test
    void constructor_shouldThrowNullPointerException_whenPromptPortIsNull() {
        assertThatThrownBy(() -> new DefaultPromptEditorUseCase(null, stubResourceCreationPort))
                .isInstanceOf(NullPointerException.class)
                .hasMessageContaining("promptPort");
    }

    @Test
    void constructor_shouldThrowNullPointerException_whenResourceCreationPortIsNull() {
        assertThatThrownBy(() -> new DefaultPromptEditorUseCase(stubPromptPort, null))
                .isInstanceOf(NullPointerException.class)
                .hasMessageContaining("resourceCreationPort");
    }

    // -------------------------------------------------------------------------
    // loadPrompt
    // -------------------------------------------------------------------------

    @Test
    void loadPrompt_shouldDelegateToPromptPort_andReturnSuccess() {
        // Given
        stubPromptPort.loadResult = new PromptLoadingSuccess(STUB_IDENTIFIER, STUB_CONTENT);

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

        // Then
        assertThat(result).isInstanceOf(PromptLoadingSuccess.class);
        PromptLoadingSuccess success = (PromptLoadingSuccess) result;
        assertThat(success.promptContent()).isEqualTo(STUB_CONTENT);
        assertThat(success.promptIdentifier()).isEqualTo(STUB_IDENTIFIER);
        assertThat(stubPromptPort.loadCallCount).isEqualTo(1);
    }

    @Test
    void loadPrompt_shouldDelegateToPromptPort_andReturnFailure() {
        // Given
        stubPromptPort.loadResult = new PromptLoadingFailure("FILE_NOT_FOUND", "Datei nicht vorhanden");

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

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

    // -------------------------------------------------------------------------
    // savePrompt
    // -------------------------------------------------------------------------

    @Test
    void savePrompt_shouldDelegateToPromptPort_andReturnSaved() {
        // Given
        stubPromptPort.saveResult = new PromptSaveResult.Saved("/some/path/prompt.txt");

        // When
        PromptSaveResult result = useCase.savePrompt(STUB_CONTENT);

        // Then
        assertThat(result).isInstanceOf(PromptSaveResult.Saved.class);
        assertThat(stubPromptPort.lastSavedContent).isEqualTo(STUB_CONTENT);
        assertThat(stubPromptPort.saveCallCount).isEqualTo(1);
    }

    @Test
    void savePrompt_shouldDelegateToPromptPort_andReturnWriteFailed() {
        // Given
        stubPromptPort.saveResult = new PromptSaveResult.WriteFailed("Schreibfehler", null);

        // When
        PromptSaveResult result = useCase.savePrompt(STUB_CONTENT);

        // Then
        assertThat(result).isInstanceOf(PromptSaveResult.WriteFailed.class);
    }

    @Test
    void savePrompt_shouldThrowNullPointerException_whenContentIsNull() {
        assertThatThrownBy(() -> useCase.savePrompt(null))
                .isInstanceOf(NullPointerException.class)
                .hasMessageContaining("content");
    }

    // -------------------------------------------------------------------------
    // createDefaultPromptIfMissing
    // -------------------------------------------------------------------------

    @Test
    void createDefaultPromptIfMissing_shouldDelegateToResourceCreationPort_andReturnApplied() {
        // Given
        CorrectionSuggestion.CreatePromptFile suggestion =
                new CorrectionSuggestion.CreatePromptFile(
                        "/some/prompt.txt", "Standard anlegen", 60);
        CorrectionOutcome.Applied applied = new CorrectionOutcome.Applied(
                suggestion, "Standard-Prompt-Datei wurde angelegt.");
        stubResourceCreationPort.createPromptFileResult = applied;

        // When
        CorrectionOutcome result = useCase.createDefaultPromptIfMissing(suggestion);

        // Then
        assertThat(result).isInstanceOf(CorrectionOutcome.Applied.class);
        assertThat(stubResourceCreationPort.createPromptFileCallCount).isEqualTo(1);
        assertThat(stubResourceCreationPort.lastSuggestion).isSameAs(suggestion);
    }

    @Test
    void createDefaultPromptIfMissing_shouldThrowNullPointerException_whenSuggestionIsNull() {
        assertThatThrownBy(() -> useCase.createDefaultPromptIfMissing(null))
                .isInstanceOf(NullPointerException.class)
                .hasMessageContaining("suggestion");
    }

    // -------------------------------------------------------------------------
    // Test-Stubs
    // -------------------------------------------------------------------------

    private static class StubPromptPort implements PromptPort {
        PromptLoadingResult loadResult = new PromptLoadingSuccess(
                new PromptIdentifier("stub.txt"), "Stub-Inhalt");
        PromptSaveResult saveResult = new PromptSaveResult.Saved("/stub/path.txt");
        int loadCallCount = 0;
        int saveCallCount = 0;
        String lastSavedContent = null;

        @Override
        public PromptLoadingResult loadPrompt() {
            loadCallCount++;
            return loadResult;
        }

        @Override
        public PromptSaveResult savePrompt(String content) {
            saveCallCount++;
            lastSavedContent = content;
            return saveResult;
        }
    }

    private static class StubResourceCreationPort implements ResourceCreationPort {
        CorrectionOutcome createPromptFileResult = new CorrectionOutcome.Applied(
                new CorrectionSuggestion.CreatePromptFile("/stub.txt", "Stub", 60),
                "Angelegt.");
        int createPromptFileCallCount = 0;
        CorrectionSuggestion.CreatePromptFile lastSuggestion = null;

        @Override
        public CorrectionOutcome createDirectory(CorrectionSuggestion.CreateDirectory suggestion) {
            return new CorrectionOutcome.NotAttempted(suggestion, "Nicht implementiert im Stub.");
        }

        @Override
        public CorrectionOutcome createPromptFile(CorrectionSuggestion.CreatePromptFile suggestion) {
            createPromptFileCallCount++;
            lastSuggestion = suggestion;
            return createPromptFileResult;
        }

        @Override
        public CorrectionOutcome prepareSqlitePath(CorrectionSuggestion.PrepareSqlitePath suggestion) {
            return new CorrectionOutcome.NotAttempted(suggestion, "Nicht implementiert im Stub.");
        }
    }
}
