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

import com.atlassian.event.api.EventPublisher;
import com.atlassian.johnson.event.EventLevel;
import com.atlassian.johnson.event.EventType;
import com.atlassian.bitbucket.Product;
import com.atlassian.bitbucket.event.migration.MigrationCanceledEvent;
import com.atlassian.bitbucket.event.migration.MigrationFailedEvent;
import com.atlassian.bitbucket.event.migration.MigrationStartedEvent;
import com.atlassian.bitbucket.event.migration.MigrationSucceededEvent;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.stash.internal.db.DatabaseHandle;
import com.atlassian.stash.internal.db.DatabaseManager;
import com.atlassian.stash.internal.johnson.JohnsonUtils;
import com.atlassian.stash.internal.maintenance.MaintenanceTask;
import com.atlassian.stash.internal.maintenance.MaintenanceTaskFactory;
import com.atlassian.stash.internal.maintenance.MaintenanceType;
import com.atlassian.stash.internal.maintenance.TaskMaintenanceEvent;
import com.atlassian.stash.internal.migration.CanceledMigrationException;
import com.atlassian.stash.internal.migration.MigrationException;
import org.springframework.beans.factory.annotation.Value;

/**
 * Note this class should not be made available for component scan. It requires targetDatabase
 * to be supplied from a child context with the qualifier "{@value #QUALIFIER_TARGET_DATABASE}".
 */
public class DatabaseMigrationTask extends BaseMigrationTask {

    private final EventPublisher eventPublisher;
    private final I18nService i18nService;

    public DatabaseMigrationTask(DatabaseManager databaseManager, EventPublisher eventPublisher,
                                 MaintenanceTaskFactory factory, I18nService i18nService,
                                 DatabaseHandle targetDatabase,
                                 @Value("${migration.drain.db.timeout}") int drainTimeoutSeconds,
                                 @Value("${migration.drain.db.force.timeout}") int forceDrainTimeoutSeconds) {
        super(databaseManager, factory, i18nService, targetDatabase, drainTimeoutSeconds, forceDrainTimeoutSeconds);

        this.eventPublisher = eventPublisher;
        this.i18nService = i18nService;
    }

    @Override
    public void run() {
        MigrationException exception = null;
        try {
            eventPublisher.publish(new MigrationStartedEvent(this));

            super.run();
        } catch (RuntimeException e) {
            exception = convertToMigrationException(e);

            throw exception;
        } finally {
            publishMigrationEvent(exception);
        }
    }

    @Override
    protected MaintenanceTask createMigrationTask(MaintenanceTaskFactory factory, MigrationState state) {
        TaskMaintenanceEvent event = new TaskMaintenanceEvent(EventType.get("database-unavailable"),
                Product.NAME + " is unavailable while its data is migrated to a new database",
                EventLevel.get(JohnsonUtils.LEVEL_MAINTENANCE), MaintenanceType.MIGRATION);

        // The whole migration needs to execute in maintenance mode - wrap it in the MaintenanceModePhase
        return factory.maintenanceModePhaseBuilder()
                .event(event)
                .add(super.createMigrationTask(factory, state), 100)
                .build();
    }

    protected void publishMigrationEvent(MigrationException exception) {
        if (exception instanceof CanceledMigrationException) {
            eventPublisher.publish(new MigrationCanceledEvent(this));
        } else if (exception != null) {
            eventPublisher.publish(new MigrationFailedEvent(this));
        } else {
            eventPublisher.publish(new MigrationSucceededEvent(this));
        }
    }

    private MigrationException convertToMigrationException(RuntimeException e) {
        if (e instanceof MigrationException) {
            return (MigrationException) e;
        }
        return new MigrationException(i18nService.createKeyedMessage("bitbucket.migration.failed", Product.NAME), e);
    }
}
