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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent;
import galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest;
import io.grpc.stub.ClientCallStreamObserver;
import io.grpc.stub.ClientResponseObserver;
import io.grpc.stub.StreamObserver;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot;
import mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage;
import mxaccess_gateway.v1.MxaccessGateway.MxEvent;
import mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest;
import mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest;
import mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest;
import org.junit.jupiter.api.Test;

/**
 * Lifecycle / cancellation contract tests applied uniformly to each of the
 * four subscription classes that extend {@link MxGatewayStreamSubscription}.
 *
 * <p>Locks in the Client.Java-036 refactor: every subclass must exhibit the
 * same behaviour for (a) cancel-before-beforeStart eagerly cancelling the
 * stream once it attaches, (b) cancel-after-beforeStart forwarding directly
 * to the stream, (c) the cancel message matching the subclass's documented
 * value, (d) {@code close()} delegating to {@code cancel()}, and (e) the
 * wrapping observer forwarding {@code onNext}/{@code onError}/{@code onCompleted}
 * to the caller's observer.
 */
final class MxGatewayStreamSubscriptionContractTests {

    @Test
    void cancelBeforeBeforeStartCancelsStreamWhenItAttaches_eventSubscription() {
        runCancelBeforeBeforeStartTest(new MxGatewayEventSubscription(), "client cancelled event stream");
    }

    @Test
    void cancelBeforeBeforeStartCancelsStreamWhenItAttaches_alarmFeedSubscription() {
        runCancelBeforeBeforeStartTest(
                new MxGatewayAlarmFeedSubscription(), "client cancelled alarm feed");
    }

    @Test
    void cancelBeforeBeforeStartCancelsStreamWhenItAttaches_activeAlarmsSubscription() {
        runCancelBeforeBeforeStartTest(
                new MxGatewayActiveAlarmsSubscription(), "client cancelled active-alarms query");
    }

    @Test
    void cancelBeforeBeforeStartCancelsStreamWhenItAttaches_deployEventSubscription() {
        runCancelBeforeBeforeStartTest(
                new DeployEventSubscription(), "client cancelled deploy event stream");
    }

    @Test
    void cancelAfterBeforeStartForwardsToStream_eventSubscription() {
        runCancelAfterBeforeStartTest(new MxGatewayEventSubscription(), "client cancelled event stream");
    }

    @Test
    void cancelAfterBeforeStartForwardsToStream_alarmFeedSubscription() {
        runCancelAfterBeforeStartTest(
                new MxGatewayAlarmFeedSubscription(), "client cancelled alarm feed");
    }

    @Test
    void cancelAfterBeforeStartForwardsToStream_activeAlarmsSubscription() {
        runCancelAfterBeforeStartTest(
                new MxGatewayActiveAlarmsSubscription(), "client cancelled active-alarms query");
    }

    @Test
    void cancelAfterBeforeStartForwardsToStream_deployEventSubscription() {
        runCancelAfterBeforeStartTest(
                new DeployEventSubscription(), "client cancelled deploy event stream");
    }

    @Test
    void closeDelegatesToCancel_eventSubscription() {
        runCloseDelegatesToCancelTest(new MxGatewayEventSubscription());
    }

    @Test
    void closeDelegatesToCancel_alarmFeedSubscription() {
        runCloseDelegatesToCancelTest(new MxGatewayAlarmFeedSubscription());
    }

    @Test
    void closeDelegatesToCancel_activeAlarmsSubscription() {
        runCloseDelegatesToCancelTest(new MxGatewayActiveAlarmsSubscription());
    }

    @Test
    void closeDelegatesToCancel_deployEventSubscription() {
        runCloseDelegatesToCancelTest(new DeployEventSubscription());
    }

    @Test
    void wrappedObserverForwardsOnNextOnErrorOnCompleted_eventSubscription() {
        MxEvent event = MxEvent.newBuilder().setWorkerSequence(7L).build();
        runForwardingTest(new MxGatewayEventSubscription(), event);
    }

    @Test
    void wrappedObserverForwardsOnNextOnErrorOnCompleted_alarmFeedSubscription() {
        AlarmFeedMessage msg = AlarmFeedMessage.newBuilder().setSnapshotComplete(true).build();
        runForwardingTest(new MxGatewayAlarmFeedSubscription(), msg);
    }

    @Test
    void wrappedObserverForwardsOnNextOnErrorOnCompleted_activeAlarmsSubscription() {
        ActiveAlarmSnapshot snap = ActiveAlarmSnapshot.newBuilder()
                .setAlarmFullReference("ref")
                .setSeverity(500)
                .build();
        runForwardingTest(new MxGatewayActiveAlarmsSubscription(), snap);
    }

    @Test
    void wrappedObserverForwardsOnNextOnErrorOnCompleted_deployEventSubscription() {
        DeployEvent ev = DeployEvent.newBuilder().setSequence(1L).build();
        runForwardingTest(new DeployEventSubscription(), ev);
    }

