package com.atlassian.bitbucket.mesh.app;

import com.atlassian.bitbucket.mesh.config.StorageConfig;
import com.atlassian.bitbucket.mesh.io.MoreFiles;
import com.atlassian.bitbucket.mesh.io.PruneStaleFileCallback;
import com.atlassian.bitbucket.mesh.layout.StorageLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;

import javax.annotation.Nonnull;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant;

import static com.atlassian.bitbucket.mesh.io.MoreFiles.pruneStaleFiles;

public class DefaultStorageManager implements StorageManager {

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

    private final StorageConfig config;
    private final Path tempDir;

    public DefaultStorageManager(StorageConfig config, StorageLayout storageLayout) {
        this.config = config;

        tempDir = storageLayout.getTempDir();
    }

    @Scheduled(initialDelayString = "PT1M", fixedRateString = "${storage.temp-cleanup-raw-interval}")
    public void cleanTmp() {
        CleanupCallback callback = new CleanupCallback();

        Instant now = Instant.now();
        try {
            //Use the file system time if available, in the off chance that the tmp dir is on NFS and we have
            //clock drift between NFS and the local node.
            now = MoreFiles.getFileSystemTime(tempDir);
        } catch (FileNotFoundException | NoSuchFileException e) {
            //tempDir does not exist
        } catch (IOException e) {
            log.debug("Could not determine file system time for {}; using the local time instead", tempDir, e);
        }
        try {
            pruneStaleFiles(tempDir, now.minus(config.getTempExpiry()), true, callback);
        } catch (IOException e) {
            log.warn("Failed the clean up of {}", tempDir, e);
        } finally {
            if (callback.getTotalAttemptedDeletes() > 0) {
                log.info("Finished the clean up of {}. Successfully deleted {}/{} files and directories. " +
                                "Unsuccessfully deleted {}/{} files and directories.",
                        tempDir, callback.getTotalSuccessfulDeletes(), callback.getTotalAttemptedDeletes(),
                        callback.getTotalUnsuccessfulDeletes(), callback.getTotalAttemptedDeletes());
            } else {
                log.debug("Finished the clean up of {}. No files or folders were deleted.", tempDir);
            }
        }
    }

    private static class CleanupCallback implements PruneStaleFileCallback {

        private int totalAttemptedDeletes;
        private int totalSuccessfulDeletes;
        private int totalUnsuccessfulDeletes;

        public int getTotalAttemptedDeletes() {
            return totalAttemptedDeletes;
        }

        public int getTotalSuccessfulDeletes() {
            return totalSuccessfulDeletes;
        }

        public int getTotalUnsuccessfulDeletes() {
            return totalUnsuccessfulDeletes;
        }

        @Override
        public void onDeleted(@Nonnull Path path, @Nonnull BasicFileAttributes attrs) {
            totalSuccessfulDeletes++;
            totalAttemptedDeletes++;
        }

        @Override
        public void onError(@Nonnull Path path, @Nonnull IOException e) throws IOException {
            totalUnsuccessfulDeletes++;
            totalAttemptedDeletes++;
        }
    }
}
