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

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.stash.internal.ApplicationSettings;
import com.atlassian.stash.internal.maintenance.AbstractMaintenanceTask;
import com.atlassian.stash.internal.maintenance.migration.MigrationState;
import com.atlassian.stash.internal.migration.MigrationException;
import com.atlassian.bitbucket.util.Progress;
import com.atlassian.bitbucket.util.ProgressImpl;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import static com.atlassian.bitbucket.util.FileUtils.createTempDir;
import static com.google.common.base.Preconditions.checkState;

public class UnpackBackupFilesStep extends AbstractMaintenanceTask {

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

    private final I18nService i18nService;
    private volatile int progress;
    private final ApplicationSettings settings;
    private final MigrationState state;

    public UnpackBackupFilesStep(MigrationState state, I18nService i18nService, ApplicationSettings settings) {
        this.i18nService = i18nService;
        this.settings = settings;
        this.state = state;
    }

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

    @Override
    public void run() {
        File backupFile = state.getBackupFile();
        checkState(backupFile != null && backupFile.exists(), "Backup file not found");

        try {
            state.setUnzippedBackupDirectory(unpackZip(backupFile));
        } catch (IOException e) {
            throw new MigrationException(i18nService.createKeyedMessage("bitbucket.backup.restore.unpacking.backup.failed", backupFile.getAbsolutePath()), e);
        }
    }

    protected File unpackZip(File zipFile) throws IOException {
        File directory = createTempDir(zipFile.getName() + "-unpack", ".backup", settings.getTempDir());
        log.debug("Unpacking backup files to {}", directory.getAbsolutePath());

        ZipFile zip = new ZipFile(zipFile);
        try {
            Enumeration<? extends ZipEntry> entries = zip.entries();
            while (entries.hasMoreElements()) {
                if (isCanceled()) {
                    log.debug("Canceled while unpacking backup files");
                    return directory;
                }
                ZipEntry entry = entries.nextElement();
                File file = new File(directory, entry.getName());

                log.debug("Unpacking {} ({} bytes) to {}",
                        entry.getName(), entry.getSize(), file.getAbsolutePath());
                long bytes = IOUtils.copyLarge(zip.getInputStream(entry), new FileOutputStream(file));
                if (bytes != entry.getSize()) {
                    log.warn("{} may not have been completely extracted; unpacked {} bytes where {} were expected",
                            entry.getName(), bytes, entry.getSize());
                }
            }
            log.debug("All backup files have been unpacked");
        } finally {
            progress = 100;
            try {
                zip.close();
            } catch (IOException e) {
                log.warn("Failed to close backup file " + zipFile.getAbsolutePath(), e);
            }
        }

        return directory;
    }

}
