package cn.ibizlab.codegen.cmd;

import cn.ibizlab.codegen.CodegenConstants;
import cn.ibizlab.codegen.DefaultGenerator;
import cn.ibizlab.codegen.Generator;
import cn.ibizlab.codegen.GeneratorNotFoundException;
import cn.ibizlab.codegen.config.CodegenConfigurator;
import cn.ibizlab.codegen.config.CodegenConfiguratorUtils;
import cn.ibizlab.codegen.*;
import cn.ibizlab.codegen.config.EmbedTemplate;
import cn.ibizlab.codegen.config.Volume;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

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

@SuppressWarnings({"java:S106"})
@Command(name = "generate", description = "Generate code with the specified generator.")
public class Generate extends IbizLabGeneratorCommand {





    @Option(name = {"-o", "--output"}, title = "output directory",
            description = "where to write the generated files (current dir by default)")
    private String output = "";

    @Option(name = {"-i", "--input-spec"}, title = "spec file",
            description = "location of the ibizlab-Model spec, as URL or file (required if not loaded via config using -c)")
    private String spec;

    @Option(name = {"-f", "--input-spec-filter"}, title = "model's filter regex",
            description = "input spec model file's filter regex, multiple regex are supported, the format of page:EntityAEditView,page:EntityAEditView,appEntity:EntityA,ctrl:main " +
                    "(or multiple options, each with -f page:EntityAEditView -f page:EntityAEditView -f appEntity:EntityA -f ctrl@FORM:main)")
    private List<String> filters;

    @Option(name = {"-t", "--template-dir"}, title = "template directories",
            description = "folder containing the template files, multiple paths are supported, the format of /templateA,/tmp/templateB " +
                    "(or multiple options, each with -t /templateA -t /tmp/templateB)")
    private List<String> templateDirs;

    @Option(name = {"--embed-template"}, title = "embed template name",
            description = "special embed template name, e.g. ibizlab-template-ibizedge:1.0-SNAPSHOT or ibizlab-template-ibizboot:1.0-SNAPSHOT , sets template name properties that can be load inner template from maven repo ")
    private List<String> embedTemplates;

    @Option(name = {"--template-path"}, title = "template files relative path",
            description = "special template file's relative path, multiple paths are supported, the format of /folderA/README.md.hbs,/folderB/sub/file.json.hbs " +
                    "(or multiple options, each with --template-path /folderA/README.md.hbs --template-path /folderB/sub/file.json.hbs)")
    private List<String> templatePaths;


    @Option(name = {"--template-filter"}, title = "template file's filter regex",
            description = "template file's filter regex, multiple regex are supported, the format of /folderA,/folderB/.*/{{entities}}.java.hbs " +
                    "(or multiple options, each with --template-filter /folderA --template-filter /folderB/.*/{{entities}}.java.hbs)")
    private List<String> templateFilters;

    @Option(name = {"-v","--volume"}, title = "template file's filter regex",
            description = "volume dir's or file's , multiple regex are supported, the format of /folderA,/folderB/file1.java " +
                    "(or multiple options, each with -v /folderA -v /folderB/file1.java)")
    private List<String> volumes;

    @Option(
            name = {"-a", "--auth"},
            title = "authorization",
            description = "adds authorization headers when fetching the definitions remotely. "
                    + "Pass in a URL-encoded string of name:header with a comma separating multiple values")
    private String auth;

    @Option(
            name = {"--global-property"},
            title = "global properties",
            description = "sets specified global properties (previously called 'system properties') in "
                    + "the format of name=value,name=value (or multiple options, each with name=value)")
    private List<String> globalProperties = new ArrayList<>();

    @Option(
            name = {"-c", "--config"},
            title = "configuration file",
            description = "Path to configuration file. It can be JSON or YAML. "
                    + "If file is JSON, the content should have the format {\"optionKey\":\"optionValue\", \"optionKey1\":\"optionValue1\"...}. "
                    + "If file is YAML, the content should have the format optionKey: optionValue. "
                    + "Supported options can be different for each language. Run config-help -g {generator name} command for language-specific config options.")
    private String configFile;

    @Option(name = {"-n","--project-name","--system-name"}, title = "package name",
            description = CodegenConstants.PROJECT_NAME)
    private String name;

    @Option(name = {"--package-name"}, title = "package name",
            description = CodegenConstants.PACKAGE_NAME_DESC)
    private String packageName;




    @Option(
            name = {"-p", "--additional-properties"},
            title = "additional properties",
            description = "sets additional properties that can be referenced by the mustache templates in the format of name=value,name=value."
                    + " You can also have multiple occurrences of this option.")
    private List<String> additionalProperties = new ArrayList<>();





