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

import com.atlassian.activeobjects.spi.AbstractRestoreProgressMonitor;
import com.atlassian.activeobjects.spi.Backup;
import com.atlassian.activeobjects.spi.HotRestartEvent;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.stash.internal.maintenance.AbstractMaintenanceTask;
import com.atlassian.stash.internal.maintenance.backup.BackupState;
import com.atlassian.stash.internal.migration.MigrationException;
import com.atlassian.stash.internal.plugin.OsgiServiceProxyFactory;
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.FileInputStream;

import static com.google.common.base.Preconditions.checkState;

/**
 * Step in the backup/restore process that restores the ActiveObjects data to the target database.
 */
public class ActiveObjectsRestoreStep extends AbstractMaintenanceTask {

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

    private final Backup backup;
    private final EventPublisher eventPublisher;
    private final I18nService i18nService;
    private volatile Progress progress;
    private final RestoreState state;

    public ActiveObjectsRestoreStep(RestoreState state, EventPublisher eventPublisher, I18nService i18nService,
                                    OsgiServiceProxyFactory osgiServiceProxyFactory) {
        this.eventPublisher = eventPublisher;
        this.i18nService = i18nService;
        this.state = state;

        this.backup = osgiServiceProxyFactory.createProxy(Backup.class);
    }

    @Nonnull
    @Override
    public Progress getProgress() {
        return progress != null ? progress : new ProgressImpl(i18nService.getMessage("bitbucket.backup.restore.activeobjects"), 0);
    }

    @Override
    public void run() {
        File backupDir = state.getUnzippedBackupDirectory();
        checkState(backupDir != null, "Unpacked backup not found");

        File activeObjectsFile = new File(backupDir, BackupState.ACTIVE_OBJECTS_BACKUP_FILE);
        log.debug("Restoring ActiveObjects data from {}", activeObjectsFile.getAbsolutePath());

        try {
            FileInputStream stream = new FileInputStream(activeObjectsFile);
            try {
                backup.restore(stream, new RestoreMonitor());

                // Tell ActiveObjects to reset its internal database provider cache.
                // If we didn't have this, ActiveObjects would believe that it still operated on the source database.
                // The need for this statement surfaced in STASHDEV-1216: Viewing SSH keys fails after migrating to Postgres 9.
                eventPublisher.publish(HotRestartEvent.INSTANCE);
            } finally {
                IOUtils.closeQuietly(stream);
            }
        } catch (Exception e) {
            throw new MigrationException(i18nService.createKeyedMessage("bitbucket.backup.restore.activeobjects.failed", activeObjectsFile.getAbsolutePath()), e);
        }
    }

    private class RestoreMonitor extends AbstractRestoreProgressMonitor {

        private static final int PROGRESS_SCHEMA = 20;
        private static final int PROGRESS_DATA = 80;

        //Bring generation of these messages forward to avoid Hz classloader issues when called back by AO
        private final String restoreDefinitionsMsg = i18nService.getMessage("bitbucket.backup.activeobjects.restore.definitions");
        private final String restoreDataMsg = i18nService.getMessage("bitbucket.backup.activeobjects.restore.data");

        private int tableCount;
        private int tablesRestored;

        @Override
        public void beginTableDefinitionsRestore() {
            progress = new ProgressImpl(restoreDefinitionsMsg, 0);
        }

        @Override
        public void beginTablesRestore() {
            progress = new ProgressImpl(restoreDataMsg, PROGRESS_SCHEMA);
        }

        @Override
        public void endTableDataRestore(String tableName) {
            progress = new ProgressImpl(progress.getMessage(), PROGRESS_SCHEMA + (PROGRESS_DATA * ++tablesRestored) / tableCount);
        }

        @Override
        public void updateTotalNumberOfTablesToRestore(int tableCount) {
            this.tableCount = tableCount;
        }
    }
}
