/**
 * Copyright (C) 2016-2019 Expedia, Inc.
 *
 * Licensed 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 com.hotels.road.loadingbay;

import static java.util.Collections.emptyList;

import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

import com.hotels.road.agents.trafficcop.spi.Agent;
import com.hotels.road.loadingbay.model.Destinations;
import com.hotels.road.loadingbay.model.Hive;
import com.hotels.road.loadingbay.model.HiveRoad;
import com.hotels.road.loadingbay.model.HiveStatus;
import com.hotels.road.tollbooth.client.api.PatchOperation;

@Slf4j
@Component
public class LoadingBay implements Agent<HiveRoad> {

  static final OffsetDateTime EPOCH = OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
  private final HiveTableAction hiveTableAction;
  private final Function<HiveRoad, LanderMonitor> monitorFactory;
  private final Map<String, LanderMonitor> monitors;

  @Autowired
  public LoadingBay(HiveTableAction hiveTableAction, Function<HiveRoad, LanderMonitor> monitorFactory) {
    this.hiveTableAction = hiveTableAction;
    this.monitorFactory = monitorFactory;

    monitors = new HashMap<>();
  }

  @Override
  public List<PatchOperation> newModel(String key, HiveRoad newModel) {
    return inspectModel(key, newModel);
  }

  @Override
  public List<PatchOperation> updatedModel(String key, HiveRoad oldModel, HiveRoad newModel) {
    return Collections.emptyList();
  }

  @Override
  public void deletedModel(String key, HiveRoad oldModel) {
    log.warn("I don't know how to handle model deletion.");
  }

  @Override
  public List<PatchOperation> inspectModel(String key, HiveRoad model) {
    Optional<Hive> hive = Optional.ofNullable(model.getDestinations()).map(Destinations::getHive);
    if (hive.isPresent()) {
      OffsetDateTime lastRun = hive.map(Hive::getStatus).map(HiveStatus::getLastRun).orElse(EPOCH);
      log.debug("Inspecting road {}. Lander last ran at {}", model.getName(), lastRun);
      try {
        List<PatchOperation> patches = hiveTableAction.checkAndApply(model);
        LanderMonitor monitor = monitors.computeIfAbsent(model.getName(), n -> monitorFactory.apply(model));
        monitor.establishLandingFrequency(hive.map(Hive::getLandingInterval).orElse(Hive.DEFAULT_LANDING_INTERVAL));
        monitor.setEnabled(hive.get().isEnabled());
        return patches;
      } catch (NoActiveSchemaException e) {
        log.info("No schema defined on road '{}'", model.getName());
        return emptyList();
      } catch (Exception e) {
        log.error("Error while applying actions for road '{}'", model.getName(), e);
        return emptyList();
      }
    } else {
      log.info("Skipping road {} because it has no Hive destination", model.getName());
      if (monitors.containsKey(model.getName())) {
        try {
          monitors.remove(model.getName()).close();
        } catch (Exception e) {
          log.warn("Error shutting down DestinationMonitor for {}", model.getName(), e);
        }
      }
      return emptyList();
    }
  }
}