/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * 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.
 */

#include "cuttlefish/host/commands/cvd/cli/commands/stop.h"

#include <signal.h>  // IWYU pragma: keep
#include <stdlib.h>

#include <chrono>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "cuttlefish/flag_parser/flag.h"
#include "cuttlefish/flag_parser/gflags_compat.h"
#include "cuttlefish/host/commands/cvd/cli/command_request.h"
#include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h"
#include "cuttlefish/host/commands/cvd/cli/selector/selector.h"
#include "cuttlefish/host/commands/cvd/cli/types.h"
#include "cuttlefish/host/commands/cvd/cli/utils.h"
#include "cuttlefish/host/commands/cvd/instances/instance_manager.h"
#include "cuttlefish/host/commands/cvd/instances/local_instance.h"
#include "cuttlefish/host/libs/metrics/device_metrics_orchestration.h"
#include "cuttlefish/result/result.h"

namespace cuttlefish {
namespace {

constexpr char kSummaryHelpText[] = "Stop all instances in a group";

constexpr char kDetailedHelpText[] =
    R"""(
Stops all instances in an instance group

Usage:
cvd stop [--wait_for_launcher=SECONDS] [--clear_instance_dirs]

Stops a running cuttlefish instance group.

--wait_for_launcher=SECONDS    The number of seconds to wait for the launcher to
                     respond to the stop request. If SECONDS is 0 it will wait
                     indefinitely. Defaults to 5 seconds.

--clear_instance_dirs    If provided the instance directories will be deleted
                     after stopping.
)""";

struct StopFlags {
  size_t wait_for_launcher_secs = 5;
  bool clear_instance_dirs = false;
};
Result<StopFlags> ParseCommandFlags(cvd_common::Args& args) {
  StopFlags flag_values;
  std::vector<Flag> flags = {
      GflagsCompatFlag("wait_for_launcher", flag_values.wait_for_launcher_secs),
      GflagsCompatFlag("clear_instance_dirs", flag_values.clear_instance_dirs),
  };
  CF_EXPECT(ConsumeFlags(flags, args, {.fail_on_unexpected_argument = true}));
  return flag_values;
}

}  // namespace

CvdStopCommandHandler::CvdStopCommandHandler(InstanceManager& instance_manager)
    : instance_manager_(instance_manager) {}

Result<void> CvdStopCommandHandler::Handle(const CommandRequest& request) {
  std::vector<std::string> cmd_args = request.SubcommandArguments();

  if (!CF_EXPECT(instance_manager_.HasInstanceGroups())) {
    return CF_ERR(NoGroupMessage(request));
  }

  auto group = CF_EXPECT(selector::SelectGroup(instance_manager_, request));
  CF_EXPECT(group.HasActiveInstances(), "Selected group is not running");

  StopFlags flags = CF_EXPECT(ParseCommandFlags(cmd_args));
  std::optional<std::chrono::seconds> launcher_timeout;
  if (flags.wait_for_launcher_secs > 0) {
    launcher_timeout.emplace(flags.wait_for_launcher_secs);
  }

  std::vector<unsigned> instance_nums;
  if (request.Selectors().instance_names) {
    for (const auto& name : *request.Selectors().instance_names) {
      std::vector<LocalInstance> instances = group.FindByInstanceName(name);
      CF_EXPECTF(!instances.empty(), "Instance '{}' not found in group '{}'",
                 name, group.GroupName());
      for (const auto& inst : instances) {
        instance_nums.push_back(inst.Id());
      }
    }
  }

  Result<void> stop_outcome = instance_manager_.StopInstanceGroup(
      group, launcher_timeout,
      flags.clear_instance_dirs ? InstanceDirActionOnStop::Clear
                                : InstanceDirActionOnStop::Keep,
      instance_nums);

  GatherVmStopMetrics(group);

  CF_EXPECT(std::move(stop_outcome));
  return {};
}

cvd_common::Args CvdStopCommandHandler::CmdList() const {
  return {"stop", "stop_cvd"};
}

std::string CvdStopCommandHandler::SummaryHelp() const {
  return kSummaryHelpText;
}

Result<std::string> CvdStopCommandHandler::DetailedHelp(
    const CommandRequest& request) {
  return kDetailedHelpText;
}

std::unique_ptr<CvdCommandHandler> NewCvdStopCommandHandler(
    InstanceManager& instance_manager) {
  return std::unique_ptr<CvdCommandHandler>(
      new CvdStopCommandHandler(instance_manager));
}

}  // namespace cuttlefish
