package io.smallrye.config;

import static io.smallrye.config.ConfigMappings.ConfigClass.configClass;
import static io.smallrye.config.KeyValuesConfigSource.config;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;

import org.junit.jupiter.api.Test;

public class ConfigMappingFullTest {
    @Test
    void ambiguousUnnamedKeysDefaults() {
        SmallRyeConfig config = new SmallRyeConfigBuilder()
                .withSources(config(
                        "datasource.postgresql.jdbc.url", "value",
                        "datasource.postgresql.password", "value"))
                .withMapping(DataSourcesJdbcRuntimeConfig.class)
                .withMapping(DataSourcesJdbcBuildTimeConfig.class)
                .withMapping(DataSourcesRuntimeConfig.class)
                .withMapping(DataSourcesBuildTimeConfig.class)
                .build();

        assertTrue(
                config.getConfigMapping(DataSourcesRuntimeConfig.class).dataSources().get("postgresql").password().isPresent());

        config = new SmallRyeConfigBuilder()
                .withSources(config(
                        "datasource.postgresql.jdbc.url", "value",
                        "datasource.postgresql.password", "value"))
                .withMapping(DataSourcesJdbcBuildTimeConfig.class)
                .withMapping(DataSourcesJdbcRuntimeConfig.class)
                .withMapping(DataSourcesRuntimeConfig.class)
                .withMapping(DataSourcesBuildTimeConfig.class)
                .build();

        assertTrue(
                config.getConfigMapping(DataSourcesRuntimeConfig.class).dataSources().get("postgresql").password().isPresent());

        config = new SmallRyeConfigBuilder()
                .withSources(config(
                        "datasource.postgresql.jdbc.url", "value",
                        "datasource.postgresql.password", "value"))
                .withMapping(DataSourcesJdbcBuildTimeConfig.class)
                .withMapping(DataSourcesJdbcRuntimeConfig.class)
                .withMapping(DataSourcesBuildTimeConfig.class)
                .withMapping(DataSourcesRuntimeConfig.class)
                .build();

        assertTrue(
                config.getConfigMapping(DataSourcesRuntimeConfig.class).dataSources().get("postgresql").password().isPresent());

        config = new SmallRyeConfigBuilder()
                .withSources(config(
                        "datasource.postgresql.jdbc.url", "value",
                        "datasource.postgresql.password", "value"))
                .withMapping(DataSourcesRuntimeConfig.class)
                .withMapping(DataSourcesJdbcBuildTimeConfig.class)
                .withMapping(DataSourcesJdbcRuntimeConfig.class)
                .withMapping(DataSourcesBuildTimeConfig.class)
                .build();

        assertTrue(
                config.getConfigMapping(DataSourcesRuntimeConfig.class).dataSources().get("postgresql").password().isPresent());
    }

    @Test
    void propertyNamesMatcher() {
        PropertyNamesMatcher matcher = ConfigMappings.propertyNamesMatcher(
                List.of(configClass(DataSourcesJdbcBuildTimeConfig.class, "datasource.build"),
                        configClass(DataSourcesJdbcRuntimeConfig.class, "datasource.runtime"),
                        configClass(DataSourcesBuildTimeConfig.class, "datasource.build"),
                        configClass(DataSourcesRuntimeConfig.class, "datasource.runtime")));

        assertTrue(matcher.matches("datasource.build.jdbc"));
        assertTrue(matcher.matches("datasource.build", "jdbc"));
        assertFalse(matcher.matches("datasource.build.jdbc-unknown"));
        assertTrue(matcher.matches("datasource.build.named.jdbc"));
        assertFalse(matcher.matches("datasource.build.named.jdbc.unknown"));

        assertTrue(matcher.matches("datasource.build.devservices.container-env.key"));
        assertTrue(matcher.matches("datasource.build.devservices.container-env.one.two"));
        assertTrue(matcher.matches("datasource.build.devservices.container-env.one.three"));
    }

    @ConfigMapping(prefix = "datasource")
    interface DataSourcesRuntimeConfig {
        @WithParentName
        @WithDefaults
        @WithUnnamedKey("<default>")
        Map<String, DataSourceRuntimeConfig> dataSources();

        interface DataSourceRuntimeConfig {
            @WithDefault("true")
            boolean active();

            Optional<String> username();

            Optional<String> password();

            Optional<String> credentialsProvider();

            Optional<String> credentialsProviderName();
        }
    }

    @ConfigMapping(prefix = "datasource")
    interface DataSourcesJdbcRuntimeConfig {
        DataSourceJdbcRuntimeConfig jdbc();

        @WithParentName
        @WithDefaults
        Map<String, DataSourceJdbcOuterNamedRuntimeConfig> namedDataSources();

        interface DataSourceJdbcOuterNamedRuntimeConfig {
            DataSourceJdbcRuntimeConfig jdbc();
        }

        interface DataSourceJdbcRuntimeConfig {
            Optional<String> url();

            OptionalInt initialSize();

            @WithDefault("0")
            int minSize();

