package org.zalando.stups.fullstop.jobs.scm;

import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.zalando.kontrolletti.KontrollettiOperations;
import org.zalando.kontrolletti.ListCommitsResponse;
import org.zalando.kontrolletti.resources.Repository;
import org.zalando.stups.clients.kio.Application;
import org.zalando.stups.clients.kio.ApplicationBase;
import org.zalando.stups.clients.kio.KioOperations;
import org.zalando.stups.fullstop.jobs.FullstopJob;
import org.zalando.stups.fullstop.teams.Account;
import org.zalando.stups.fullstop.teams.TeamOperations;
import org.zalando.stups.fullstop.violation.Violation;
import org.zalando.stups.fullstop.violation.ViolationBuilder;
import org.zalando.stups.fullstop.violation.ViolationSink;
import org.zalando.stups.fullstop.violation.entity.AccountRegion;
import org.zalando.stups.fullstop.violation.service.ApplicationLifecycleService;

import javax.annotation.PostConstruct;
import java.time.ZonedDateTime;
import java.util.Optional;
import java.util.Set;

import static java.time.LocalDate.now;
import static java.time.ZoneOffset.UTC;
import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang3.StringUtils.abbreviate;
import static org.slf4j.LoggerFactory.getLogger;
import static org.zalando.kontrolletti.CommitRangeRequest.Builder.inRepository;
import static org.zalando.stups.fullstop.violation.ViolationType.MISSING_SPEC_LINKS;

@Component
public class ScmCommitsJob implements FullstopJob {

    private final Logger log = getLogger(getClass());

    private static final String EVENT_ID = "scmCommitsJob";
    private final KioOperations kio;
    private final KontrollettiOperations kontrolletti;
    private final ApplicationLifecycleService lifecycle;
    private final ViolationSink violationSink;
    private final TeamOperations teamService;

    @Autowired
    public ScmCommitsJob(
            final KioOperations kio,
            final KontrollettiOperations kontrolletti,
            final ApplicationLifecycleService lifecycle,
            final ViolationSink violationSink,
            final TeamOperations teamService) {
        this.kio = kio;
        this.kontrolletti = kontrolletti;
        this.lifecycle = lifecycle;
        this.violationSink = violationSink;
        this.teamService = teamService;
    }

    @PostConstruct
    public void init() {
        log.info("{} initialized", getClass().getSimpleName());
    }

    @Scheduled(
            fixedRate = 1000 * 60 * 150, // 2.5 hours
            initialDelay = 1000 * 60 * 10 // 10 minutes
    )
    @Override
    public void run() {
        log.info("{} started processing", getClass().getSimpleName());

        final Set<String> activeAccountIds = teamService.getActiveAccounts()
                .stream()
                .map(Account::getId)
                .collect(toSet());

        kio.listApplications().stream()
                .map(ApplicationBase::getId)
                .map(kio::getApplicationById)
                .filter(Application::isActive)
                .forEach(app -> processApplication(app, activeAccountIds));

        log.info("{} finished processing", getClass().getSimpleName());
    }

    private void processApplication(final Application app, final Set<String> activeAccountIds) {
        try {
            lifecycle.findDeployments(app.getId()).stream()
                    .filter(deployment -> activeAccountIds.contains(deployment.getAccount()))
                    .forEach(deployment ->
                    Optional.ofNullable(app.getScmUrl())
                            .filter(StringUtils::isNotBlank)
                            .map(kontrolletti::normalizeRepositoryUrl) // TODO is this necessary?
                            .map(kontrolletti::getRepository)
                            .flatMap(repo -> findViolationInRepo(repo, deployment, app))
                            .ifPresent(violationSink::put));
        } catch (final Exception e) {
            log.warn("Failed to verify commits in scm repository of " + app, e);
        }
    }

    private Optional<Violation> findViolationInRepo(final Repository repository, final AccountRegion deployment, final Application app) {
        final ZonedDateTime yesterdayMidnight = now().minusDays(1).atStartOfDay(UTC);
        final ZonedDateTime todayMidnight = yesterdayMidnight.plusDays(1);
        return Optional.ofNullable(kontrolletti.listCommits(
                inRepository(repository)
                        .fromDate(yesterdayMidnight)
                        .toDate(todayMidnight)
                        .isValid(false)
                        .perPage(10)
                        .build()))
                .map(ListCommitsResponse::getContent)
                .filter(list -> !list.isEmpty())
                .map(invalidCommits -> new ViolationBuilder()
                        .withAccountId(deployment.getAccount())
                        .withRegion(deployment.getRegion())
                        .withType(MISSING_SPEC_LINKS)
                        // make sure to produce maximum one violation per repository and day
                        .withEventId(EVENT_ID + "-" + repository.getUrl() + "-" + yesterdayMidnight.format(ISO_LOCAL_DATE))
                        .withPluginFullyQualifiedClassName(ScmCommitsJob.class)
                        .withApplicationId(app.getId())
                        .withMetaInfo(ImmutableMap.of(
                                "application_id", app.getId(),
                                "repository", repository.getUrl(),
                                "invalid_commits", invalidCommits.stream()
                                        .map(commit -> commit.getCommitId() + " (" + abbreviate(commit.getMessage(), 32) + ")")
                                        .collect(joining("; "))))
                        .build());

    }

}

@@ -737,77 +737,37 @@
 top.
-teams.Account;%0Aimport org.zalando.stups.fullstop.teams.TeamOperations
+jobs.common.AccountIdSupplier
 ;%0Aim
@@ -1408,57 +1408,8 @@
 ng;%0A
-import static java.util.stream.Collectors.toSet;%0A
 impo
@@ -2079,34 +2079,34 @@
 nal 
-TeamOperations teamService
+AccountIdSupplier accounts
 ;%0A%0A 
@@ -2363,34 +2363,34 @@
 nal 
-TeamOperations teamService
+AccountIdSupplier accounts
 ) %7B%0A
@@ -2552,33 +2552,27 @@
 his.
-teamService = teamService
+accounts = accounts
 ;%0A  
@@ -2982,135 +2982,21 @@
 s = 
-teamService.getActiveAccounts()%0A                .stream()%0A                .map(Account::getId)%0A                .collect(toSet()
+accounts.get(
 );%0A%0A

refactor ScmCommitsJob