package com.zb.mom.ww.mxgateway.client;

import galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent;
import galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.stub.ClientCallStreamObserver;
import io.grpc.stub.ClientResponseObserver;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Iterator-style adaptor over the {@code WatchDeployEvents} server-streaming
 * RPC. Mirrors {@link MxEventStream}: events arrive on a background gRPC thread
 * and are buffered in a bounded blocking queue; the iterator drains them.
 * Closing the stream cancels the underlying gRPC call.
 */
public final class DeployEventStream implements Iterator<DeployEvent>, AutoCloseable {
    private static final Object END = new Object();

    private final BlockingQueue<Object> queue;
    private final AtomicBoolean closed = new AtomicBoolean();
    private volatile ClientCallStreamObserver<WatchDeployEventsRequest> requestStream;
    private Object next;

    DeployEventStream(int capacity) {
        queue = new ArrayBlockingQueue<>(capacity);
    }

    ClientResponseObserver<WatchDeployEventsRequest, DeployEvent> observer() {
        return new ClientResponseObserver<>() {
            @Override
            public void beforeStart(ClientCallStreamObserver<WatchDeployEventsRequest> requestStream) {
                DeployEventStream.this.requestStream = requestStream;
                if (closed.get()) {
                    requestStream.cancel("client cancelled deploy event stream", null);
                }
            }

            @Override
            public void onNext(DeployEvent value) {
                offer(value);
            }

            @Override
            public void onError(Throwable error) {
                if (Status.fromThrowable(error).getCode() == Status.Code.CANCELLED && closed.get()) {
                    offer(END);
                    return;
                }
                offer(error);
            }

            @Override
            public void onCompleted() {
                offer(END);
            }
        };
    }

    @Override
    public boolean hasNext() {
        if (next == END) {
            return false;
        }
        if (next == null) {
            next = take();
        }
        if (next instanceof RuntimeException runtimeException) {
            next = END;
            throw runtimeException;
        }
        if (next instanceof Throwable throwable) {
            next = END;
            throw new MxGatewayException(
                    "galaxy watch deploy events failed: " + throwable.getMessage(), throwable);
        }
        return next != END;
    }

    @Override
    public DeployEvent next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        Object value = next;
        next = null;
        return (DeployEvent) value;
    }

    @Override
    public void close() {
        closed.set(true);
        ClientCallStreamObserver<WatchDeployEventsRequest> stream = requestStream;
        if (stream != null) {
            stream.cancel("client cancelled deploy event stream", null);
        }
        offer(END);
    }

    private Object take() {
        while (true) {
            try {
                return queue.take();
            } catch (InterruptedException error) {
                Thread.currentThread().interrupt();
                return new StatusRuntimeException(
                        Status.CANCELLED.withDescription("interrupted while reading deploy events"));
            }
        }
    }

    private void offer(Object value) {
        Objects.requireNonNull(value, "value");
        if (value == END) {
            if (!queue.offer(value)) {
                queue.clear();
                queue.offer(value);
            }
            return;
        }
        if (!queue.offer(value)) {
            ClientCallStreamObserver<WatchDeployEventsRequest> stream = requestStream;
            if (stream != null) {
                stream.cancel("client deploy event stream queue overflowed", null);
            }
            queue.clear();
            queue.offer(new MxGatewayException("galaxy watch deploy events queue overflowed"));
            queue.offer(END);
        }
    }
}
