/*
 * Copyright (C) 2023 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/reset.h"

#include <ctype.h>

#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#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/types.h"
#include "cuttlefish/host/commands/cvd/instances/instance_manager.h"
#include "cuttlefish/result/result.h"

namespace cuttlefish {
namespace {

constexpr char kResetSubcmd[] = "reset";

constexpr char kSummaryHelpText[] =
    "Used to stop devices, optionally clean up instance files, and shut down "
    "the deprecated cvd server process";

constexpr char kDetailedHelpText[] = R"(usage: cvd reset <args>

* Warning: Cvd reset is an experimental implementation. When you are in panic,
cvd reset is the last resort.

args:
  --help                 Prints this message.
    help

  --device-by-cvd-only   Terminates devices that a cvd server started
                         This excludes the devices launched by "launch_cvd"
                         or "cvd_internal_start" directly (default: false)

  --clean-runtime-dir    Cleans up the runtime directory for the devices
                         Yet to be implemented. For now, if true, only if
                         stop_cvd supports --clear_instance_dirs and the
                         device could be stopped by stop_cvd, the flag takes
                         effects. (default: true)

  --yes                  Resets without asking the user confirmation.
   -y

description:

  1. Gracefully stops all devices that the cvd client can reach.
  2. Forcefully stops all run_cvd processes and their subprocesses.
  3. Kill the cvd server itself if unresponsive.
  4. Reset the states of the involved instance lock files
     -- If cvd reset stops a device, it resets the corresponding lock file.
  5. Optionally, cleans up the runtime files of the stopped devices.)";

struct ParsedFlags {
  bool clean_runtime_dir = true;
  bool is_confirmed_by_flag = false;
};

static Result<ParsedFlags> ParseResetFlags(cvd_common::Args subcmd_args) {
  if (subcmd_args.size() > 2 && subcmd_args.at(2) == "help") {
    // Turn `cvd reset help` into `cvd reset --help`
    subcmd_args[2] = "--help";
  }

  ParsedFlags parsed_flags;

  Flag y_flag =
      GflagsCompatFlag("yes", parsed_flags.is_confirmed_by_flag).Alias("y");
  std::vector<Flag> flags{
      y_flag,
      GflagsCompatFlag("clean-runtime-dir", parsed_flags.clean_runtime_dir),
  };
  CF_EXPECT(
      ConsumeFlags(flags, subcmd_args, {.fail_on_unexpected_argument = true}));

  return parsed_flags;
}

static bool GetUserConfirm() {
  std::cout << "Are you sure to reset all the devices, runtime files, "
            << "and the cvd server if any [y/n]? ";
  std::string user_confirm;
  std::getline(std::cin, user_confirm);
  std::transform(user_confirm.begin(), user_confirm.end(), user_confirm.begin(),
                 ::tolower);
  return (user_confirm == "y" || user_confirm == "yes");
}
}  // namespace

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

Result<void> CvdResetCommandHandler::Handle(const CommandRequest& request) {
  std::vector<std::string> subcmd_args = request.SubcommandArguments();
  auto options = CF_EXPECT(ParseResetFlags(subcmd_args));

  // cvd reset. Give one more opportunity
  if (!options.is_confirmed_by_flag && !GetUserConfirm()) {
    std::cout << "For more details: " << "  cvd reset --help" << std::endl;
    return {};
  }

  if (options.clean_runtime_dir) {
    CF_EXPECT(instance_manager_.ResetAndClearInstanceDirs());
  } else {
    CF_EXPECT(instance_manager_.Reset());
  }
  return {};
}

cvd_common::Args CvdResetCommandHandler::CmdList() const {
  return {kResetSubcmd};
}

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

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

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

}  // namespace cuttlefish
