/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch licenses this file to you 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.elasticsearch.gradle.precommit;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaException;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SchemaValidatorsConfig;
import com.networknt.schema.SpecVersion;
import com.networknt.schema.ValidationMessage;
import org.gradle.api.DefaultTask;
import org.gradle.api.UncheckedIOException;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.work.ChangeType;
import org.gradle.work.Incremental;
import org.gradle.work.InputChanges;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.StreamSupport;

/**
 * Incremental task to validate a set of JSON files against against a schema.
 */
public class ValidateJsonAgainstSchemaTask extends DefaultTask {

    private final ObjectMapper mapper = new ObjectMapper();
    private Set<String> ignore = new HashSet<>();
    private File jsonSchema;
    private FileCollection inputFiles;

    @Incremental
    @InputFiles
    public FileCollection getInputFiles() {
        return inputFiles;
    }

    public void setInputFiles(FileCollection inputFiles) {
        this.inputFiles = inputFiles;
    }

    @InputFile
    public File getJsonSchema() {
        return jsonSchema;
    }

    public void setJsonSchema(File jsonSchema) {
        this.jsonSchema = jsonSchema;
    }

    @Input
    @Optional
    public Set<String> getIgnore() {
        return ignore;
    }

    public void ignore(String... ignore) {
        this.ignore.addAll(Arrays.asList(ignore));
    }

    @OutputFile
    public File getReport() {
        return new File(getProject().getBuildDir(), "reports/validateJson.txt");
    }

    @TaskAction
    public void validate(InputChanges inputChanges) throws IOException {
        File jsonSchemaOnDisk = getJsonSchema();
        getLogger().debug("JSON schema : [{}]", jsonSchemaOnDisk.getAbsolutePath());
        SchemaValidatorsConfig config = new SchemaValidatorsConfig();
        JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
        JsonSchema jsonSchema = factory.getSchema(mapper.readTree(jsonSchemaOnDisk), config);
        Map<File, Set<String>> errors = new LinkedHashMap<>();
        // incrementally evaluate input files
        StreamSupport.stream(inputChanges.getFileChanges(getInputFiles()).spliterator(), false)
            .filter(f -> f.getChangeType() != ChangeType.REMOVED)
            .forEach(fileChange -> {
                File file = fileChange.getFile();
                if (ignore.contains(file.getName())) {
                    getLogger().debug("Ignoring file [{}] due to configuration", file.getName());
                } else if (file.isDirectory() == false) {
                    // validate all files and hold on to errors for a complete report if there are failures
                    getLogger().debug("Validating JSON [{}]", file.getName());
                    try {
                        Set<ValidationMessage> validationMessages = jsonSchema.validate(mapper.readTree(file));
                        maybeLogAndCollectError(validationMessages, errors, file);
                    } catch (IOException e) {
                        throw new UncheckedIOException(e);
                    }
                }
            });
        if (errors.isEmpty()) {
            Files.writeString(getReport().toPath(), "Success! No validation errors found.", StandardOpenOption.CREATE);
        } else {
            try (PrintWriter printWriter = new PrintWriter(getReport())) {
                printWriter.printf("Schema: %s%n", jsonSchemaOnDisk);
                printWriter.println("----------Validation Errors-----------");
                errors.values().stream().flatMap(Collection::stream).forEach(printWriter::println);
            }
            StringBuilder sb = new StringBuilder();
            sb.append("Error validating JSON. See the report at: ");
            sb.append(getReport().toURI().toASCIIString());
            sb.append(System.lineSeparator());
            sb.append(
                String.format("JSON validation failed: %d files contained %d violations", errors.keySet().size(), errors.values().size())
            );
            throw new JsonSchemaException(sb.toString());
        }
    }

    private void maybeLogAndCollectError(Set<ValidationMessage> messages, Map<File, Set<String>> errors, File file) {
        for (ValidationMessage message : messages) {
            getLogger().error("[validate JSON][ERROR][{}][{}]", file.getName(), message.toString());
            errors.computeIfAbsent(file, k -> new LinkedHashSet<>())
                .add(String.format("%s: %s", file.getAbsolutePath(), message.toString()));
        }
    }
}

@@ -1283,43 +1283,8 @@
 on;%0A
-import org.gradle.api.tasks.Input;%0A
 impo
@@ -1310,32 +1310,32 @@
 asks.InputFile;%0A
+
 import org.gradl
@@ -1362,46 +1362,8 @@
 es;%0A
-import org.gradle.api.tasks.Optional;%0A
 impo
@@ -1697,33 +1697,8 @@
 on;%0A
-import java.util.Arrays;%0A
 impo
@@ -1726,34 +1726,8 @@
 on;%0A
-import java.util.HashSet;%0A
 impo
@@ -2086,58 +2086,8 @@
 ();%0A
-    private Set%3CString%3E ignore = new HashSet%3C%3E();%0A
     
@@ -2547,201 +2547,8 @@
  %7D%0A%0A
-    @Input%0A    @Optional%0A    public Set%3CString%3E getIgnore() %7B%0A        return ignore;%0A    %7D%0A%0A    public void ignore(String... ignore) %7B%0A        this.ignore.addAll(Arrays.asList(ignore));%0A    %7D%0A%0A
     
@@ -3513,24 +3513,24 @@
 .getFile();%0A
+
             
@@ -3540,168 +3540,8 @@
  if 
-(ignore.contains(file.getName())) %7B%0A                    getLogger().debug(%22Ignoring file %5B%7B%7D%5D due to configuration%22, file.getName());%0A                %7D else if 
 (fil

Remove ability to ingore files from spec validation (#56647)