package com.atlassian.stash.internal.integrity;

import com.atlassian.bitbucket.license.LicenseService;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import com.atlassian.bitbucket.user.SecurityService;
import com.atlassian.extras.api.bitbucket.BitbucketServerLicense;
import com.atlassian.scheduler.*;
import com.atlassian.scheduler.config.*;
import com.atlassian.stash.internal.ApplicationConstants;
import com.atlassian.stash.internal.mode.DefaultApplicationMode;
import com.atlassian.stash.internal.scheduling.ScheduledJobSource;
import com.atlassian.stash.internal.spring.AbstractSmartLifecycle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@DefaultApplicationMode
@Component
public class IntegrityCheckStartupTask extends AbstractSmartLifecycle implements ScheduledJobSource {

    static final JobId JOB_ID = JobId.of(StartupIntegrityJobRunner.class.getSimpleName());
    static final JobRunnerKey JOB_RUNNER_KEY = JobRunnerKey.of(StartupIntegrityJobRunner.class.getName());

    private final IntegrityCheckReporter integrityCheckReporter;
    private final IntegrityCheckService integrityCheckService;
    private final boolean runIntegrityCheck;
    private final SecurityService securityService;

    @Autowired
    public IntegrityCheckStartupTask(LicenseService licenseService,
                                     ApplicationPropertiesService propertiesService,
                                     IntegrityCheckReporter integrityCheckReporter,
                                     IntegrityCheckService integrityCheckService,
                                     SecurityService securityService) {
        this.integrityCheckReporter = integrityCheckReporter;
        this.integrityCheckService = integrityCheckService;
        this.securityService = securityService;

        if (propertiesService.isDisasterRecovery()) {
            BitbucketServerLicense license = licenseService.get();
            if (license != null && license.isClusteringEnabled()) {
                runIntegrityCheck = true;
            } else {
                integrityCheckReporter.warn("No Data Center license installed. Not running integrity checks.");
                runIntegrityCheck = false;
            }
        } else {
            runIntegrityCheck = false;
        }
    }

    @Override
    public int getPhase() {
        return ApplicationConstants.LIFECYCLE_PHASE_INTEGRITY_CHECK;
    }

    @Override
    public void schedule(@Nonnull SchedulerService schedulerService) throws SchedulerServiceException {
        if (runIntegrityCheck) {
            try {
                integrityCheckReporter.info("Scheduling integrity check job {}", JOB_ID);
                StartupIntegrityJobRunner jobRunner = new StartupIntegrityJobRunner();
                schedulerService.registerJobRunner(JOB_RUNNER_KEY, jobRunner);
                Date startTime = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(15));
                schedulerService.scheduleJob(JOB_ID, JobConfig.forJobRunnerKey(JOB_RUNNER_KEY)
                        .withRunMode(RunMode.RUN_ONCE_PER_CLUSTER)
                        .withSchedule(Schedule.runOnce(startTime)));
            } catch (SchedulerServiceException e) {
                integrityCheckReporter.error("Failed to schedule integrity check job", e);
            }
        }
    }

    @Override
    public void start() {
        if (runIntegrityCheck) {
            integrityCheckReporter.debug("Integrity check startup task started");
            securityService.withPermission(Permission.SYS_ADMIN, "integrity check operations")
                    .call(integrityCheckService::rescopeOpenPullRequests);
        }
        super.start();
    }

    @Override
    public void unschedule(@Nonnull SchedulerService schedulerService) throws SchedulerServiceException {
        if (runIntegrityCheck) {
            schedulerService.unregisterJobRunner(JOB_RUNNER_KEY);
        }
    }

    private class StartupIntegrityJobRunner implements JobRunner {

        @Nullable
        @Override
        public JobRunnerResponse runJob(JobRunnerRequest ignored) {
            securityService.withPermission(Permission.SYS_ADMIN, "integrity check operations").call(() -> {
                integrityCheckService.runChecks();
                return null;
            });

            return JobRunnerResponse.success();
        }
    }
}