            @WithDefault("20")
            int maxSize();

            @WithDefault("2M")
            String backgroundValidationInterval();

            Optional<String> foregroundValidationInterval();

            @WithDefault("5S")
            Optional<String> acquisitionTimeout();

            Optional<String> leakDetectionInterval();

            @WithDefault("5M")
            String idleRemovalInterval();

            Optional<String> maxLifetime();

            @WithDefault("false")
            boolean extendedLeakReport();

            @WithDefault("false")
            boolean flushOnClose();

            @WithDefault("true")
            boolean detectStatementLeaks();

            Optional<String> newConnectionSql();

            Optional<String> validationQuerySql();

            @WithDefault("true")
            boolean poolingEnabled();

            Map<String, String> additionalJdbcProperties();

            @WithName("telemetry.enabled")
            Optional<Boolean> telemetry();
        }
    }

    @ConfigMapping(prefix = "datasource")
    interface DataSourcesBuildTimeConfig {
        @WithParentName
        @WithDefaults
        @WithUnnamedKey("<default>")
        Map<String, DataSourceBuildTimeConfig> dataSources();

        @WithName("health.enabled")
        @WithDefault("true")
        boolean healthEnabled();

        @WithName("metrics.enabled")
        @WithDefault("false")
        boolean metricsEnabled();

        Optional<String> url();

        Optional<String> driver();

        interface DataSourceBuildTimeConfig {
            Optional<String> dbKind();

            Optional<String> dbVersion();

            DevServicesBuildTimeConfig devservices();

            @WithDefault("false")
            boolean healthExclude();

            interface DevServicesBuildTimeConfig {
                Optional<Boolean> enabled();

                Optional<String> imageName();

                Map<String, String> containerEnv();

                Map<String, String> containerProperties();

                Map<String, String> properties();

                OptionalInt port();

                Optional<String> command();

                Optional<String> dbName();

                Optional<String> username();

                Optional<String> password();

                Optional<String> initScriptPath();

                Map<String, String> volumes();

                @WithDefault("true")
                boolean reuse();
            }
        }
    }

    @ConfigMapping(prefix = "datasource")
    interface DataSourcesJdbcBuildTimeConfig {
        @WithParentName
        @WithDefaults
        @WithUnnamedKey("<default>")
        Map<String, DataSourceJdbcOuterNamedBuildTimeConfig> dataSources();

        interface DataSourceJdbcOuterNamedBuildTimeConfig {
            DataSourceJdbcBuildTimeConfig jdbc();

            interface DataSourceJdbcBuildTimeConfig {
                @WithParentName
                @WithDefault("true")
                boolean enabled();

                Optional<String> driver();

                Optional<Boolean> enableMetrics();

                @WithDefault("false")
                boolean tracing();

                @WithDefault("false")
                boolean telemetry();
            }
        }
    }

    @Test
    void splitMappings() {
        SmallRyeConfig config = new SmallRyeConfigBuilder()
                .withSources(new EnvConfigSource(Map.of("SMALLRYE_CONFIG_ORM_DATABASE_GENERATION", "drop-and-create"), 100))
                .withMapping(OrmConfig.class)
                .withMapping(OrmRuntimeConfig.class)
                .build();

        OrmConfig ormConfig = config.getConfigMapping(OrmConfig.class);
        assertEquals(1, ormConfig.persistenceUnits().size());
        OrmRuntimeConfig ormRuntimeConfig = config.getConfigMapping(OrmRuntimeConfig.class);
        assertEquals(1, ormRuntimeConfig.persistenceUnits().size());
        assertEquals("drop-and-create",
                ormRuntimeConfig.persistenceUnits().get("<default>").database().generation().generation());
    }

    @ConfigMapping(prefix = "smallrye.config-orm")
    interface OrmConfig {
        OrmConfigDatabase database();

        @WithParentName
        @WithUnnamedKey("<default>")
        @WithDefaults
        Map<String, OrmConfigPersistenceUnit> persistenceUnits();

        interface OrmConfigDatabase {

        }

        interface OrmConfigPersistenceUnit {
            OrmConfigPersistenceUnitDatabase database();

            interface OrmConfigPersistenceUnitDatabase {
                @WithDefault("false")
                boolean globallyQuotedIdentifiers();
            }
        }
    }

    @ConfigMapping(prefix = "smallrye.config-orm")
    interface OrmRuntimeConfig {
        @WithParentName
        @WithUnnamedKey("<default>")
        @WithDefaults
        Map<String, OrmRuntimeConfigPersistenceUnit> persistenceUnits();

        interface OrmRuntimeConfigPersistenceUnit {
            OrmConfigPersistenceUnitDatabase database();

            interface OrmConfigPersistenceUnitDatabase {
                OrmConfigPersistenceUnitDatabaseGeneration generation();
            }

            interface OrmConfigPersistenceUnitDatabaseGeneration {
                @WithParentName
                @WithDefault("none")
                String generation();
            }
        }
    }
}
