/*
 * Copyright 2021 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kie.kogito.codegen.openapi.client.generator;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.kie.kogito.codegen.openapi.client.OpenApiClientOperation;
import org.kie.kogito.codegen.openapi.client.OpenApiSpecDescriptor;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.SupportingFile;
import org.openapitools.codegen.api.TemplateFileType;
import org.openapitools.codegen.languages.AbstractJavaCodegen;
import org.openapitools.codegen.languages.JavaClientCodegen;

import static java.util.stream.Collectors.toList;

/**
 * Custom {@link JavaClientCodegen} implementation to control the OpenApi client code generation.
 * We override their behavior to meet the Kogito Application requirements.
 * Here, we enforce the default Java library, which date library to use, and our custom templates.
 */
public class KogitoJavaClientCodegen extends JavaClientCodegen {

    private static final String CUSTOM_TEMPLATES = "custom-templates/resteasy";
    private static final String CUSTOM_API_CLIENT_TEMPLATE = "kogitoApiClient.mustache";
    private static final String CUSTOM_API_CLIENT_FILENAME = "KogitoApiClient.java";
    private final DefaultGenerator generator;
    private final String customApiClientTemplate;

    protected KogitoJavaClientCodegen(final DefaultGenerator generator, final String apiClientTemplate) {
        this.generator = generator;
        this.generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "false");
        this.setLibrary(JavaClientCodegen.RESTEASY);
        this.setTemplateDir(CUSTOM_TEMPLATES);
        this.setDateLibrary(AbstractJavaCodegen.JAVA8_MODE);
        // not working, see #postProcessSupportingFileData
        this.setUseRuntimeException(true);
        if (apiClientTemplate == null || apiClientTemplate.isEmpty()) {
            this.customApiClientTemplate = CUSTOM_API_CLIENT_TEMPLATE;
        } else {
            this.customApiClientTemplate = apiClientTemplate;
        }
    }

    /**
     * Fills the required operations generated data in a given {@link OpenApiSpecDescriptor}.
     *
     * @param descriptor the resource with the required operations
     */
    protected void processGeneratedOperations(final OpenApiSpecDescriptor descriptor) {
        final Map<String, List<CodegenOperation>> paths = this.generator.processPaths(this.openAPI.getPaths());
        paths.forEach((api, operations) -> operations.forEach(operation -> descriptor.getRequiredOperations().stream()
                .filter(resourceOperation -> resourceOperation.getOperationId().equals(operation.operationId))
                .findFirst()
                .ifPresent(resourceOperation -> {
                    resourceOperation.setApi(api);
                    resourceOperation.setMethodName(operation.operationId);
                    resourceOperation.setGeneratedClass(this.apiPackage + "." + this.toApiName(api));
                    if (operation.hasParams) {
                        // as the natural order generated by the tool
                        for (int i = 0; i < operation.allParams.size(); i++) {
                            resourceOperation.addParameter(OpenApiClientOperation.newParameter(i, operation.allParams.get(i).paramName));
                        }
                    }
                })));
        final List<OpenApiClientOperation> nonProcessedOps =
                descriptor.getRequiredOperations()
                        .stream()
                        .filter(ro -> ro.getGeneratedClass() == null || ro.getMethodName() == null).collect(toList());
        if (!nonProcessedOps.isEmpty()) {
            throw new IllegalArgumentException(
                    "Required Operations " + nonProcessedOps + " not found in the processed spec " + descriptor.getResourceName() + ". Available operations are " + collectCodegenOperations(paths));
        }
    }

    private List<String> collectCodegenOperations(final Map<String, List<CodegenOperation>> paths) {
        return paths.entrySet().stream().flatMap(e -> e.getValue().stream()).map(o -> o.operationId).collect(toList());
    }

    @Override
    public void processOpts() {
        super.processOpts();
        final SupportingFile apiClientFile = this.supportingFiles().stream()
                .filter(f -> f.getTemplateType() == TemplateFileType.SupportingFiles && f.getDestinationFilename().equals("ApiClient.java"))
                .findFirst().orElseThrow(() -> new IllegalArgumentException("Can't find ApiClient.java supporting file, impossible to generate OpenApi Client application"));
        // our custom ApiClient will be generated in the same folder
        this.supportingFiles().add(new SupportingFile(this.customApiClientTemplate, apiClientFile.getFolder(), CUSTOM_API_CLIENT_FILENAME));
    }

    @Override
    public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
        // TODO: open an issue on OpenApi project, this should be added by them
        objs.put(JavaClientCodegen.USE_RUNTIME_EXCEPTION, true);
        return super.postProcessSupportingFileData(objs);
    }

    @Override
    @SuppressWarnings({ "unchecked" })
    public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> objs, List<Object> allModels) {
        super.postProcessOperationsWithModels(objs, allModels);
        // remove models import since we don't care about them
        List<Map<String, String>> imports = (List<Map<String, String>>) objs.get("imports");
        for (Iterator<Map<String, String>> itr = imports.iterator(); itr.hasNext();) {
            String itrImport = itr.next().get("import");
            if (itrImport.contains(".model.")) {
                itr.remove();
            }
        }
        return objs;
    }
}
