package de.gecheckt.pdf.umbenenner.adapter.out.bootstrap.validation;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.nio.file.Files;
import java.nio.file.Path;

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

import de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily;
import de.gecheckt.pdf.umbenenner.application.config.provider.MultiProviderConfiguration;
import de.gecheckt.pdf.umbenenner.application.config.provider.ProviderConfiguration;
import de.gecheckt.pdf.umbenenner.application.config.startup.StartConfiguration;

/**
 * Unit tests for {@link StartConfigurationValidator}.
 * <p>
 * Tests cover valid configuration, missing mandatory fields, invalid values,
 * and path relationship validations.
 */
class StartConfigurationValidatorTest {

    private final StartConfigurationValidator validator = new StartConfigurationValidator();

    @TempDir
    Path tempDir;

    /** Helper: builds a minimal valid multi-provider configuration for use in tests. */
    private static MultiProviderConfiguration validMultiProviderConfig() {
        ProviderConfiguration openAiConfig = new ProviderConfiguration(
                "gpt-4", 30, "https://api.example.com", "test-key");
        return new MultiProviderConfiguration(AiProviderFamily.OPENAI_COMPATIBLE, openAiConfig, null);
    }

    @Test
    void validate_successWithValidConfiguration() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                tempDir.resolve("lock.lock"),
                tempDir.resolve("logs"),
                "INFO",
                false
        );

        assertDoesNotThrow(() -> validator.validate(config));
    }

    @Test
    void validate_failsWhenSourceFolderIsNull() {
        StartConfiguration config = new StartConfiguration(
                null,
                tempDir.resolve("target"),
                tempDir.resolve("db.sqlite"),
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                tempDir.resolve("prompt.txt"),
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("source.folder: must not be null"));
    }

    @Test
    void validate_failsWhenTargetFolderIsNull() {
        StartConfiguration config = new StartConfiguration(
                tempDir.resolve("source"),
                null,
                tempDir.resolve("db.sqlite"),
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                tempDir.resolve("prompt.txt"),
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("target.folder: must not be null"));
    }

    @Test
    void validate_failsWhenSqliteFileIsNull() {
        StartConfiguration config = new StartConfiguration(
                tempDir.resolve("source"),
                tempDir.resolve("target"),
                null,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                tempDir.resolve("prompt.txt"),
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("sqlite.file: must not be null"));
    }

    @Test
    void validate_failsWhenMultiProviderConfigurationIsNull() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                null,
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("ai provider configuration: must not be null"));
    }

    @Test
    void validate_failsWhenPromptTemplateFileIsNull() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                null,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("prompt.template.file: must not be null"));
    }

    @Test
    void validate_failsWhenMaxRetriesTransientIsNegative() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                -1,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("max.retries.transient: must be >= 1"));
    }

    @Test
    void validate_failsWhenMaxRetriesTransientIsZero() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source2"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target2"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db2.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt2.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                0,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("max.retries.transient: must be >= 1"),
                "max.retries.transient = 0 is invalid startup configuration");
    }

    @Test
    void validate_failsWhenMaxPagesIsZeroOrNegative() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                0,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("max.pages: must be > 0"));
    }

    @Test
    void validate_failsWhenMaxTextCharactersIsZeroOrNegative() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                -1,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("max.text.characters: must be > 0"));
    }

    @Test
    void validate_maxRetriesTransientOneIsValid() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                1, // maxRetriesTransient = 1 is the minimum valid value
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        assertDoesNotThrow(() -> validator.validate(config));
    }

    @Test
    void validate_failsWhenMaxTextCharactersIsZero() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                0, // maxTextCharacters = 0 ist ungültig
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("max.text.characters: must be > 0"));
    }

    @Test
    void validate_failsWhenSourceFolderDoesNotExist() throws Exception {
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                tempDir.resolve("nonexistent"),
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("source.folder: path does not exist"));
    }

    @Test
    void validate_failsWhenSourceFolderIsNotADirectory() throws Exception {
        Path sourceFile = Files.createFile(tempDir.resolve("sourcefile.txt"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFile,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("source.folder: path is not a directory"));
    }

    @Test
    void validate_succeedsWhenTargetFolderDoesNotExistButParentExists() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                tempDir.resolve("nonexistent-target"),
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        assertDoesNotThrow(() -> validator.validate(config),
                "Validator must accept a target folder that does not yet exist but can be created");
        assertTrue(Files.isDirectory(tempDir.resolve("nonexistent-target")),
                "Target folder must have been created by the validator");
    }

    @Test
    void validate_failsWhenTargetFolderCannotBeCreated() {
        StartConfigurationValidator validatorWithFailingChecker = new StartConfigurationValidator(
                path -> null, // source folder checker always passes
                path -> "- target.folder: path does not exist and could not be created: " + path + " (Permission denied)"
        );

        StartConfiguration config = new StartConfiguration(
                tempDir.resolve("source"),
                tempDir.resolve("uncreatable-target"),
                tempDir.resolve("db.sqlite"),
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                tempDir.resolve("prompt.txt"),
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validatorWithFailingChecker.validate(config)
        );
        assertTrue(exception.getMessage().contains("target.folder: path does not exist and could not be created"),
                "Error message must indicate that the target folder could not be created");
    }

    @Test
    void validate_failsWhenTargetFolderIsNotADirectory() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFile = Files.createFile(tempDir.resolve("targetfile.txt"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFile,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("target.folder: path is not a directory"));
    }

    @Test
    void validate_failsWhenSqliteFileParentDoesNotExist() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                tempDir.resolve("nonexistent/db.sqlite"),
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("sqlite.file: parent directory does not exist"));
    }

    @Test
    void validate_failsWhenPromptTemplateFileDoesNotExist() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                tempDir.resolve("nonexistent.txt"),
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("prompt.template.file: path does not exist"));
    }

    @Test
    void validate_failsWhenPromptTemplateFileIsNotARegularFile() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path dirForPrompt = Files.createDirectory(tempDir.resolve("promptdir"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                dirForPrompt,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("prompt.template.file: path is not a regular file"));
    }

    @Test
    void validate_failsWhenSourceAndTargetAreSamePath() throws Exception {
        Path sameFolder = Files.createDirectory(tempDir.resolve("samefolder"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                sameFolder,
                sameFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("source.folder and target.folder must not resolve to the same path"));
    }

    @Test
    void validate_failsWhenMultipleErrorsOccur() {
        StartConfiguration config = new StartConfiguration(
                null,
                null,
                null,
                null,
                -1,
                0,
                -1,
                60,
                null,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        String message = exception.getMessage();
        assertTrue(message.contains("source.folder: must not be null"));
        assertTrue(message.contains("target.folder: must not be null"));
        assertTrue(message.contains("sqlite.file: must not be null"));
        assertTrue(message.contains("ai provider configuration: must not be null"));
        assertTrue(message.contains("prompt.template.file: must not be null"));
        assertTrue(message.contains("max.retries.transient: must be >= 1"));
        assertTrue(message.contains("max.pages: must be > 0"));
        assertTrue(message.contains("max.text.characters: must be > 0"));
    }

    @Test
    void validate_failsWhenSourceFolderDoesNotExist_mocked() throws Exception {
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                tempDir.resolve("nonexistent"),
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        StartConfigurationValidator.SourceFolderChecker mockChecker = path ->
            "- source.folder: path does not exist: " + path;

        StartConfigurationValidator validatorWithMock = new StartConfigurationValidator(mockChecker);

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validatorWithMock.validate(config)
        );
        assertTrue(exception.getMessage().contains("source.folder: path does not exist"));
    }

    @Test
    void validate_failsWhenSourceFolderIsNotADirectory_mocked() throws Exception {
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                tempDir.resolve("somepath"),
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        StartConfigurationValidator.SourceFolderChecker mockChecker = path ->
            "- source.folder: path is not a directory: " + path;

        StartConfigurationValidator validatorWithMock = new StartConfigurationValidator(mockChecker);

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validatorWithMock.validate(config)
        );
        assertTrue(exception.getMessage().contains("source.folder: path is not a directory"));
    }

    @Test
    void validate_failsWhenSourceFolderIsNotReadable_mocked() throws Exception {
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                tempDir.resolve("somepath"),
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        StartConfigurationValidator.SourceFolderChecker mockChecker = path ->
            "- source.folder: directory is not readable: " + path;

        StartConfigurationValidator validatorWithMock = new StartConfigurationValidator(mockChecker);

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validatorWithMock.validate(config)
        );
        assertTrue(exception.getMessage().contains("source.folder: directory is not readable"));
    }

    @Test
    void validate_succeedsWhenSourceFolderIsValid_mocked() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        StartConfigurationValidator.SourceFolderChecker mockChecker = path -> null;

        StartConfigurationValidator validatorWithMock = new StartConfigurationValidator(mockChecker);

        assertDoesNotThrow(() -> validatorWithMock.validate(config),
                "Validation should succeed when source folder checker returns null");
    }

    @Test
    void validate_failsWhenSqliteFileHasNoParent() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        Path sqliteFileWithoutParent = Path.of("db.sqlite");

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFileWithoutParent,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("sqlite.file: has no parent directory"));
    }

    @Test
    void validate_failsWhenSqliteFileParentIsNotDirectory() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        Path parentFile = Files.createFile(tempDir.resolve("parentFile.txt"));
        Path sqliteFileWithFileAsParent = parentFile.resolve("db.sqlite");

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFileWithFileAsParent,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("sqlite.file: parent is not a directory"));
    }

    @Test
    void validate_runtimeLockFileParentDoesNotExist() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                tempDir.resolve("nonexistent/lock.lock"),
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("runtime.lock.file: parent directory does not exist"));
    }

    @Test
    void validate_runtimeLockFileParentIsNotDirectory() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        Path parentFile = Files.createFile(tempDir.resolve("parentFile.txt"));
        Path lockFileWithFileAsParent = parentFile.resolve("lock.lock");

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                lockFileWithFileAsParent,
                null,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("runtime.lock.file: parent is not a directory"));
    }

    @Test
    void validate_logDirectoryExistsButIsNotDirectory() throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
        Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));

        Path logFileInsteadOfDirectory = Files.createFile(tempDir.resolve("logfile.txt"));

        StartConfiguration config = new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                60,
                promptTemplateFile,
                null,
                logFileInsteadOfDirectory,
                "INFO",
                false
        );

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("log.directory: exists but is not a directory"));
    }

    // =========================================================================
    // max.title.length (range checks, warnings logged only)
    // =========================================================================

    @Test
    void validate_failsWhenMaxTitleLengthBelowMinimum() throws Exception {
        StartConfiguration config = buildValidConfigWithMaxTitleLength(9);

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("max.title.length: must be >= 10"));
    }

    @Test
    void validate_failsWhenMaxTitleLengthAboveMaximum() throws Exception {
        StartConfiguration config = buildValidConfigWithMaxTitleLength(121);

        InvalidStartConfigurationException exception = assertThrows(
                InvalidStartConfigurationException.class,
                () -> validator.validate(config)
        );
        assertTrue(exception.getMessage().contains("max.title.length: must be <= 120"));
    }

    @Test
    void validate_succeedsForLowWarnRange() throws Exception {
        StartConfiguration config = buildValidConfigWithMaxTitleLength(15);
        assertDoesNotThrow(() -> validator.validate(config),
                "Werte im Bereich 10..39 sind zulässig (nur Warnung im Log)");
    }

    @Test
    void validate_succeedsForLowWarnRangeUpperBoundary() throws Exception {
        StartConfiguration config = buildValidConfigWithMaxTitleLength(39);
        assertDoesNotThrow(() -> validator.validate(config),
                "Wert 39 ist zulässig (nur Warnung im Log)");
    }

    @Test
    void validate_succeedsForNormalRangeLowerBoundary() throws Exception {
        StartConfiguration config = buildValidConfigWithMaxTitleLength(40);
        assertDoesNotThrow(() -> validator.validate(config),
                "Wert 40 ist unkritisch (kein Log-Warn)");
    }

    @Test
    void validate_succeedsForHighWarnRange() throws Exception {
        StartConfiguration config = buildValidConfigWithMaxTitleLength(110);
        assertDoesNotThrow(() -> validator.validate(config),
                "Werte im Bereich 100..120 sind zulässig (nur Warnung im Log)");
    }

    @Test
    void validate_succeedsForNormalRange() throws Exception {
        StartConfiguration config = buildValidConfigWithMaxTitleLength(60);
        assertDoesNotThrow(() -> validator.validate(config));
    }

    private StartConfiguration buildValidConfigWithMaxTitleLength(int maxTitleLength) throws Exception {
        Path sourceFolder = Files.createDirectory(tempDir.resolve("source-" + maxTitleLength));
        Path targetFolder = Files.createDirectory(tempDir.resolve("target-" + maxTitleLength));
        Path sqliteFile = Files.createFile(tempDir.resolve("db-" + maxTitleLength + ".sqlite"));
        Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt-" + maxTitleLength + ".txt"));

        return new StartConfiguration(
                sourceFolder,
                targetFolder,
                sqliteFile,
                validMultiProviderConfig(),
                3,
                100,
                50000,
                maxTitleLength,
                promptTemplateFile,
                null,
                null,
                "INFO",
                false
        );
    }
}
