package com.atlassian.bitbucket.mesh.repository;

import com.atlassian.bitbucket.mesh.git.exception.FailedPreconditionException;
import com.atlassian.bitbucket.mesh.io.MoreFiles;

import javax.annotation.Nonnull;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

import static com.atlassian.bitbucket.mesh.git.GitConstants.*;

class ResetRepositoryOperation extends ConcreteRepositoryVisitor<Void> {

    private final ReplicaStateRegistry replicaStateRegistry;
    private final RepositoryAttributeManager repositoryAttributeManager;

    ResetRepositoryOperation(ReplicaStateRegistry replicaStateRegistry,
                             RepositoryAttributeManager repositoryAttributeManager) {
        this.replicaStateRegistry = replicaStateRegistry;
        this.repositoryAttributeManager = repositoryAttributeManager;
    }

    @Override
    public Void defaultValue(Repository repository) {
        throw new FailedPreconditionException("Repository reset is not supported for " +
                repository.getClass().getSimpleName());
    }

    @Override
    Void visit(@Nonnull DefaultManagedRepository repository) {
        Path repoDir = repository.getPath();
        try {
            //Mark the repository as consistent to stop repairs
            replicaStateRegistry.remove(repository.getId());
            //Delete all refs
            Files.deleteIfExists(repoDir.resolve("packed-refs"));
            Path refsDir = repoDir.resolve(PATH_REFS);
            deleteDirectoryContents(refsDir);
            MoreFiles.mkdir(refsDir, "heads");
            MoreFiles.mkdir(refsDir, "tags");
            //Delete all reflogs
            deleteDirectoryContents(repoDir.resolve(PATH_LOGS));
            //Delete all dark refs
            deleteDirectoryContents(repoDir.resolve(PATH_DARK_REFS));

            //Delete the repository's dark refs and reflogs from the shared objects directory too
            SharedObjectsRepository sharedObjects = repository.getSharedObjects();
            Path sharedDir = sharedObjects.getPath();
            deleteDirectoryContents(sharedObjects.getReflogDir(repository));
            deleteDirectoryContents(MoreFiles.resolve(sharedDir, PATH_DARK_REFS, "r", repository.getLocalId()));
            //Mark the repository as dirty to ensure the (now deleted) refs are synced into shared objects, which
            //has the net effect of deleting the repository's refs in the shared-objects as well.
            Path dirtyMarker = sharedObjects.getDirtyMarker(repository);
            MoreFiles.mkdir(dirtyMarker.getParent());
            MoreFiles.touch(dirtyMarker);

            //Revert back to the default config to clear out any custom config settings
            Files.copy(repository.getTemplateDir().resolve(PATH_CONFIG), repository.getPath().resolve(PATH_CONFIG),
                    StandardCopyOption.REPLACE_EXISTING);

            //Mark the repository as consistent _again_, just in case a concurrent repair was running
            replicaStateRegistry.remove(repository.getId());
            //Clear out the version, content-hash and metadata-hash for the repository
            repositoryAttributeManager.delete(repository.getId());
            //Reset the version as if the repository has just been created (default is 1; the coordinated repository
            //creation operation increases it to 2)
            repositoryAttributeManager.setVersion(repository.getId(), 2L);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
        return null;
    }

    @Override
    Void visit(@Nonnull DefaultSharedObjectsRepository repository) {
        return null;
    }

    private static void deleteDirectoryContents(@Nonnull Path dir) throws IOException {
        try {
            MoreFiles.cleanDirectory(dir);
        } catch (FileNotFoundException | NoSuchFileException e) {
            //Directory does not exist; nothing to do
        }
    }
}