    @Override
    public void execute() {

        List<CodegenConfigurator> configs=null;
        if (configFile != null && configFile.length() > 0) {
            // attempt to load from configFile
            configs = CodegenConfigurator.fromFile(configFile);
        }
        if(configs==null)
        {
            configs=new ArrayList<>();
            configs.add(new CodegenConfigurator());
        }

        configs.forEach(configurator->{

            if (isEmpty(configurator.getInputSpec())&&isNotEmpty(spec)) {
                configurator.setInputSpec(spec);
            }
            if (isEmpty(configurator.getInputSpec())) {
                System.err.println("[error] The spec file is not found: " + spec);
                System.err.println("[error] Check the path of the ibizlab-Model spec and try again.");
                System.exit(1);
            }
            File specFile=new File(configurator.getInputSpec());
            if (!configurator.getInputSpec().matches("^http(s)?://.*")  && !specFile.exists()) {
                System.err.println("[error] The spec file is not found: " + spec);
                System.err.println("[error] Check the path of the ibizlab-Model spec and try again.");
                System.exit(1);
            }

            configurator.addAdditionalProperty("inputSpecPath",specFile.getAbsolutePath());



            if (isEmpty(configurator.getOutputDir())&&isNotEmpty(output)) {
                configurator.setOutputDir(output);
            }

            if (isEmpty(configurator.getAuth())&&isNotEmpty(auth)) {
                configurator.setAuth(auth);
            }

            if (ObjectUtils.isEmpty(configurator.getTemplateDirs())&&(!ObjectUtils.isEmpty(templateDirs))) {
                configurator.setTemplateDirs(templateDirs);
            }

            if(ObjectUtils.isEmpty(configurator.getEmbedTemplates())&&(!ObjectUtils.isEmpty(embedTemplates))) {
                List<EmbedTemplate> list=new ArrayList<>();
                embedTemplates.forEach(item->{
                    EmbedTemplate embedTemplate=EmbedTemplate.from(item);
                    if(embedTemplate!=null)
                        list.add(embedTemplate);
                });
                configurator.setEmbedTemplates(list);
            }

            if(ObjectUtils.isEmpty(configurator.getVolumes())&&(!ObjectUtils.isEmpty(volumes))) {
                List<Volume> list=new ArrayList<>();
                volumes.forEach(item->{
                    Volume volume=Volume.from(item);
                    if(volume!=null)
                        list.add(volume);
                });
                configurator.setVolumes(list);
            }

            if (ObjectUtils.isEmpty(configurator.getTemplatePaths())&&(!ObjectUtils.isEmpty(templatePaths))) {
                configurator.setTemplatePaths(templatePaths);
            }

            if (ObjectUtils.isEmpty(configurator.getTemplateFilters())&&(!ObjectUtils.isEmpty(templateFilters))) {
                configurator.setTemplateFilters(templateFilters);
            }

            if (ObjectUtils.isEmpty(configurator.getFilters())&&(!ObjectUtils.isEmpty(filters))) {
                configurator.setFilters(filters);
            }

            if (ObjectUtils.isEmpty(configurator.getAdditionalProperties().get(CodegenConstants.PACKAGE_NAME))&&isNotEmpty(packageName)) {
                configurator.setPackageName(packageName);
            }

            if (ObjectUtils.isEmpty(configurator.getAdditionalProperties().get(CodegenConstants.PROJECT_NAME))&&isNotEmpty(name)) {
                configurator.setProjectName(name);
            }




            if (globalProperties != null && !globalProperties.isEmpty()) {
                CodegenConfiguratorUtils.applyGlobalPropertiesKvpList(globalProperties, configurator);
            }

            CodegenConfiguratorUtils.applyAdditionalPropertiesKvpList(additionalProperties, configurator);


            try {

                File tempDir= Paths.get(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString()).toFile();
                if(!ObjectUtils.isEmpty(configurator.getVolumes()))
                {

                    tempDir.mkdirs();
                    try{
                        InputStream ignoreFile = this.getClass().getResourceAsStream("/templ/.ibizlab-generator-ignore"); //权限资源
                        if (!ObjectUtils.isEmpty(ignoreFile)) {
                            Files.copy(ignoreFile,Paths.get(tempDir.getPath(),".ibizlab-generator-ignore"),StandardCopyOption.REPLACE_EXISTING);
                        }
                    }catch (Exception ex){}

                    configurator.getVolumes().forEach(volume -> {
                        File source=volume.getSourcePath().toFile();
                        if(source.exists())
                        {
                            java.nio.file.Path targetPath=Paths.get(tempDir.getPath(),volume.getTarget());
                            File target=targetPath.toFile();
                            if(source.getAbsolutePath().equalsIgnoreCase(target.getAbsolutePath()))
                                return;

                            target.getParentFile().mkdirs();
                            try {
                                Files.createSymbolicLink(targetPath,Paths.get(source.getAbsolutePath()));
                                System.out.println("volume ln "+source.getAbsolutePath()+" -> "+target.getAbsolutePath());

                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                    if(configurator.getTemplateDirs()==null)
                        configurator.setTemplateDirs(new ArrayList<>());
                    configurator.getTemplateDirs().add(tempDir.getAbsolutePath());
                }

                // this null check allows us to inject for unit testing.
                Generator generator = new DefaultGenerator();


                generator.opts(configurator.toClientOptInput());
                generator.generate();



                if(!ObjectUtils.isEmpty(configurator.getEmbedTemplates()))
                {
                    LocalCommandExecutor.getInstance().executeCommand("mvn ibizlabcli:code -f "+ Paths.get(this.output,"generator.xml").toString(),120000);
                }

                if(tempDir.exists()) {
                    String osName=System.getProperty("os.name");
                    if(osName!=null&&(!osName.startsWith("Windows")))
                        LocalCommandExecutor.getInstance().executeCommand("rm -rf " + tempDir.getAbsolutePath(), 120000);
                }
            } catch (GeneratorNotFoundException e) {
                System.err.println(e.getMessage());
                System.err.println("[error] Check the spelling of the generator's name and try again.");
                System.exit(1);
            }
        });



    }
}
