package de.gecheckt.pdf.umbenenner.application.validation.technicaltest;

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

import java.util.List;

import org.junit.jupiter.api.Test;

import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor;
import de.gecheckt.pdf.umbenenner.application.validation.editor.EditorConfigurationValidator;
import de.gecheckt.pdf.umbenenner.application.validation.editor.EditorValidationInput;

/**
 * Unit-Tests für {@link TechnicalTestOrchestrator}.
 * <p>
 * Prüft insbesondere, dass alle drei Blöcke immer vollständig durchlaufen werden
 * (kein Frühabbruch), auch wenn ein Block eine Exception wirft, und dass der
 * zurückgegebene Bericht immer genau zwölf Einträge enthält.
 */
class TechnicalTestOrchestratorTest {

    // ------------------------------------------------------------------ Hilfsmethoden

    /**
     * Erstellt eine minimal gültige {@link EditorValidationInput} für den Claude-Provider.
     * Verwendet {@code max.text.characters=500} um wirtschaftliche Warnungen zu vermeiden.
     */
    private static EditorValidationInput validClaudeInput() {
        return new EditorValidationInput(
                "claude",
                "/src", "/tgt", "/db.sqlite", "/prompt.txt",
                "3", "10", "500", "60",
                "https://api.anthropic.com", "claude-3-sonnet", "30",
                EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-test",
                "https://api.openai.com", "gpt-4", "30",
                EffectiveApiKeyDescriptor.absent(), "");
    }

    /**
     * Erstellt eine {@link EditorValidationInput} mit leerem Aktiv-Provider (erzeugt Fehler im Validierungsblock).
     */
    private static EditorValidationInput emptyProviderInput() {
        return new EditorValidationInput(
                "",          // leerer aktiver Provider → Fehler in Block 1
                "", "", "", "",
                "", "", "", "",
                "", "", "",
                EffectiveApiKeyDescriptor.absent(), "",
                "", "", "",
                EffectiveApiKeyDescriptor.absent(), "");
    }

    /** No-op {@link PathCheckPort}: alle Prüfungen liefern {@code false}. */
    private static PathCheckPort noOpPathCheckPort() {
        return new PathCheckPort() {
            @Override public boolean isDirectoryReadable(String p) { return false; }
            @Override public boolean isDirectoryWritableOrCreatable(String p) { return false; }
            @Override public boolean isFileReadable(String p) { return false; }
            @Override public boolean isSqlitePathUsable(String p) { return false; }
        };
    }

    /** {@link PathCheckPort}: alle Prüfungen liefern {@code true}. */
    private static PathCheckPort allOkPathCheckPort() {
        return new PathCheckPort() {
            @Override public boolean isDirectoryReadable(String p) { return true; }
            @Override public boolean isDirectoryWritableOrCreatable(String p) { return true; }
            @Override public boolean isFileReadable(String p) { return true; }
            @Override public boolean isSqlitePathUsable(String p) { return true; }
        };
    }

    /** {@link ProviderTechnicalTestService} der immer fünf {@link CheckpointResult.Success} liefert. */
    private static ProviderTechnicalTestService allSuccessProviderService() {
        return new ProviderTechnicalTestService(
                req -> new de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog
                        .ModelCatalogResult.Success(
                                req.providerIdentifier(),
                                List.of("claude-3-sonnet", "gpt-4"),
                                java.time.Instant.now()),
                (family, propertyValue) -> EffectiveApiKeyDescriptor.fromPropertyFile());
    }

    /** {@link ProviderTechnicalTestService} der immer eine RuntimeException wirft. */
    private static ProviderTechnicalTestService throwingProviderService() {
        return new ProviderTechnicalTestService(
                req -> { throw new RuntimeException("Simulierter Provider-Fehler"); },
                (family, propertyValue) -> EffectiveApiKeyDescriptor.fromPropertyFile());
    }

    /** No-op {@link LogDiagnosticsPort}: gibt immer einen leeren Optional zurück. */
    private static LogDiagnosticsPort noOpLogDiagnosticsPort() {
        return () -> java.util.Optional.empty();
    }

