package com.atlassian.stash.internal.maintenance.backup;

import com.atlassian.bitbucket.Product;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.util.Progress;
import com.atlassian.bitbucket.util.ProgressImpl;
import com.atlassian.stash.internal.ApplicationConstants;
import com.atlassian.stash.internal.ApplicationSettings;
import com.atlassian.stash.internal.backup.BackupException;
import com.atlassian.stash.internal.config.DataSourcePropertySerializer;
import com.atlassian.stash.internal.jdbc.DataSourceConfiguration;
import com.atlassian.stash.internal.maintenance.AbstractMaintenanceTask;
import com.google.common.io.Files;
import com.google.common.io.Flushables;
import de.schlichtherle.truezip.zip.ZipEntry;
import de.schlichtherle.truezip.zip.ZipOutputStream;
import org.apache.commons.io.output.CloseShieldOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.*;
import java.util.Properties;

import static com.atlassian.stash.internal.db.DatabaseConstants.PROP_JDBC_DRIVER;
import static com.google.common.base.Preconditions.checkState;

/**
 * Step in the backup process that backs up the {@code bitbucket.properties} file.
 */
public class ConfigurationBackupStep extends AbstractMaintenanceTask {

    public static final String BANNER = "#> Produced for backup from datasource configuration";

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

    private final File sharedHomeDir;
    private final I18nService i18nService;
    private volatile int progress;
    private final BackupState state;

    public ConfigurationBackupStep(BackupState state, ApplicationSettings applicationSettings, I18nService i18nService) {
        this.sharedHomeDir = applicationSettings.getSharedHomeDir();
        this.i18nService = i18nService;
        this.state = state;
    }

    @Nonnull
    @Override
    public Progress getProgress() {
        return new ProgressImpl(
                i18nService.getMessage("bitbucket.backup.configuration", Product.NAME),
                progress);
    }

    @Override
    public void run() {
        ZipOutputStream stream = state.getBackupZipStream();
        checkState(stream != null, "A backup ZipOutputStream is required");

        try {
            ZipEntry entry = new ZipEntry(ApplicationConstants.CONFIG_FILE);
            stream.putNextEntry(entry);

            log.debug("Backing up {} configuration to {}", Product.NAME, ApplicationConstants.CONFIG_FILE);
            writeConfigurationBackup(new CloseShieldOutputStream(stream));

            stream.closeEntry();
            progress = 100;
        } catch (IOException e) {
            throw new BackupException(i18nService.createKeyedMessage("bitbucket.backup.configuration.failed", Product.NAME), e);
        }
    }

    private boolean containsJdbcProperties(File config) throws IOException {
        Properties properties = new Properties();
        properties.load(Files.asByteSource(config).openBufferedStream());
        return properties.containsKey(PROP_JDBC_DRIVER);
    }

    private void writeConfigurationBackup(OutputStream stream) throws IOException {
        File config = new File(sharedHomeDir, ApplicationConstants.CONFIG_FILE);

        if (config.exists()) {
            Files.copy(config, stream);
        }

        if (!config.exists() || !containsJdbcProperties(config)) {
            //bitbucket.properties does not exist, or it does not contain the JDBC properties.
            //Output the current datasource configuration (for record keeping)
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream));

            if (config.exists()) {
                //The existing properties have been copied over.
                //Add a new line so we won't write at the end of an existing line
                writer.write("\n");
            }

            try {
                writeDefaultConfiguration(writer);
            } finally {
                //Flush the writer but do _not_ close it. Flushing it pushes through any buffered output, but we do
                //not want to close the output stream we wrapped when we constructed it
                Flushables.flush(writer, false);
            }
        }
    }

    /**
     * Writes the current {@link DataSourceConfiguration} to the provided {@code writer}.
     * <p>
     * A {@link #BANNER banner} is written first, which enables the restore processing to detect that the configuration
     * being restored is just a placeholder and should not actually be restored.
     *
     * @param writer the writer to which the configuration should be written
     * @throws IOException if the configuration cannot be written
     * @see com.atlassian.stash.internal.config.DataSourcePropertySerializer
     */
    private void writeDefaultConfiguration(BufferedWriter writer) throws IOException {
        DataSourcePropertySerializer serializer = new DataSourcePropertySerializer(state.getSourceDatabase().getConfiguration());
        writer.write(BANNER);
        writer.write("\n");
        serializer.writeTo(writer);
    }
}
