package com.atlassian.bitbucket.mesh.git.gc;

import com.atlassian.bitbucket.mesh.git.GitRepositoryLayout;
import com.atlassian.bitbucket.mesh.io.MoreFiles;
import com.atlassian.bitbucket.mesh.repository.SharedObjectsRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

class PruneDeletedRepositoryRefsOperation implements GcOperation {

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

    private final Path dirtyMarker;
    private final String localId;
    private final GitRepositoryLayout repositoryLayout;
    private final SharedObjectsRepository sharedObjects;

    PruneDeletedRepositoryRefsOperation(String localId, SharedObjectsRepository sharedObjects,
                                        GitRepositoryLayout repositoryLayout, Path dirtyMarker) {
        this.dirtyMarker = dirtyMarker;
        this.localId = localId;
        this.repositoryLayout = repositoryLayout;
        this.sharedObjects = sharedObjects;
    }

    @Override
    public void run(@Nonnull GcContext context) {
        long start = System.currentTimeMillis();
        deleteRefLogs();
        deleteLooseRefs();
        deletePackedRefs();
        deleteDirtyMarker();

        context.addAction(GcActionType.VACUUM_PRUNE_DELETED_REPOSITORY, start);
    }

    private void deleteDirtyMarker() {
        try {
            Files.delete(dirtyMarker);
        } catch (NoSuchFileException | FileNotFoundException e) {
            //File doesn't exist anymore, which is fine
        } catch (IOException e) {
            log.warn("[{}] Could not delete dirty marker for {}", sharedObjects, localId, e);
        }
    }

    private void deleteRefLogs() {
        Path refLogsPath = MoreFiles.resolve(sharedObjects.getPath(), "logs", "r", localId);
        try {
            MoreFiles.deleteRecursively(refLogsPath);
        } catch (NoSuchFileException | FileNotFoundException e) {
            //Fine, nothing to do
        } catch (IOException e) {
            log.warn("[{}] Failed to delete vacuumed ref logs for deleted repository {}", sharedObjects, localId, e);
        }
    }

    private void deleteLooseRefs() {
        //Delete any loose refs
        Path refsPath = MoreFiles.resolve(sharedObjects.getPath(), "refs", "r", localId);
        try {
            MoreFiles.deleteRecursively(refsPath);
        } catch (NoSuchFileException | FileNotFoundException e) {
            //Fine, nothing to do
        } catch (IOException e) {
            log.warn("[{}] Failed to delete vacuumed refs for deleted repository {}", sharedObjects, localId, e);
        }
    }

    private void deletePackedRefs() {
        try {
            repositoryLayout.editFile(sharedObjects, "packed-refs", (reader, writer) -> {
                if (reader == null) {
                    return;
                }

                String namespace = " refs/r/" + localId;
                boolean pruning = false;
                BufferedReader bufferedReader = new BufferedReader(reader);
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    //prune peeled objects if the previous line was also pruned
                    pruning = (pruning && line.startsWith("^")) || line.contains(namespace);
                    if (!pruning) {
                        writer.write(line);
                        writer.write('\n');
                    }
                }
            });
        } catch (IOException e) {
            log.warn("[{}] Could not prune refs for deleted repository {}", sharedObjects, localId);
        }
    }
}
