/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2021 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.installer.validators;

import org.apache.commons.io.FileUtils;
import org.jboss.installer.core.LanguageUtils;
import org.jboss.installer.core.ValidationResult;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;

public class MavenSettingsPathValidator {

    public static final String SETTINGS_NOT_EXISTENT_WARNING_KEY = "quickstarts.maven.setup.settings_not_exists_warning";
    public static final String SETTINGS_EXISTS_EMPTY_WARNING_KEY = "quickstarts.maven.setup.settings_exists_empty_warning";
    public static final String SETTINGS_EXISTS_WARNING_KEY = "quickstarts.maven.setup.settings_exists_warning";
    public static final String NOT_ABSOLUTE_ERROR_KEY = "quickstarts.maven.setup.not_absolute_error";
    public static final String NOT_WRITABLE_ERROR_KEY = "quickstarts.maven.setup.not_writable_error";
    public static final String EMPTY_FIELD_ERROR_KEY = "quickstarts.maven.setup.empty_field_error";
    public static final String NOT_LOCAL_PATH_ERROR_KEY = "quickstarts.maven.setup.not_local_path_error";
    public static final String INVALID_SCHEMA_ERROR_KEY = "quickstarts.maven.setup.invalid_schema_error";
    public static final String FS = System.getProperty("file.separator");
    public static final String SETTINGS_XML = "settings.xml";

    private final LanguageUtils languageUtils;
    private String mavenSettingsVersion = "1.0.0";

    private final PathValidator pathValidator;
    private final String prefix = "quickstarts";

    public MavenSettingsPathValidator(LanguageUtils languageUtils) {
        this.languageUtils = languageUtils;
        pathValidator = PathValidator.newInstance("quickstarts", languageUtils);
    }

    public File getExistingParent(File dir) {
        while (dir != null && !dir.exists()) {
            dir = dir.getParentFile();
        }
        return dir;
    }

    public ValidationResult validate(String pathText) {
        if (pathText == null) {
            return ValidationResult.error(languageUtils.getString(EMPTY_FIELD_ERROR_KEY));
        }

        return CombiningValidator
                .of(()->pathValidator.validate(pathText), ()->validate(Path.of(pathText), true))
                .validate();
    }

    public ValidationResult validate(Path path, boolean warnOverwrite) {
        if (path == null) {
            return ValidationResult.error(languageUtils.getString(EMPTY_FIELD_ERROR_KEY));
        }
        File file = path.toFile();
        if (path.toString().isEmpty()) {
            return ValidationResult.error(languageUtils.getString(EMPTY_FIELD_ERROR_KEY));
        }
        if (!file.isAbsolute()) {
            return ValidationResult.error(languageUtils.getString(NOT_ABSOLUTE_ERROR_KEY));
        }
        if (file.isDirectory()) {
            file = addFileToPath(path);
        }

        if (!file.exists() && !isWritable(file) || file.exists() && !file.canWrite()) {
            return ValidationResult.error(languageUtils.getString(NOT_WRITABLE_ERROR_KEY));
        }
        if (file.exists() && file.isFile() && file.canWrite()) {
            if (file.length() == 0) {
                return ValidationResult.warning(languageUtils.getString(SETTINGS_EXISTS_EMPTY_WARNING_KEY));
            }
            if (isXmlValid(file) && determineSettingsVersion(file) && isSchemaValid(file)) {
                return warnOverwrite? ValidationResult.warning(languageUtils.getString(SETTINGS_EXISTS_WARNING_KEY)): ValidationResult.ok();
            }
            return ValidationResult.error(languageUtils.getString(INVALID_SCHEMA_ERROR_KEY));
        }
        if (!file.exists()) {
            return ValidationResult.warning(languageUtils.getString(SETTINGS_NOT_EXISTENT_WARNING_KEY));
        }
        return ValidationResult.ok();
    }

    private boolean isWritable(File file) {
        final File existingParent = getExistingParent(file);
        if (existingParent == null) {
            return false;
        }
        return Files.isWritable(existingParent.toPath());
    }

    private File addFileToPath(Path path) {
        return Paths.get(path.toString() + FS + SETTINGS_XML).toFile();
    }

    private boolean isXmlValid(File file) {
        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        builderFactory.setNamespaceAware(true);
        try {
            InputStream stream = new BufferedInputStream(new FileInputStream(file));
            String fileType = URLConnection.guessContentTypeFromStream(stream);
            return fileType != null && fileType.equals("application/xml");
        } catch (IOException e) {
            return false;
        }
    }

    private boolean isSchemaValid(File file) {
        try {
            String xsdFileName = "settings-" + mavenSettingsVersion + ".xsd";
            InputStream stream = this.getClass().getClassLoader().getResourceAsStream(xsdFileName);
            File schemaFile = File.createTempFile("eap-installer-tmp-schema", "xsd");
            schemaFile.deleteOnExit();
            FileUtils.copyInputStreamToFile(stream, schemaFile);
            Source xmlFile = new StreamSource(file);
            SchemaFactory schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
            Schema schema = schemaFactory.newSchema(schemaFile);
            javax.xml.validation.Validator validator = schema.newValidator();
            validator.validate(xmlFile);
            return true;
        } catch (Exception var7) {
            return false;
        }
    }

    private boolean determineSettingsVersion(File file) {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        try {
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document dom = db.parse(file);
            String schemaLocation = dom.getElementsByTagName("settings").item(0).getNamespaceURI();
            setMavenSettingsVersion(getVersion(schemaLocation));
        } catch (ParserConfigurationException | SAXException | IOException | NullPointerException e) {
            return false;
        }
        return true;
    }

    private String getVersion(String schemaLocation) {
        String[] splitLocation = schemaLocation.split("/");
        return Arrays.asList(splitLocation).get(splitLocation.length - 1);
    }

    private void setMavenSettingsVersion(String version) {
        this.mavenSettingsVersion = version;
    }
}
