/*
 * Copyright (c) 2012-2018 Red Hat, Inc.
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Red Hat, Inc. - initial API and implementation
 */
package org.eclipse.che.workspace.infrastructure.openshift;

import com.google.inject.assistedinject.Assisted;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.openshift.api.model.Route;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.che.api.workspace.server.URLRewriter.NoOpURLRewriter;
import org.eclipse.che.api.workspace.server.hc.ServersCheckerFactory;
import org.eclipse.che.api.workspace.server.hc.probe.ProbeScheduler;
import org.eclipse.che.api.workspace.server.hc.probe.WorkspaceProbesFactory;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.provision.InternalEnvironmentProvisioner;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.commons.annotation.Traced;
import org.eclipse.che.commons.tracing.OptionalTracer;
import org.eclipse.che.commons.tracing.TracingTags;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInternalRuntime;
import org.eclipse.che.workspace.infrastructure.kubernetes.RuntimeHangingDetector;
import org.eclipse.che.workspace.infrastructure.kubernetes.StartSynchronizerFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.bootstrapper.KubernetesBootstrapperFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.cache.KubernetesMachineCache;
import org.eclipse.che.workspace.infrastructure.kubernetes.cache.KubernetesRuntimeStateCache;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.WorkspaceVolumesStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.UnrecoverablePodEventListener;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.UnrecoverablePodEventListenerFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.SidecarToolingProvisioner;
import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment;
import org.eclipse.che.workspace.infrastructure.openshift.project.OpenShiftProject;
import org.eclipse.che.workspace.infrastructure.openshift.server.OpenShiftServerResolver;

/**
 * @author Sergii Leshchenko
 * @author Anton Korneta
 */
public class OpenShiftInternalRuntime extends KubernetesInternalRuntime<OpenShiftEnvironment> {

  private final OpenShiftProject project;
  private final UnrecoverablePodEventListenerFactory unrecoverablePodEventListenerFactory;

  @Inject
  public OpenShiftInternalRuntime(
      @Named("che.infra.kubernetes.workspace_start_timeout_min") int workspaceStartTimeout,
      @Named("che.infra.kubernetes.ingress_start_timeout_min") int ingressStartTimeout,
      NoOpURLRewriter urlRewriter,
      UnrecoverablePodEventListenerFactory unrecoverablePodEventListenerFactory,
      KubernetesBootstrapperFactory bootstrapperFactory,
      ServersCheckerFactory serverCheckerFactory,
      WorkspaceVolumesStrategy volumesStrategy,
      ProbeScheduler probeScheduler,
      WorkspaceProbesFactory probesFactory,
      RuntimeEventsPublisher eventPublisher,
      KubernetesSharedPool sharedPool,
      KubernetesRuntimeStateCache runtimesStatusesCache,
      KubernetesMachineCache machinesCache,
      StartSynchronizerFactory startSynchronizerFactory,
      Set<InternalEnvironmentProvisioner> internalEnvironmentProvisioners,
      OpenShiftEnvironmentProvisioner kubernetesEnvironmentProvisioner,
      SidecarToolingProvisioner<OpenShiftEnvironment> toolingProvisioner,
      RuntimeHangingDetector runtimeHangingDetector,
      @Nullable OptionalTracer tracer,
      @Assisted OpenShiftRuntimeContext context,
      @Assisted OpenShiftProject project) {
    super(
        workspaceStartTimeout,
        ingressStartTimeout,
        urlRewriter,
        unrecoverablePodEventListenerFactory,
        bootstrapperFactory,
        serverCheckerFactory,
        volumesStrategy,
        probeScheduler,
        probesFactory,
        eventPublisher,
        sharedPool,
        runtimesStatusesCache,
        machinesCache,
        startSynchronizerFactory,
        internalEnvironmentProvisioners,
        kubernetesEnvironmentProvisioner,
        toolingProvisioner,
        runtimeHangingDetector,
        tracer,
        context,
        project);
    this.project = project;
    this.unrecoverablePodEventListenerFactory = unrecoverablePodEventListenerFactory;
  }

  @Override
  protected void startMachines() throws InfrastructureException {
    OpenShiftEnvironment osEnv = getContext().getEnvironment();
    String workspaceId = getContext().getIdentity().getWorkspaceId();

    createSecrets(osEnv, workspaceId);
    createConfigMaps(osEnv, workspaceId);
    List<Service> createdServices = createServices(osEnv, workspaceId);
    List<Route> createdRoutes = createRoutes(osEnv, workspaceId);

    // TODO https://github.com/eclipse/che/issues/7653
    // project.pods().watch(new AbnormalStopHandler());

    project.deployments().watchEvents(new MachineLogsPublisher());
    if (unrecoverablePodEventListenerFactory.isConfigured()) {
      Map<String, Pod> pods = getContext().getEnvironment().getPods();
      UnrecoverablePodEventListener handler =
          unrecoverablePodEventListenerFactory.create(
              pods.keySet(), this::handleUnrecoverableEvent);
      project.deployments().watchEvents(handler);
    }

    doStartMachine(new OpenShiftServerResolver(createdServices, createdRoutes));
  }

  @Traced
  @SuppressWarnings("WeakerAccess") // package-private so that interception is possible
  void createSecrets(OpenShiftEnvironment env, String workspaceId) throws InfrastructureException {
    TracingTags.WORKSPACE_ID.set(workspaceId);
    for (Secret secret : env.getSecrets().values()) {
      project.secrets().create(secret);
    }
  }

  @Traced
  @SuppressWarnings("WeakerAccess") // package-private so that interception is possible
  void createConfigMaps(OpenShiftEnvironment env, String workspaceId)
      throws InfrastructureException {
    TracingTags.WORKSPACE_ID.set(workspaceId);
    for (ConfigMap configMap : env.getConfigMaps().values()) {
      project.configMaps().create(configMap);
    }
  }

  @Traced
  @SuppressWarnings("WeakerAccess") // package-private so that interception is possible
  List<Service> createServices(OpenShiftEnvironment env, String workspaceId)
      throws InfrastructureException {
    TracingTags.WORKSPACE_ID.set(workspaceId);
    Collection<Service> servicesToCreate = env.getServices().values();
    List<Service> createdServices = new ArrayList<>(servicesToCreate.size());
    for (Service service : servicesToCreate) {
      createdServices.add(project.services().create(service));
    }

    return createdServices;
  }

  @Traced
  @SuppressWarnings("WeakerAccess") // package-private so that interception is possible
  List<Route> createRoutes(OpenShiftEnvironment env, String workspaceId)
      throws InfrastructureException {
    TracingTags.WORKSPACE_ID.set(workspaceId);
    Collection<Route> routesToCreate = env.getRoutes().values();
    List<Route> createdRoutes = new ArrayList<>(routesToCreate.size());
    for (Route route : routesToCreate) {
      createdRoutes.add(project.routes().create(route));
    }

    return createdRoutes;
  }
}
