package com.atlassian.stash.internal.cluster;

import com.atlassian.stash.internal.maintenance.MaintenanceTaskState;
import com.atlassian.stash.internal.maintenance.MaintenanceTaskStatus;
import com.atlassian.stash.internal.maintenance.MaintenanceTaskStatusSupplier;
import com.hazelcast.nio.ObjectDataInput;
import com.hazelcast.nio.ObjectDataOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;
import java.io.IOException;

import static com.atlassian.stash.internal.cluster.ClusterJoinCheckAction.PASSIVATE_OTHER_NODE;
import static com.atlassian.stash.internal.cluster.ClusterJoinCheckAction.PASSIVATE_THIS_NODE;

/**
 * A {@link ClusterJoinRequirement} which requires that the server node is not currently performing a backup or
 * database migration
 */
@Component
public class MaintenanceClusterJoinCheck implements ClusterJoinCheck {

    private static final Logger log = LoggerFactory.getLogger(MaintenanceClusterJoinCheck.class);
    private static final String MAINTENANCE_MESSAGE = "The node is currently performing a maintenance task";

    private final MaintenanceTaskStatusSupplier statusHolder;

    /**
     * Note, the {@code localLatestTaskHolder} must be used instead of the clustered version to avoid a cyclic
     * dependency between Hazelcast and the {@link MaintenanceTaskStatusSupplier} implementation.
     */
    @Autowired
    public MaintenanceClusterJoinCheck(
            @Qualifier("localMaintenanceTaskStatusSupplier") MaintenanceTaskStatusSupplier statusHolder) {
        this.statusHolder = statusHolder;
    }

    @Nonnull
    @Override
    public String getName() {
        return "migrationStatus";
    }

    @Nonnull
    @Override
    public ClusterJoinCheckResult accept(@Nonnull ClusterJoinRequest request) throws IOException {
        return checkMaintenanceTasks(request);
    }

    @Nonnull
    @Override
    public ClusterJoinCheckResult connect(@Nonnull ClusterJoinRequest request) throws IOException {
        return checkMaintenanceTasks(request);
    }

    @Override
    public int getOrder() {
        return 10;
    }

    @Nonnull
    @Override
    public ClusterJoinCheckResult onUnknown(@Nonnull ClusterJoinRequest request) {
        return ClusterJoinCheckResult.OK;
    }

    private ClusterJoinCheckResult checkMaintenanceTasks(ClusterJoinRequest request) throws IOException {
        MaintenanceTaskStatus latestTask = statusHolder.get();
        String otherId = null;
        MaintenanceTaskState otherState = null;

        ObjectDataInput in = request.in();
        ObjectDataOutput out = request.out();

        if (latestTask == null) {
            out.writeBoolean(false);
        } else {
            out.writeBoolean(true);
            out.writeUTF(latestTask.getId());
            out.writeInt(latestTask.getState().getId());
        }

        // read what maintenance task the other node is performing, if any
        if (in.readBoolean()) {
            otherId = in.readUTF();
            otherState = MaintenanceTaskState.fromId(in.readInt());
        }

        if (latestTask == null) {
            if (otherState == MaintenanceTaskState.RUNNING) {
                // this node is not performing maintenance, but the other node is
                return ClusterJoinCheckResult.passivate(PASSIVATE_THIS_NODE, MAINTENANCE_MESSAGE);
            }
        } else if (latestTask.getId().equals(otherId)) {
            // both nodes are already performing / have performed the same maintenance task. We're most likely recovering
            // from a network partition. Let the nodes rejoin.
            if (latestTask.getState() != otherState) {
                log.info("Local node reports {} maintenance task in state {} while the remote node reports state {}. " +
                                "Recovering from a network partition?",
                        latestTask.getType(), latestTask.getState(), otherState);
            }
            return ClusterJoinCheckResult.OK;
        } else if (latestTask.getState() == MaintenanceTaskState.RUNNING) {
            // this node is performing maintenance, but the other node is not or is performing a different maintenance
            // task (task IDs don't match up)
            return ClusterJoinCheckResult.passivate(PASSIVATE_OTHER_NODE, MAINTENANCE_MESSAGE);
        } else if (otherState == MaintenanceTaskState.RUNNING) {
            // this node is not running maintenance, but the other is
            return ClusterJoinCheckResult.passivate(PASSIVATE_THIS_NODE, MAINTENANCE_MESSAGE);
        }

        // neither node is currently performing maintenance
        return ClusterJoinCheckResult.OK;
    }
}