    private static CheckpointResult findById(List<CheckpointResult> results, CheckpointId id) {
        return results.stream()
                .filter(r -> r.checkpointId() == id)
                .findFirst()
                .orElseThrow(() -> new AssertionError("No result for CheckpointId: " + id));
    }

    // ------------------------------------------------------------------ Tests: Vollständig grüner Pfad

    /**
     * Alle drei Blöcke liefern Erfolg: der Bericht enthält genau 12 Success-Einträge.
     */
    @Test
    void allBlocksSucceed_reportContainsTwelveSuccessEntries() {
        TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
                new EditorConfigurationValidator(),
                allOkPathCheckPort(),
                allSuccessProviderService(),
                noOpLogDiagnosticsPort());

        TechnicalTestReport report = orchestrator.run(
                TechnicalTestRequest.of(validClaudeInput()));

        assertThat(report.size()).isEqualTo(12);
        assertThat(report.results())
                .allSatisfy(r -> assertThat(r).isInstanceOf(CheckpointResult.Success.class));
        assertThat(report.hasErrors()).isFalse();
        assertThat(report.hasWarnings()).isFalse();
    }

    // ------------------------------------------------------------------ Tests: Kein Frühabbruch

    /**
     * Bericht enthält immer genau 12 Einträge, auch wenn Block 2 und Block 3 alle Fehler liefern.
     */
    @Test
    void alwaysTwelveCheckpointsInReport_evenWithFailures() {
        TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
                new EditorConfigurationValidator(),
                noOpPathCheckPort(),   // Block 2: alle Pfade nicht vorhanden
                throwingProviderService(), // Block 3: Exception
                noOpLogDiagnosticsPort());

        TechnicalTestReport report = orchestrator.run(
                TechnicalTestRequest.of(emptyProviderInput()));

        assertThat(report.size()).isEqualTo(12);
    }

    // ------------------------------------------------------------------ Tests: Block 1 - Exception führt zu Failure

    /**
     * Wenn der lokale Validator eine Exception wirft, werden die beiden Block-1-Checkpoints
     * als Failure mit "Interner Fehler" markiert. Blöcke 2 und 3 laufen trotzdem durch.
     */
    @Test
    void localValidatorThrowsException_block1CheckpointsAreFailure_otherBlocksRunThrough() {
        // EditorConfigurationValidator der eine Exception wirft
        EditorConfigurationValidator throwingValidator = new EditorConfigurationValidator() {
            @Override
            public de.gecheckt.pdf.umbenenner.application.validation.editor.EditorValidationReport
                    validate(EditorValidationInput input) {
                throw new RuntimeException("Simulierter Validierungsfehler");
            }
        };

        TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
                throwingValidator,
                allOkPathCheckPort(),
                allSuccessProviderService(),
                noOpLogDiagnosticsPort());

        TechnicalTestReport report = orchestrator.run(
                TechnicalTestRequest.of(validClaudeInput()));

        assertThat(report.size()).isEqualTo(12);

        // Block-1-Checkpoints müssen Failure mit "Interner Fehler" sein
        CheckpointResult basicValidation = findById(report.results(),
                CheckpointId.CONFIGURATION_BASIC_VALIDATION);
        assertThat(basicValidation).isInstanceOf(CheckpointResult.Failure.class);
        assertThat(((CheckpointResult.Failure) basicValidation).message())
                .contains("Interner Fehler");

        CheckpointResult providerConfig = findById(report.results(),
                CheckpointId.PROVIDER_CONFIGURATION);
        assertThat(providerConfig).isInstanceOf(CheckpointResult.Failure.class);
        assertThat(((CheckpointResult.Failure) providerConfig).message())
                .contains("Interner Fehler");

        // Block-2-Checkpoints laufen durch (Pfade sind "ok" durch allOkPathCheckPort)
        assertThat(findById(report.results(), CheckpointId.SOURCE_FOLDER_PRESENT))
                .isInstanceOf(CheckpointResult.Success.class);
        assertThat(findById(report.results(), CheckpointId.TARGET_FOLDER_USABLE))
                .isInstanceOf(CheckpointResult.Success.class);
        assertThat(findById(report.results(), CheckpointId.PROMPT_FILE_PRESENT))
                .isInstanceOf(CheckpointResult.Success.class);
        assertThat(findById(report.results(), CheckpointId.SQLITE_PATH_USABLE))
                .isInstanceOf(CheckpointResult.Success.class);
    }

    // ------------------------------------------------------------------ Tests: Block 3 - Exception führt zu Failure

    /**
     * Wenn der ProviderTechnicalTestService eine Exception wirft, werden die fünf Block-3-Checkpoints
     * als Failure mit "Interner Fehler" markiert. Blöcke 1 und 2 laufen trotzdem durch.
     */
    @Test
    void providerServiceThrowsException_block3CheckpointsAreFailure_otherBlocksRunThrough() {
        TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
                new EditorConfigurationValidator(),
                allOkPathCheckPort(),
                throwingProviderService(),
                noOpLogDiagnosticsPort());

        TechnicalTestReport report = orchestrator.run(
                TechnicalTestRequest.of(validClaudeInput()));

        assertThat(report.size()).isEqualTo(12);

        // Block-3-Checkpoints müssen Failure mit "Interner Fehler" sein
        List<CheckpointId> block3Ids = List.of(
                CheckpointId.API_KEY_PRESENT,
                CheckpointId.BASE_URL_REACHABLE,
                CheckpointId.API_KEY_ACCEPTED,
                CheckpointId.MODEL_LIST_AVAILABLE,
                CheckpointId.SELECTED_MODEL_PLAUSIBLE);

        for (CheckpointId id : block3Ids) {
            CheckpointResult result = findById(report.results(), id);
            assertThat(result).as("Block-3-Checkpoint %s muss Failure sein", id)
                    .isInstanceOf(CheckpointResult.Failure.class);
            assertThat(((CheckpointResult.Failure) result).message())
                    .as("Fehlermeldung für %s muss 'Interner Fehler' enthalten", id)
                    .contains("Interner Fehler");
        }

        // Block-1-Checkpoints laufen durch
        assertThat(findById(report.results(), CheckpointId.CONFIGURATION_BASIC_VALIDATION))
                .isInstanceOf(CheckpointResult.Success.class);

        // Block-2-Checkpoints laufen durch
        assertThat(findById(report.results(), CheckpointId.SOURCE_FOLDER_PRESENT))
                .isInstanceOf(CheckpointResult.Success.class);
    }

    // ------------------------------------------------------------------ Tests: CorrectionSuggestion

    /**
     * Wenn der Zielordner nicht vorhanden aber anlegbar ist, enthält der Checkpoint
     * {@link CheckpointId#TARGET_FOLDER_USABLE} eine {@link CorrectionSuggestion.CreateDirectory}.
     */
    @Test
    void targetFolderNotPresent_correctionSuggestionIsCreateDirectory() {
        // PathCheckPort: Zielordner nicht schreibbar (returned false), aber wir testen
        // dass eine CreateDirectory-Suggestion angehängt wird
        PathCheckPort pathPort = new PathCheckPort() {
            @Override public boolean isDirectoryReadable(String p) { return true; }
            @Override public boolean isDirectoryWritableOrCreatable(String p) { return false; } // Zielordner
            @Override public boolean isFileReadable(String p) { return true; }
            @Override public boolean isSqlitePathUsable(String p) { return true; }
        };

        TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
                new EditorConfigurationValidator(),
                pathPort,
                allSuccessProviderService(),
                noOpLogDiagnosticsPort());

        TechnicalTestReport report = orchestrator.run(
                TechnicalTestRequest.of(validClaudeInput()));

        CheckpointResult targetFolderResult = findById(report.results(),
                CheckpointId.TARGET_FOLDER_USABLE);
        assertThat(targetFolderResult).isInstanceOf(CheckpointResult.Failure.class);
        CheckpointResult.Failure failure = (CheckpointResult.Failure) targetFolderResult;
        assertThat(failure.hasCorrectionSuggestion()).isTrue();
        assertThat(failure.correctionSuggestion().orElseThrow())
                .isInstanceOf(CorrectionSuggestion.CreateDirectory.class);
    }

    /**
     * Wenn die Prompt-Datei fehlt, enthält der Checkpoint {@link CheckpointId#PROMPT_FILE_PRESENT}
     * eine {@link CorrectionSuggestion.CreatePromptFile}.
     */
    @Test
    void promptFileMissing_correctionSuggestionIsCreatePromptFile() {
        PathCheckPort pathPort = new PathCheckPort() {
            @Override public boolean isDirectoryReadable(String p) { return true; }
            @Override public boolean isDirectoryWritableOrCreatable(String p) { return true; }
            @Override public boolean isFileReadable(String p) { return false; } // Prompt-Datei fehlt
            @Override public boolean isSqlitePathUsable(String p) { return true; }
        };

        TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
                new EditorConfigurationValidator(),
                pathPort,
                allSuccessProviderService(),
                noOpLogDiagnosticsPort());

        TechnicalTestReport report = orchestrator.run(
                TechnicalTestRequest.of(validClaudeInput()));

        CheckpointResult promptResult = findById(report.results(),
                CheckpointId.PROMPT_FILE_PRESENT);
        assertThat(promptResult).isInstanceOf(CheckpointResult.Failure.class);
        CheckpointResult.Failure failure = (CheckpointResult.Failure) promptResult;
        assertThat(failure.hasCorrectionSuggestion()).isTrue();
        assertThat(failure.correctionSuggestion().orElseThrow())
                .isInstanceOf(CorrectionSuggestion.CreatePromptFile.class);
    }

    /**
     * Wenn der SQLite-Pfad nicht nutzbar ist, enthält der Checkpoint
     * {@link CheckpointId#SQLITE_PATH_USABLE} eine {@link CorrectionSuggestion.PrepareSqlitePath}.
     */
    @Test
    void sqlitePathNotUsable_correctionSuggestionIsPrepareSqlitePath() {
        PathCheckPort pathPort = new PathCheckPort() {
            @Override public boolean isDirectoryReadable(String p) { return true; }
            @Override public boolean isDirectoryWritableOrCreatable(String p) { return true; }
            @Override public boolean isFileReadable(String p) { return true; }
            @Override public boolean isSqlitePathUsable(String p) { return false; } // SQLite-Pfad nicht nutzbar
        };

        TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
                new EditorConfigurationValidator(),
                pathPort,
                allSuccessProviderService(),
                noOpLogDiagnosticsPort());

        TechnicalTestReport report = orchestrator.run(
                TechnicalTestRequest.of(validClaudeInput()));

        CheckpointResult sqliteResult = findById(report.results(),
                CheckpointId.SQLITE_PATH_USABLE);
        assertThat(sqliteResult).isInstanceOf(CheckpointResult.Failure.class);
        CheckpointResult.Failure failure = (CheckpointResult.Failure) sqliteResult;
        assertThat(failure.hasCorrectionSuggestion()).isTrue();
        assertThat(failure.correctionSuggestion().orElseThrow())
                .isInstanceOf(CorrectionSuggestion.PrepareSqlitePath.class);
    }

    // ------------------------------------------------------------------ Tests: Korrekte Checkpoint-Reihenfolge

    /**
     * Der Bericht enthält genau die erwarteten 12 Checkpoint-IDs.
     */
    @Test
    void report_containsAllExpectedCheckpointIds() {
        TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
                new EditorConfigurationValidator(),
                noOpPathCheckPort(),
                allSuccessProviderService(),
                noOpLogDiagnosticsPort());

        TechnicalTestReport report = orchestrator.run(
                TechnicalTestRequest.of(validClaudeInput()));

        List<CheckpointId> actualIds = report.results().stream()
                .map(CheckpointResult::checkpointId)
                .toList();

        assertThat(actualIds).containsExactlyInAnyOrder(
                CheckpointId.CONFIGURATION_BASIC_VALIDATION,
                CheckpointId.PROVIDER_CONFIGURATION,
                CheckpointId.PROMPT_FILE_PRESENT,
                CheckpointId.SOURCE_FOLDER_PRESENT,
                CheckpointId.TARGET_FOLDER_USABLE,
                CheckpointId.SQLITE_PATH_USABLE,
                CheckpointId.LOG_DIRECTORY_USABLE,
                CheckpointId.API_KEY_PRESENT,
                CheckpointId.BASE_URL_REACHABLE,
                CheckpointId.API_KEY_ACCEPTED,
                CheckpointId.MODEL_LIST_AVAILABLE,
                CheckpointId.SELECTED_MODEL_PLAUSIBLE);
    }

    // ------------------------------------------------------------------ Tests: Prompt-Pfad-Fallback (AP-007)

    /**
     * Kein Prompt-Pfad im Editorzustand, aber configFilePath gesetzt →
     * Suggestion nutzt den Standardpfad {@code <config-parent>/prompt.txt}.
     */
    @Test
    void promptFileMissing_noConfiguredPath_withConfigFilePath_suggestionUsesDefaultPath() {
        // Editorzustand ohne Prompt-Pfad, aber mit configFilePath
        EditorValidationInput inputWithoutPrompt = new EditorValidationInput(
                "claude",
                "/src", "/tgt", "/db.sqlite",
                "",    // kein Prompt-Pfad
                "3", "10", "500", "60",
                "https://api.anthropic.com", "claude-3-sonnet", "30",
                EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-test",
                "https://api.openai.com", "gpt-4", "30",
                EffectiveApiKeyDescriptor.absent(), "");

        // PathCheckPort: Dateien fehlen, aber Elternordner sind schreibbar
        PathCheckPort pathPort = new PathCheckPort() {
            @Override public boolean isDirectoryReadable(String p) { return true; }
            @Override public boolean isDirectoryWritableOrCreatable(String p) { return true; }
            @Override public boolean isFileReadable(String p) { return false; } // Prompt fehlt
            @Override public boolean isSqlitePathUsable(String p) { return true; }
        };

        TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
                new EditorConfigurationValidator(),
                pathPort,
                allSuccessProviderService(),
                noOpLogDiagnosticsPort());

        // configFilePath = C:/config/application.properties → Elternordner = C:/config
        TechnicalTestReport report = orchestrator.run(
                new TechnicalTestRequest(inputWithoutPrompt, "C:/config/application.properties", ""));

        CheckpointResult promptResult = findById(report.results(), CheckpointId.PROMPT_FILE_PRESENT);
        assertThat(promptResult).isInstanceOf(CheckpointResult.Failure.class);
        CheckpointResult.Failure failure = (CheckpointResult.Failure) promptResult;
        assertThat(failure.hasCorrectionSuggestion()).isTrue();
        CorrectionSuggestion suggestion = failure.correctionSuggestion().orElseThrow();
        assertThat(suggestion).isInstanceOf(CorrectionSuggestion.CreatePromptFile.class);
        // Pfad muss im Elternordner der Konfigurationsdatei liegen
        String suggestionPath = ((CorrectionSuggestion.CreatePromptFile) suggestion).path();
        assertThat(suggestionPath).contains("config");
        assertThat(suggestionPath).contains("prompt.txt");
    }

    /**
     * Kein Prompt-Pfad im Editorzustand, kein configFilePath →
     * Suggestion nutzt Fallback-Pfad {@code config/prompt.txt}.
     */
    @Test
    void promptFileMissing_noConfiguredPath_noConfigFilePath_suggestionUsesFallbackPath() {
        EditorValidationInput inputWithoutPrompt = new EditorValidationInput(
                "claude",
                "/src", "/tgt", "/db.sqlite",
                "",    // kein Prompt-Pfad
                "3", "10", "500", "60",
                "https://api.anthropic.com", "claude-3-sonnet", "30",
                EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-test",
                "https://api.openai.com", "gpt-4", "30",
                EffectiveApiKeyDescriptor.absent(), "");

        PathCheckPort pathPort = new PathCheckPort() {
            @Override public boolean isDirectoryReadable(String p) { return true; }
            @Override public boolean isDirectoryWritableOrCreatable(String p) { return true; }
            @Override public boolean isFileReadable(String p) { return false; } // Prompt fehlt
            @Override public boolean isSqlitePathUsable(String p) { return true; }
        };

        TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
                new EditorConfigurationValidator(),
                pathPort,
                allSuccessProviderService(),
                noOpLogDiagnosticsPort());

        // Kein configFilePath → Fallback auf config/prompt.txt
        TechnicalTestReport report = orchestrator.run(
                TechnicalTestRequest.of(inputWithoutPrompt));

        CheckpointResult promptResult = findById(report.results(), CheckpointId.PROMPT_FILE_PRESENT);
        assertThat(promptResult).isInstanceOf(CheckpointResult.Failure.class);
        CheckpointResult.Failure failure = (CheckpointResult.Failure) promptResult;
        assertThat(failure.hasCorrectionSuggestion()).isTrue();
        String suggestionPath = ((CorrectionSuggestion.CreatePromptFile) failure.correctionSuggestion().orElseThrow()).path();
        assertThat(suggestionPath).contains("config");
        assertThat(suggestionPath).contains("prompt.txt");
    }

    /**
     * Prompt-Datei fehlt, aber Elternordner ist nicht beschreibbar →
     * keine Suggestion, stattdessen Failure mit Meldung „bitte manuell anlegen".
     */
    @Test
    void promptFileMissing_parentNotWritable_noSuggestion_failureWithManualHint() {
        PathCheckPort pathPort = new PathCheckPort() {
            @Override public boolean isDirectoryReadable(String p) { return true; }
            @Override public boolean isDirectoryWritableOrCreatable(String p) { return false; } // Elternordner nicht schreibbar
            @Override public boolean isFileReadable(String p) { return false; } // Prompt fehlt
            @Override public boolean isSqlitePathUsable(String p) { return true; }
        };

        TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
                new EditorConfigurationValidator(),
                pathPort,
                allSuccessProviderService(),
                noOpLogDiagnosticsPort());

        TechnicalTestReport report = orchestrator.run(
                TechnicalTestRequest.of(validClaudeInput()));

        CheckpointResult promptResult = findById(report.results(), CheckpointId.PROMPT_FILE_PRESENT);
        assertThat(promptResult).isInstanceOf(CheckpointResult.Failure.class);
        CheckpointResult.Failure failure = (CheckpointResult.Failure) promptResult;
        // Keine Suggestion, da Elternordner nicht schreibbar
        assertThat(failure.hasCorrectionSuggestion()).isFalse();
        // Meldung muss Hinweis auf manuelles Anlegen enthalten
        assertThat(failure.message()).contains("manuell anlegen");
    }

    // ------------------------------------------------------------------ Tests: resolvePromptPath (statische Hilfsmethode)

    /**
     * Wenn ein konfigurierter Prompt-Pfad vorhanden ist, wird dieser zurückgegeben.
     */
    @Test
    void resolvePromptPath_configuredPathPresent_returnsConfiguredPath() {
        String result = TechnicalTestOrchestrator.resolvePromptPath(
                "/data/prompt.txt",
                "/config/application.properties");
        assertThat(result).isEqualTo("/data/prompt.txt");
    }

    /**
     * Wenn kein konfigurierter Pfad vorhanden ist, aber ein configFilePath gesetzt ist,
     * wird der Standardpfad im Elternordner der Konfigurationsdatei zurückgegeben.
     */
    @Test
    void resolvePromptPath_noConfiguredPath_withConfigFilePath_returnsParentBasedDefault() {
        // Plattformunabhängig: separator kann / oder \ sein
        String result = TechnicalTestOrchestrator.resolvePromptPath(
                "",
                "C:/myconfig/application.properties");
        assertThat(result).startsWith("C:" + java.io.File.separator + "myconfig");
        assertThat(result).endsWith("prompt.txt");
    }

    /**
     * Wenn weder konfigurierter Pfad noch configFilePath vorhanden sind,
     * wird {@code config/prompt.txt} als Fallback zurückgegeben.
     */
    @Test
    void resolvePromptPath_noConfiguredPath_noConfigFilePath_returnsFallback() {
        String result = TechnicalTestOrchestrator.resolvePromptPath("", "");
        assertThat(result).contains("config");
        assertThat(result).endsWith("prompt.txt");
    }
}
