package com.atlassian.bitbucket.mesh.gprc;

import io.grpc.Context;
import io.grpc.Deadline;

import javax.annotation.Nonnull;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.TimeUnit;

import static java.util.Objects.requireNonNull;

public class GrpcSupport {

    @Nonnull
    public static Instant getDeadline(@Nonnull Duration timeout) {
        requireNonNull(timeout, "timeout");

        Instant result = Instant.now().plus(timeout.toMillis() > 0L ? timeout : Duration.ofDays(365L));
        Deadline deadline = Context.current().getDeadline();
        if (deadline == null) {
            return result;
        }
        Instant grpcDeadline = Instant.now().plusMillis(deadline.timeRemaining(TimeUnit.MILLISECONDS));
        return grpcDeadline.isBefore(result) ? grpcDeadline : result;
    }

    @Nonnull
    public static Duration getTimeout() {
        Deadline deadline = Context.current().getDeadline();
        if (deadline == null) {
            return Duration.ZERO;
        }

        //Apply a minimum of 1 ms because 0 or negative are interpreted by the system as "not specified" and
        //results in the default timeout being applied.
        return Duration.ofMillis(Math.max(1L, deadline.timeRemaining(TimeUnit.MILLISECONDS)));
    }

    /**
     * @param timeout the desired timeout to apply
     * @return {@code timeout} if no deadline is set, or the deadline is later than {@code timeout} from now; otherwise
     *         a timeout corresponding to the gRPC deadline.
     */
    @Nonnull
    public static Duration maybeReduceToDeadline(@Nonnull Duration timeout) {
        Deadline deadline = Context.current().getDeadline();
        if (deadline == null) {
            return timeout;
        }

        //Apply a minimum of 1 ms because 0 or negative are interpreted by the system as "not specified" and
        //results in the default timeout being applied.
        Duration grpcTimeout = Duration.ofMillis(Math.max(1L, deadline.timeRemaining(TimeUnit.MILLISECONDS)));
        return grpcTimeout.compareTo(timeout) < 0 ? grpcTimeout : timeout;
    }
}
