package com.atlassian.bitbucket.mesh.transaction;

import com.atlassian.bitbucket.mesh.git.hook.GitHookOutputWriter;
import com.atlassian.bitbucket.mesh.git.ref.RefUpdate;
import com.atlassian.bitbucket.mesh.repository.Repository;
import com.atlassian.bitbucket.mesh.util.BuilderSupport;
import com.atlassian.bitbucket.mesh.util.Person;
import com.google.common.collect.ImmutableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Objects;
import java.util.Set;

import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang3.StringUtils.appendIfMissing;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

public class ReferenceTransaction {

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

    private final Set<RefUpdate> refUpdates;
    private final Repository repository;
    private final ReferenceTransactionState state;
    private final Person user;
    private final GitHookOutputWriter writer;

    private boolean rejected;

    private ReferenceTransaction(Builder builder) {
        refUpdates = builder.refUpdates.build();
        repository = builder.repository;
        state = builder.state;
        user = builder.user;
        writer = builder.writer;
    }

    @Nonnull
    public Set<RefUpdate> getRefUpdates() {
        return refUpdates;
    }

    @Nonnull
    public Repository getRepository() {
        return repository;
    }

    @Nonnull
    public ReferenceTransactionState getState() {
        return state;
    }

    @Nullable
    public Person getUser() {
        return user;
    }

    public boolean isRejected() {
        return rejected;
    }

    @Override
    public String toString() {
        return "ReferenceTransaction{" +
                "refUpdates=" + refUpdates +
                ", repository=" + repository +
                ", state=" + state +
                '}';
    }

    public void reject(@Nullable String message) {
        rejected = true;
        if (isNotBlank(message)) {
            try {
                writer.writeStderr(appendIfMissing(message, "\n"));
            } catch (IOException e) {
                log.warn("[{}] Failed to write veto message to client: {}", repository, message);
            }
        }
    }

    public static class Builder extends BuilderSupport {

        private final ImmutableSet.Builder<RefUpdate> refUpdates;
        private final Repository repository;
        private final ReferenceTransactionState state;
        private final GitHookOutputWriter writer;

        private Person user;

        public Builder(@Nonnull Repository repository, @Nonnull ReferenceTransactionState state,
                       @Nonnull GitHookOutputWriter writer) {
            this.repository = requireNonNull(repository, "repository");
            this.state = requireNonNull(state, "state");
            this.writer = requireNonNull(writer, "writer");

            refUpdates = ImmutableSet.builder();
        }

        @Nonnull
        public ReferenceTransaction build() {
            return new ReferenceTransaction(this);
        }

        @Nonnull
        public Builder user(Person value) {
            user = value;
            return this;
        }

        @Nonnull
        public Builder refUpdates(@Nullable RefUpdate value, @Nullable RefUpdate... moreValues) {
            addIf(Objects::nonNull, refUpdates, value, moreValues);
            return this;
        }

        @Nonnull
        public Builder refUpdates(@Nullable Iterable<RefUpdate> values) {
            addIf(Objects::nonNull, refUpdates, values);
            return this;
        }
    }
}
