package cn.ibizlab.codegen.config;

import cn.ibizlab.codegen.CodegenConfig;
import cn.ibizlab.codegen.CodegenConstants;
import cn.ibizlab.codegen.templating.HandlebarsEngineAdapter;
import cn.ibizlab.codegen.templating.MustacheEngineAdapter;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import cn.ibizlab.codegen.*;
import cn.ibizlab.codegen.templating.TemplatingEngineAdapter;
import cn.ibizlab.codegen.templating.TemplatingEngineLoader;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.core.util.Yaml;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.*;

import static org.apache.commons.lang3.StringUtils.isNotEmpty;

/**
 * A class which manages the contextual configuration for code generation.
 * This includes configuring the generator, templating, and the workflow which orchestrates these.
 *
 * This helper also enables the deserialization of {@link GeneratorSettings} via application-specific Jackson JSON usage
 * (see {@link DynamicSettings}.
 */
@SuppressWarnings("UnusedReturnValue")
public final class CodegenConfigurator {

    public static final Logger LOGGER = LoggerFactory.getLogger(CodegenConfigurator.class);

    private String inputSpec;
    private String outputDir;
    private List<String> filters;
    private List<String> templateDirs;
    private List<String> templatePaths;
    private List<String> templateFilters;
    private String auth;

    private String templatingEngineName="handlebars";

    private Map<String, String> globalProperties = new HashMap<>();
    private Map<String, Object> additionalProperties = new HashMap<>();


    private GeneratorSettings generatorSettings=new GeneratorSettings();


    public CodegenConfigurator() {

    }

    @SuppressWarnings("DuplicatedCode")
    public static CodegenConfigurator fromFile(String configFile, Module... modules) {

        if (isNotEmpty(configFile)) {
            DynamicSettings settings = readDynamicSettings(configFile, modules);

            CodegenConfigurator configurator = new CodegenConfigurator();

            GeneratorSettings generatorSettings = settings.getGeneratorSettings();



            if(generatorSettings.getAdditionalProperties() != null) {
                configurator.additionalProperties.putAll(generatorSettings.getAdditionalProperties());
            }



            return configurator;
        }
        return null;
    }

    private static DynamicSettings readDynamicSettings(String configFile, Module... modules) {
        ObjectMapper mapper;

        if (FilenameUtils.isExtension(configFile.toLowerCase(Locale.ROOT), new String[]{"yml", "yaml"})) {
            mapper = Yaml.mapper().copy();
        } else {
            mapper = Json.mapper().copy();
        }

        if (modules != null && modules.length > 0) {
            mapper.registerModules(modules);
        }

        mapper.registerModule(new GuavaModule());

        try {
            return mapper.readValue(new File(configFile), DynamicSettings.class);
        } catch (IOException ex) {
            LOGGER.error(ex.getMessage());
            throw new RuntimeException("Unable to deserialize config file: " + configFile);
        }
    }


    public CodegenConfigurator addAdditionalProperty(String key, Object value) {
        this.additionalProperties.put(key, value);
        generatorSettings.getAdditionalProperties().put(key, value);
        return this;
    }

    public CodegenConfigurator addGlobalProperty(String key, String value) {
        this.globalProperties.put(key, value);
        return this;
    }



    public CodegenConfigurator setAdditionalProperties(Map<String, Object> additionalProperties) {
        this.additionalProperties = additionalProperties;
        generatorSettings.setAdditionalProperties(additionalProperties);
        return this;
    }


    public CodegenConfigurator setGlobalProperties(Map<String, String> globalProperties) {
        this.globalProperties = globalProperties;
        return this;
    }



    public CodegenConfigurator setProjectName(String projectName) {
        if (StringUtils.isNotEmpty(projectName)) {
            addAdditionalProperty(CodegenConstants.PROJECT_NAME, projectName);
        }
        generatorSettings.setProjectName(projectName);
        return this;
    }


    public CodegenConfigurator setPackageName(String packageName) {
        if (StringUtils.isNotEmpty(packageName)) {
            addAdditionalProperty(CodegenConstants.PACKAGE_NAME, packageName);
        }
        generatorSettings.setPackageName(packageName);
        return this;
    }

