package com.atlassian.stash.internal.scm.git.pull.task;

import com.atlassian.bitbucket.concurrent.BucketProcessor;
import com.atlassian.stash.internal.scm.git.pull.PullRequestRefBuilder;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import com.atlassian.bitbucket.scm.git.GitScmConfig;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.user.SecurityService;
import com.atlassian.bitbucket.util.UncheckedOperation;
import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.File;
import java.io.FilenameFilter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @since 3.4
 */
public class PullRequestTaskProcessor implements BucketProcessor<PullRequestTask> {

    private static final FilenameFilter IS_VERSION_FILE = new FilenameFilter() {

        @Override
        public boolean accept(File dir, String name) {
            return name.startsWith("v");
        }
    };
    private static final PullRequestTaskVisitor<Integer> TO_VERSION =
            new PullRequestTaskVisitor<Integer>() {

                @Override
                public Integer visit(@Nonnull RemoveOldPullRequestVersionsTask task) {
                    return task.getVersion();
                }
            };

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

    private final GitScmConfig config;
    private final RepositoryService repositoryService;
    private final SecurityService securityService;

    public PullRequestTaskProcessor(GitScmConfig config, RepositoryService repositoryService,
                                    SecurityService securityService) {
        this.config = config;
        this.repositoryService = repositoryService;
        this.securityService = securityService;
    }

    @Override
    public void process(@Nonnull String bucketId, @Nonnull List<PullRequestTask> tasks) {
        final int repositoryId;
        try {
            repositoryId = Integer.parseInt(bucketId);
        } catch (NumberFormatException e) {
            log.warn("Discarding {} tasks for invalid bucket [{}]", tasks.size(), bucketId);

            return;
        }

        Repository repository = securityService.withPermission(Permission.REPO_READ, "Processing pull request tasks")
                .call(new UncheckedOperation<Repository>() {

                    @Override
                    public Repository perform() {
                        return repositoryService.getById(repositoryId);
                    }
                });
        if (repository == null) {
            log.info("{}: Discarding {} task(s); the repository has been deleted", bucketId, tasks.size());

            return;
        }

        cleanUpVersions(repository, mapVersions(tasks));
    }

    private void cleanUpVersions(Repository repository, Map<Long, Integer> versionsByPullRequest) {
        log.debug("{}: Cleaning up versions for {} pull request(s)", repository, versionsByPullRequest.size());

        File repositoryDir = config.getRepositoryDir(repository);
        for (Map.Entry<Long, Integer> entry : versionsByPullRequest.entrySet()) {
            File pullRequestDir = PullRequestRefBuilder.darkRef().id(entry.getKey()).toFile(repositoryDir);
            String[] versions = pullRequestDir.list(IS_VERSION_FILE);
            if (versions == null || versions.length == 0) {
                log.trace("{}: Pull request {} has no cached versions", repository, entry.getKey());
                continue;
            }

            int keep = entry.getValue();
            for (String version : versions) {
                try {
                    if (Integer.parseInt(version.substring(1)) < keep) {
                        if (new File(pullRequestDir, version).delete()) {
                            log.trace("{}:{}: Deleted {}", repository, entry.getKey(), version);
                        } else {
                            //The next time the version for this pull request changes we'll end up trying this again,
                            //so there's no point in trying deleteOnExit
                            log.warn("{}:{}: Failed to delete {}", repository, entry.getKey(), version);
                        }
                    }
                } catch (NumberFormatException e) {
                    log.debug("{}:{}: Ignoring unexpected version-like file {{}]", repository, entry.getKey(), version);
                }
            }
        }
    }

    private Map<Long, Integer> mapVersions(List<PullRequestTask> tasks) {
        Map<Long, Integer> versionsByPullRequest = new HashMap<>();
        for (PullRequestTask task : tasks) {
            long id = task.getPullRequestId();
            int version = task.accept(TO_VERSION);

            Integer current = versionsByPullRequest.get(id);
            if (current == null || current < version) {
                versionsByPullRequest.put(id, version);
            }
        }

        return ImmutableMap.copyOf(versionsByPullRequest);
    }
}
