package de.gecheckt.pdf.umbenenner.bootstrap.startup;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Optional;

import org.junit.jupiter.api.Test;

/**
 * Unit tests for {@link CliArgumentParser}.
 * <p>
 * Covers all supported option combinations and all documented error cases.
 */
class CliArgumentParserTest {

    private final CliArgumentParser parser = new CliArgumentParser();

    // =========================================================================
    // Valid combinations
    // =========================================================================

    @Test
    void noArgs_returnsGuiModeWithNoConfigPath() {
        StartupArgumentsParseResult result = parser.parse(new String[]{});

        StartupArguments args = assertValid(result);
        assertEquals(StartupMode.GUI, args.mode(), "No args must default to GUI mode");
        assertEquals(Optional.empty(), args.configPath(), "No args must produce empty configPath");
    }

    @Test
    void headlessOnly_returnsHeadlessModeWithNoConfigPath() {
        StartupArgumentsParseResult result = parser.parse(new String[]{"--headless"});

        StartupArguments args = assertValid(result);
        assertEquals(StartupMode.HEADLESS, args.mode(), "--headless must set HEADLESS mode");
        assertEquals(Optional.empty(), args.configPath(), "--headless without --config must produce empty configPath");
    }

    @Test
    void configOnly_returnsGuiModeWithConfigPath() {
        StartupArgumentsParseResult result = parser.parse(new String[]{"--config", "config/app.properties"});

        StartupArguments args = assertValid(result);
        assertEquals(StartupMode.GUI, args.mode(), "--config alone must default to GUI mode");
        assertEquals(Optional.of("config/app.properties"), args.configPath(),
                "--config must supply the given path");
    }

    @Test
    void headlessThenConfig_returnsHeadlessModeWithConfigPath() {
        StartupArgumentsParseResult result = parser.parse(
                new String[]{"--headless", "--config", "C:\\config\\app.properties"});

        StartupArguments args = assertValid(result);
        assertEquals(StartupMode.HEADLESS, args.mode());
        assertEquals(Optional.of("C:\\config\\app.properties"), args.configPath());
    }

    @Test
    void configThenHeadless_returnsHeadlessModeWithConfigPath() {
        StartupArgumentsParseResult result = parser.parse(
                new String[]{"--config", "my.properties", "--headless"});

        StartupArguments args = assertValid(result);
        assertEquals(StartupMode.HEADLESS, args.mode(),
                "--headless after --config must still produce HEADLESS mode");
        assertEquals(Optional.of("my.properties"), args.configPath());
    }

    @Test
    void configWithAbsolutePath_acceptsWindowsPathWithDriveLetter() {
        StartupArgumentsParseResult result = parser.parse(
                new String[]{"--config", "S:\\shared\\config\\application.properties"});

        StartupArguments args = assertValid(result);
        assertEquals(Optional.of("S:\\shared\\config\\application.properties"), args.configPath(),
                "Mapped drive paths must be accepted as config path values");
    }

    // =========================================================================
    // Invalid combinations
    // =========================================================================

    @Test
    void configWithoutValue_returnsInvalid() {
        StartupArgumentsParseResult result = parser.parse(new String[]{"--config"});

        assertInvalid(result, "--config");
    }

    @Test
    void configFollowedByAnotherOption_returnsInvalid() {
        StartupArgumentsParseResult result = parser.parse(new String[]{"--config", "--headless"});

        assertInvalid(result, "--config");
    }

    @Test
    void unknownArgument_returnsInvalid() {
        StartupArgumentsParseResult result = parser.parse(new String[]{"--unknown"});

        assertInvalid(result, "--unknown");
    }

    @Test
    void unknownPositionalArgument_returnsInvalid() {
        StartupArgumentsParseResult result = parser.parse(new String[]{"somevalue"});

        assertInvalid(result, "somevalue");
    }

    @Test
    void duplicateHeadless_returnsInvalid() {
        StartupArgumentsParseResult result = parser.parse(new String[]{"--headless", "--headless"});

        assertInvalid(result, "--headless");
    }

    @Test
    void duplicateConfig_returnsInvalid() {
        StartupArgumentsParseResult result = parser.parse(
                new String[]{"--config", "a.properties", "--config", "b.properties"});

        assertInvalid(result, "--config");
    }

    @Test
    void configWithBlankValue_returnsInvalid() {
        StartupArgumentsParseResult result = parser.parse(new String[]{"--config", "   "});

        assertInvalid(result, "--config");
    }

    @Test
    void unknownArgAfterValidArgs_returnsInvalid() {
        StartupArgumentsParseResult result = parser.parse(
                new String[]{"--headless", "--config", "app.properties", "--extra"});

        assertInvalid(result, "--extra");
    }

    // =========================================================================
    // Helpers
    // =========================================================================

    /**
     * Asserts the result is Valid and returns the contained {@link StartupArguments}.
     */
    private static StartupArguments assertValid(StartupArgumentsParseResult result) {
        assertInstanceOf(StartupArgumentsParseResult.Valid.class, result,
                "Expected Valid parse result but got: " + result);
        return ((StartupArgumentsParseResult.Valid) result).arguments();
    }

    /**
     * Asserts the result is Invalid and that the error message contains the given fragment.
     */
    private static void assertInvalid(StartupArgumentsParseResult result, String expectedFragment) {
        assertInstanceOf(StartupArgumentsParseResult.Invalid.class, result,
                "Expected Invalid parse result but got: " + result);
        String errorMessage = ((StartupArgumentsParseResult.Invalid) result).errorMessage();
        assertTrue(errorMessage.contains(expectedFragment),
                "Error message should contain '" + expectedFragment + "' but was: " + errorMessage);
    }
}