    public CodegenConfigurator setGitRepoId(String gitRepoId) {
        if (StringUtils.isNotEmpty(gitRepoId)) {
            addAdditionalProperty(CodegenConstants.GIT_REPO_ID, gitRepoId);
        }
        generatorSettings.setGitRepoId(gitRepoId);
        return this;
    }

    public CodegenConfigurator setGitHost(String gitHost) {
        if (StringUtils.isNotEmpty(gitHost)) {
            addAdditionalProperty(CodegenConstants.GIT_HOST, gitHost);
        }
        generatorSettings.setGitHost(gitHost);
        return this;
    }

    public CodegenConfigurator setGitUserId(String gitUserId) {
        if (StringUtils.isNotEmpty(gitUserId)) {
            addAdditionalProperty(CodegenConstants.GIT_USER_ID, gitUserId);
        }
        generatorSettings.setGitUserId(gitUserId);
        return this;
    }


    public CodegenConfigurator setReleaseNote(String releaseNote) {
        if (StringUtils.isNotEmpty(releaseNote)) {
            addAdditionalProperty(CodegenConstants.RELEASE_NOTE, releaseNote);
        }
        generatorSettings.setReleaseNote(releaseNote);
        return this;
    }

    public CodegenConfigurator setHttpUserAgent(String httpUserAgent) {
        if (StringUtils.isNotEmpty(httpUserAgent)) {
            addAdditionalProperty(CodegenConstants.HTTP_USER_AGENT, httpUserAgent);
        }
        generatorSettings.setHttpUserAgent(httpUserAgent);
        return this;
    }



    public CodegenConfigurator setAuth(String auth) {
        // do not cache this in additional properties.
        this.auth = auth;
        return this;
    }

    public CodegenConfigurator setInputSpec(String inputSpec) {
        this.inputSpec = inputSpec;
        return this;
    }


    public CodegenConfigurator setOutputDir(String outputDir) {
        this.outputDir = outputDir;
        return this;
    }



    public CodegenConfigurator setTemplateDirs(List<String> templateDirs) {
        this.templateDirs = templateDirs;
        return this;
    }

    public CodegenConfigurator setTemplatePaths(List<String> templatePaths) {
        this.templatePaths = templatePaths;
        return this;
    }

    public CodegenConfigurator setTemplateFilters(List<String> templateFilters) {
        this.templateFilters = templateFilters;
        return this;
    }

    public CodegenConfigurator setFilters(List<String> filters) {
        this.filters = filters;
        return this;
    }

    public CodegenConfig toClientOptInput() {


        // We load the config via generatorSettings.getGeneratorName() because this is guaranteed to be set
        // regardless of entrypoint (CLI sets properties on this type, config deserialization sets on generatorSettings).
        CodegenConfig config = new CodegenConfig();

        // TODO: Work toward CodegenConfig having a "WorkflowSettings" property, or better a "Workflow" object which itself has a "WorkflowSettings" property.

        TemplatingEngineAdapter templatingEngine = new HandlebarsEngineAdapter();
        config.setTemplatingEngine(templatingEngine);

        if(this.additionalProperties!=null)
            config.getAdditionalProperties().putAll(this.additionalProperties);
        if(this.globalProperties!=null)
            config.getAdditionalProperties().putAll(this.globalProperties);

        if(!StringUtils.isEmpty(this.inputSpec))
            config.setInputSpec(this.inputSpec);
        if(!StringUtils.isEmpty(this.outputDir))
            config.setOutputDir(this.outputDir);

        if(!ObjectUtils.isEmpty(filters)) {
            config.setFilters(this.filters);
        }

        if(!ObjectUtils.isEmpty(templateDirs)) {
            config.setTemplateDirs(this.templateDirs);
        }

        if(!ObjectUtils.isEmpty(templatePaths)) {
            config.setTemplatePaths(this.templatePaths);
        }

        if(!ObjectUtils.isEmpty(templateFilters)) {
            config.setTemplateFilters(this.templateFilters);
        }

        if(!StringUtils.isEmpty(this.auth))
            config.setAuth(this.auth);

        return config;
    }


}