    private static <Req, Resp> void runCancelBeforeBeforeStartTest(
            MxGatewayStreamSubscription<Req, Resp> subscription, String expectedMessage) {
        ClientResponseObserver<Req, Resp> wrapped = subscription.wrap(new NoopObserver<>());
        RecordingClientCallStreamObserver<Req> stream = new RecordingClientCallStreamObserver<>();

        subscription.cancel();
        wrapped.beforeStart(stream);

        assertTrue(stream.cancelled, "stream should have been cancelled by beforeStart after prior cancel()");
        assertEquals(expectedMessage, stream.cancelMessage);
    }

    private static <Req, Resp> void runCancelAfterBeforeStartTest(
            MxGatewayStreamSubscription<Req, Resp> subscription, String expectedMessage) {
        ClientResponseObserver<Req, Resp> wrapped = subscription.wrap(new NoopObserver<>());
        RecordingClientCallStreamObserver<Req> stream = new RecordingClientCallStreamObserver<>();

        wrapped.beforeStart(stream);
        assertFalse(stream.cancelled, "stream should not be cancelled before cancel() is called");
        subscription.cancel();

        assertTrue(stream.cancelled, "stream should have been cancelled by direct cancel()");
        assertEquals(expectedMessage, stream.cancelMessage);
    }

    private static <Req, Resp> void runCloseDelegatesToCancelTest(
            MxGatewayStreamSubscription<Req, Resp> subscription) {
        ClientResponseObserver<Req, Resp> wrapped = subscription.wrap(new NoopObserver<>());
        RecordingClientCallStreamObserver<Req> stream = new RecordingClientCallStreamObserver<>();

        wrapped.beforeStart(stream);
        subscription.close();

        assertTrue(stream.cancelled, "close() should delegate to cancel()");
    }

    private static <Req, Resp> void runForwardingTest(
            MxGatewayStreamSubscription<Req, Resp> subscription, Resp value) {
        List<Resp> received = new ArrayList<>();
        AtomicReference<Throwable> errorRef = new AtomicReference<>();
        AtomicReference<Boolean> completed = new AtomicReference<>(false);

        StreamObserver<Resp> caller = new StreamObserver<>() {
            @Override
            public void onNext(Resp v) {
                received.add(v);
            }

            @Override
            public void onError(Throwable t) {
                errorRef.set(t);
            }

            @Override
            public void onCompleted() {
                completed.set(true);
            }
        };

        ClientResponseObserver<Req, Resp> wrapped = subscription.wrap(caller);
        RecordingClientCallStreamObserver<Req> stream = new RecordingClientCallStreamObserver<>();
        wrapped.beforeStart(stream);

        wrapped.onNext(value);
        IllegalStateException boom = new IllegalStateException("boom");
        wrapped.onError(boom);
        wrapped.onCompleted();

        assertEquals(1, received.size());
        assertEquals(value, received.get(0));
        assertNotNull(errorRef.get());
        assertEquals(boom, errorRef.get());
        assertTrue(completed.get());
    }

    private static final class NoopObserver<T> implements StreamObserver<T> {
        @Override
        public void onNext(T value) {
        }

        @Override
        public void onError(Throwable t) {
        }

        @Override
        public void onCompleted() {
        }
    }

    private static final class RecordingClientCallStreamObserver<T> extends ClientCallStreamObserver<T> {
        boolean cancelled;
        String cancelMessage;

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setOnReadyHandler(Runnable onReadyHandler) {
        }

        @Override
        public void disableAutoInboundFlowControl() {
        }

        @Override
        public void request(int count) {
        }

        @Override
        public void setMessageCompression(boolean enable) {
        }

        @Override
        public void cancel(String message, Throwable cause) {
            cancelled = true;
            cancelMessage = message;
        }

        @Override
        public void onNext(T value) {
        }

        @Override
        public void onError(Throwable error) {
        }

        @Override
        public void onCompleted() {
        }
    }

    // Compile-time guarantee that the parameter types still match the
    // generic bounds — catches a regression where a subclass changes its
    // request/response types out from under the shared base.
    @SuppressWarnings("unused")
    private static void typeBoundsCheck() {
        MxGatewayStreamSubscription<StreamEventsRequest, MxEvent> a = new MxGatewayEventSubscription();
        MxGatewayStreamSubscription<StreamAlarmsRequest, AlarmFeedMessage> b = new MxGatewayAlarmFeedSubscription();
        MxGatewayStreamSubscription<QueryActiveAlarmsRequest, ActiveAlarmSnapshot> c =
                new MxGatewayActiveAlarmsSubscription();
        MxGatewayStreamSubscription<WatchDeployEventsRequest, DeployEvent> d = new DeployEventSubscription();
    }
}
