/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.servicecomb.pack.alpha.server.fsm;

import static com.seanyinx.github.unit.scaffolding.Randomness.uniquify;

import io.grpc.ManagedChannel;
import io.grpc.stub.StreamObserver;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import org.apache.servicecomb.pack.alpha.core.OmegaCallback;
import org.apache.servicecomb.pack.contract.grpc.GrpcAck;
import org.apache.servicecomb.pack.contract.grpc.GrpcCompensateCommand;
import org.apache.servicecomb.pack.contract.grpc.GrpcServiceConfig;
import org.apache.servicecomb.pack.contract.grpc.TxEventServiceGrpc;
import org.apache.servicecomb.pack.contract.grpc.TxEventServiceGrpc.TxEventServiceBlockingStub;
import org.apache.servicecomb.pack.contract.grpc.TxEventServiceGrpc.TxEventServiceStub;

public class OmegaEventSender {
  GrpcServiceConfig serviceConfig;
  protected ManagedChannel clientChannel;
  private TxEventServiceStub asyncStub;
  private TxEventServiceBlockingStub blockingStub;
  private final Queue<GrpcCompensateCommand> receivedCommands = new ConcurrentLinkedQueue<>();
  private final CompensationStreamObserver compensateResponseObserver = new CompensationStreamObserver(
      this::onCompensation);
  private Map<String, Map<String, OmegaCallback>> omegaCallbacks;
  private OmegaEventSagaSimulator omegaEventSagaSimulator;

  private String serviceName;
  private String instanceId;

  public void configClient(ManagedChannel clientChannel){
    this.clientChannel = clientChannel;
    this.asyncStub = TxEventServiceGrpc.newStub(clientChannel);
    this.blockingStub = TxEventServiceGrpc.newBlockingStub(clientChannel);
  }

  public void shutdown(){
    this.clientChannel.shutdown();
    this.clientChannel = null;
  }

  public void onConnected(){
    serviceConfig = GrpcServiceConfig.newBuilder()
        .setServiceName(serviceName)
        .setInstanceId(instanceId)
        .build();
    asyncStub.onConnected(serviceConfig, compensateResponseObserver);
    omegaEventSagaSimulator = OmegaEventSagaSimulator.builder().serviceName(serviceName).instanceId(instanceId).build();

  }

  public void onDisconnected(){
    blockingStub.onDisconnected(serviceConfig);
  }

  public void setOmegaCallbacks(
      Map<String, Map<String, OmegaCallback>> omegaCallbacks) {
    this.omegaCallbacks = omegaCallbacks;
  }

  public Queue<GrpcCompensateCommand> getReceivedCommands() {
    return receivedCommands;
  }

  public TxEventServiceBlockingStub getBlockingStub() {
    return blockingStub;
  }

  public Map<String, Map<String, OmegaCallback>> getOmegaCallbacks(){
    return omegaCallbacks;
  }

  public void reset(){
    receivedCommands.clear();
  }

  public OmegaEventSagaSimulator getOmegaEventSagaSimulator(){
    return omegaEventSagaSimulator;
  }

  private class CompensationStreamObserver implements StreamObserver<GrpcCompensateCommand> {
    private final Consumer<GrpcCompensateCommand> consumer;
    private boolean completed = false;

    private CompensationStreamObserver() {
      this(command -> {});
    }

    private CompensationStreamObserver(Consumer<GrpcCompensateCommand> consumer) {
      this.consumer = consumer;
    }

    @Override
    public void onNext(GrpcCompensateCommand command) {
      // intercept received command
      consumer.accept(command);
      receivedCommands.add(command);
    }

    @Override
    public void onError(Throwable t) {
    }

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

    boolean isCompleted() {
      return completed;
    }
  }

  private GrpcAck onCompensation(GrpcCompensateCommand command) {
    return blockingStub.onTxEvent(omegaEventSagaSimulator.txCompensatedEvent(command.getGlobalTxId(),command.getLocalTxId(),command.getParentTxId()));
  }

  public static OmegaEventSender.Builder builder() {
    return new OmegaEventSender.Builder();
  }

  public static final class Builder {

    private String serviceName = uniquify("omega-serviceName");
    private String instanceId = uniquify("omega-instanceId");

    public OmegaEventSender.Builder serviceName(String serviceName) {
      this.serviceName = serviceName;
      return this;
    }

    public OmegaEventSender.Builder instanceId(String instanceId) {
      this.instanceId = instanceId;
      return this;
    }

    public OmegaEventSender build() {
      OmegaEventSender omegaEventSender = new OmegaEventSender();
      omegaEventSender.instanceId = this.instanceId;
      omegaEventSender.serviceName = this.serviceName;
      return omegaEventSender;
    }
  }
}
