package com.atlassian.stash.internal.maintenance;

import com.atlassian.stash.internal.db.DatabaseHandle;
import com.atlassian.stash.internal.db.DatabaseManager;
import com.atlassian.stash.internal.jdbc.DataSourceConfiguration;
import com.atlassian.stash.internal.maintenance.backup.*;
import com.atlassian.stash.internal.maintenance.latch.LatchMode;
import com.atlassian.stash.internal.maintenance.migration.*;
import com.atlassian.stash.internal.maintenance.restore.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringValueResolver;

import javax.annotation.Nonnull;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * @since 2.1
 */
@Component("maintenanceTaskFactory")
public class SpringMaintenanceTaskFactory implements MaintenanceTaskFactory {

    private final ConfigurableBeanFactory beanFactory;
    private final StringValueResolver stringValueResolver;
    private final DatabaseManager databaseManager;

    @Autowired
    public SpringMaintenanceTaskFactory(ApplicationContext applicationContext, DatabaseManager databaseManager) {
        this.beanFactory = (ConfigurableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        this.databaseManager = databaseManager;

        this.stringValueResolver = new StringValueResolver() {
            @Override
            public String resolveStringValue(String s) {
                return beanFactory.resolveEmbeddedValue(s);
            }
        };
    }

    @Nonnull
    @Override
    public ActiveObjectsRestoreStep activeObjectsRestoreStep(@Nonnull RestoreState state) {
        return create(ActiveObjectsRestoreStep.class, "restoreState", checkNotNull(state, "state"));
    }

    @Nonnull
    @Override
    public BackupTask backupTask() {
        return create(BackupTask.class);
    }

    @Nonnull
    @Override
    public ExternalBackupTask externalBackupTask() {
        return create(ExternalBackupTask.class);
    }

    @Nonnull
    @Override
    public ActiveObjectsBackupStep activeObjectsBackupStep(@Nonnull BackupState state) {
        return create(ActiveObjectsBackupStep.class, "backupState", checkNotNull(state, "state"));
    }

    @Nonnull
    @Override
    public BackupClientPlaceholderStep backupClientBackupStep() {
        return create(BackupClientPlaceholderStep.class);
    }

    @Nonnull
    @Override
    public BackupPhase.Builder backupPhaseBuilder(@Nonnull BackupState state) {
        return create(BackupPhase.Builder.class, "backupState", checkNotNull(state, "state"));
    }

    @Nonnull
    @Override
    public ChangelogsBackupStep changelogsBackupStep(@Nonnull BackupState state) {
        return create(ChangelogsBackupStep.class, "backupState", checkNotNull(state, "state"));
    }

    @Nonnull
    @Override
    public ConfigurationBackupStep configurationBackupStep(@Nonnull BackupState state) {
        return create(ConfigurationBackupStep.class, "backupState", checkNotNull(state, "state"));
    }

    @Nonnull
    @Override
    public DatabaseBackupStep databaseBackupStep(@Nonnull BackupState state) {
        return create(DatabaseBackupStep.class, "backupState", checkNotNull(state, "state"));
    }

    @Nonnull
    @Override
    public DatabaseRestoreStep databaseRestoreStep(@Nonnull RestoreState state) {
        return create(DatabaseRestoreStep.class, "restoreState", checkNotNull(state, "state"));
    }

    @Nonnull
    @Override
    public FinalizeMigrationStep finalizeMigrationStep(@Nonnull MigrationState state) {
        return create(FinalizeMigrationStep.class, "restoreState", checkNotNull(state, "state"));
    }

    @Nonnull
    @Override
    public LatchAndDrainDatabaseStep latchAndDrainDatabaseMigrationStep(@Nonnull LatchMode latchMode) {
        return create(LatchAndDrainDatabaseMigrationStep.class, "latchMode", latchMode);
    }

    @Nonnull
    @Override
    public LatchAndDrainDatabaseStep latchAndDrainDatabaseBackupStep(@Nonnull LatchMode latchMode) {
        return create(LatchAndDrainDatabaseBackupStep.class, "latchMode", latchMode);
    }

    @Nonnull
    @Override
    public LatchAndDrainScmStep latchAndDrainScmStep(@Nonnull LatchMode latchMode) {
        return create(LatchAndDrainScmStep.class, "latchMode", latchMode);
    }

    @Nonnull
    @Override
    public MaintenanceModePhase.Builder maintenanceModePhaseBuilder() {
        return create(MaintenanceModePhase.Builder.class);
    }

    @Nonnull
    @Override
    public DatabaseMigrationTask migrationTask(@Nonnull DataSourceConfiguration configuration) {
        return create(DatabaseMigrationTask.class, BaseMigrationTask.QUALIFIER_TARGET_DATABASE,
                databaseManager.prepareDatabase(checkNotNull(configuration, "configuration")));
    }

    @Nonnull
    @Override
    public ReleaseAffixedDatabaseStep releaseAffixedDatabaseStep() {
        return create(ReleaseAffixedDatabaseStep.class);
    }

    @Nonnull
    @Override
    public RestorePhase.Builder restorePhaseBuilder(@Nonnull RestoreState state) {
        return create(RestorePhase.Builder.class, "restoreState", checkNotNull(state, "state"));
    }

    @Nonnull
    @Override
    public DatabaseSetupTask setupTask(@Nonnull DataSourceConfiguration configuration) {
        return create(DatabaseSetupTask.class, BaseMigrationTask.QUALIFIER_TARGET_DATABASE,
                databaseManager.prepareDatabase(checkNotNull(configuration, "configuration")));
    }

    @Nonnull
    @Override
    public UnlatchDatabaseStep unlatchDatabaseStep(@Nonnull LatchMode latchMode, DatabaseHandle databaseHandle) {
        return create(UnlatchDatabaseStep.Builder.class, "latchMode", latchMode)
                .target(databaseHandle)
                .build();
    }

    @Nonnull
    @Override
    public UnlatchScmStep unlatchScmStep(@Nonnull LatchMode latchMode) {
        return create(UnlatchScmStep.class, "latchMode", latchMode);
    }

    @Nonnull
    @Override
    public UnpackBackupFilesStep unpackBackupFilesStep(@Nonnull MigrationState state) {
        return create(UnpackBackupFilesStep.class, "migrationState", checkNotNull(state, "state"));
    }

    private <T> T create(Class<T> clazz) {
        return create(clazz, null, null);
    }

    private <T> T create(Class<T> clazz, String contextBeanName, Object context) {
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory(beanFactory);
        factory.copyConfigurationFrom(beanFactory);
        factory.addEmbeddedValueResolver(stringValueResolver);

        //The app context is not available from the child context
        if (context != null) {
            checkArgument(contextBeanName != null, "no context bean name was provided");
            factory.registerSingleton(contextBeanName, context);
        }

        return clazz.cast(factory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false));
    }
}
