package com.atlassian.bitbucket.mesh.management;

import com.atlassian.bitbucket.mesh.io.MoreFiles;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.atlassian.bitbucket.mesh.layout.StorageLayout.MESH_NODE_PROPERTIES;
import static com.atlassian.bitbucket.mesh.layout.StorageLayout.MESH_PROPERTIES;
import static com.atlassian.bitbucket.mesh.management.SupportUtils.APPLICATION_PROPERTIES_JSON;
import static java.util.regex.Pattern.CASE_INSENSITIVE;

/**
 * Copied from the ATST (project-ironfist) project, combines the FileSanitizer and FileSanitizationPatternManager.
 */
public class FileSanitizer {

    static final String SANITIZER_MESSAGE = "Sanitized for the Support Zip";

    private static final Logger log = LoggerFactory.getLogger(FileSanitizer.class);

    private static final Pattern AUTHENTICATION_TOKEN_PATTERN =
            Pattern.compile("(\\s*authentication\\.token\\s*[=:]\\s*).*");
    private static final List<Pattern> BASIC_PATTERNS = ImmutableList.of(
            Pattern.compile(".*user*.*", CASE_INSENSITIVE),
            Pattern.compile(".*password*.*", CASE_INSENSITIVE));
    private static final Pattern PASSWORD_ARGUMENT_PATTERN =
            Pattern.compile("(\\s*.*password\\s*[=:]\\s*).*", CASE_INSENSITIVE);
    private static final Pattern USER_ARGUMENT_PATTERN =
            Pattern.compile("(\\s*.*user\\s*[=:]\\s*).*", CASE_INSENSITIVE);

    private final Map<String, List<Pattern>> patternsByFile;
    private final Path tempDir;

    public FileSanitizer(@Nonnull Path tempDir) {
        this.tempDir = tempDir;

        patternsByFile = ImmutableMap.<String, List<Pattern>>builder()
                .put(APPLICATION_PROPERTIES_JSON, ImmutableList.of(USER_ARGUMENT_PATTERN, PASSWORD_ARGUMENT_PATTERN))
                .put(MESH_NODE_PROPERTIES, Collections.singletonList(AUTHENTICATION_TOKEN_PATTERN))
                .put(MESH_PROPERTIES, Collections.singletonList(AUTHENTICATION_TOKEN_PATTERN))
                .build();
    }

    /**
     * Used when trying to sanitize an already existing file.
     *
     * @param sanitize the file to be sanitized
     * @return the sanitized file if it can be sanitized otherwise it is left as is
     * @throws IOException if there are issues performing IO on the file to sanitize
     */
    public Path sanitize(@Nonnull Path sanitize) throws IOException {
        String fileName = sanitize.getFileName().toString();
        List<Pattern> patterns = patternsByFile.get(fileName);
        if (patterns.isEmpty()) {
            log.debug("{} does not need to be sanitized", fileName);
            return sanitize;
        }

        Path output = Files.createTempFile(tempDir, "sanitizer", "out");
        try (BufferedWriter writer = Files.newBufferedWriter(output);
             BufferedReader reader = Files.newBufferedReader(sanitize)) {
            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(sanitizeLine(patterns, line));
                writer.write(System.lineSeparator());
            }
        } catch (IOException e) {
            MoreFiles.deleteQuietly(output);
            throw e;
        }

        return output;
    }

    /**
     * Sanitize the provided line based on the patterns we look for by the provided filename.
     *
     * @param fileName the filename used to determine what pattern to match on
     * @param line     the line to sanitize
     * @return the sanitized line if it matches a pattern otherwise it is left as is
     */
    public String sanitizeLine(@Nonnull String fileName, @Nonnull String line) {
        List<Pattern> patterns = patternsByFile.get(fileName);
        if (patterns.isEmpty()) {
            log.trace("{} does not need to be sanitized", fileName);
            return line;
        }

        return sanitizeLine(patterns, line);
    }

    /**
     * Sanitized the provided list based on the patterns we look for by the provided filename.
     *
     * @param fileName the filename used to determine what pattern to match on
     * @param list     the list to be sanitized
     * @return the sanitized list if it matches a pattern otherwise it is left as is
     */
    public List<String> sanitizeList(@Nonnull String fileName, @Nonnull List<String> list) {
        return list.stream()
                .map(argument -> sanitizeLine(fileName, argument))
                .collect(Collectors.toList());
    }

    /**
     * Sanitize the provided map (its values) based on the basic patterns of the class.
     *
     * @param map the map to be sanitized
     * @return the sanitized map if it matches the basic patterns otherwise it is left as is
     */
    public Map<String, String> sanitizeMap(@Nonnull Map<String, String> map) {
        ImmutableMap.Builder<String, String> sanitizedMap = ImmutableMap.builder();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String property = entry.getKey();
            sanitizedMap.put(property, sanitizeValue(property, entry.getValue()));
        }
        
        return sanitizedMap.build();
    }

    /**
     * Sanitize the provided value if the associated property matches the patterns.
     *
     * @param property the property name that is checked against the patterns
     * @param value    the value the is to be sanitized
     * @return the sanitized value for the given property or the value as is if it does not need sanitization
     */
    public String sanitizeValue(@Nonnull String property, @Nonnull String value) {
        for (Pattern pattern : BASIC_PATTERNS) {
            if (pattern.matcher(property).matches()) {
                return SANITIZER_MESSAGE;
            }
        }

        return value;
    }

    /**
     * Go through the list of patterns and see if we can sanitize the line based on any matches.
     *
     * @param patterns the patterns to match the line on
     * @param line     the line that is to be sanitized
     * @return the sanitized line if it matches a pattern otherwise it is left as is
     */
    private String sanitizeLine(@Nonnull List<Pattern> patterns, @Nonnull String line) {
        for (Pattern pattern : patterns) {
            //This assumes the pattern only has one part to keep, and it always comes first
            //If/when we need more, we can generalize further at that time
            line = pattern.matcher(line).replaceAll("$1" + SANITIZER_MESSAGE);
        }

        return line;
    }
}