// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/support_tool/signin_data_collector.h"

#include "base/containers/fixed_flat_map.h"
#include "base/containers/flat_set.h"
#include "base/files/file_util.h"
#include "base/json/json_writer.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/about_signin_internals_factory.h"
#include "components/feedback/redaction_tool/pii_types.h"
#include "components/signin/core/browser/about_signin_internals.h"

namespace {

PIIMap DetectPII(
    std::string signin_status,
    scoped_refptr<redaction::RedactionToolContainer> redaction_tool_container) {
  redaction::RedactionTool* redaction_tool = redaction_tool_container->Get();
  return redaction_tool->Detect(std::move(signin_status));
}

std::string RedactPII(
    std::set<redaction::PIIType> pii_types_to_keep,
    base::FilePath target_directory,
    scoped_refptr<redaction::RedactionToolContainer> redaction_tool_container,
    std::string signin_json) {
  redaction::RedactionTool* redaction_tool = redaction_tool_container->Get();
  auto result = redaction_tool->RedactAndKeepSelected(std::move(signin_json),
                                                      pii_types_to_keep);
  return result;
}

// Writes `signin_json` into a file named "signin.json" in `target_directory`.
bool WriteFile(std::string json, base::FilePath target_directory) {
  base::FilePath target_file =
      target_directory.Append(FILE_PATH_LITERAL("signin.json"));
  return base::WriteFile(target_file, json);
}

}  // namespace

SigninDataCollector::SigninDataCollector(Profile* profile)
    : profile_(profile) {}

SigninDataCollector::~SigninDataCollector() = default;

std::string SigninDataCollector::GetName() const {
  return "Signin Data Collector";
}

std::string SigninDataCollector::GetDescription() const {
  return "Collects signin and token information abut the currently signed in "
         "user, and "
         "writes it to \"signin.json\" file.";
}

const PIIMap& SigninDataCollector::GetDetectedPII() {
  return pii_map_;
}

void SigninDataCollector::CollectDataAndDetectPII(
    DataCollectorDoneCallback on_data_collected_callback,
    scoped_refptr<base::SequencedTaskRunner> task_runner_for_redaction_tool,
    scoped_refptr<redaction::RedactionToolContainer> redaction_tool_container) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // `profile_` can't be null because `SigninDataCollector` will be created
  // after profile is created. Check `SupportToolHandler` and
  // chrome/browser/support_tool/support_tool_util.h for more details.
  CHECK(profile_);

  if (profile_->IsIncognitoProfile()) {
    SupportToolError error = {
        SupportToolErrorCode::kDataCollectorError,
        "SigninDataCollector can't work without profile or in incognito mode."};
    std::move(on_data_collected_callback).Run(error);
    return;
  }

  AboutSigninInternals* about_signin_internals =
      AboutSigninInternalsFactory::GetForProfile(profile_);
  // See AboutSigninInternals::SigninStatus::ToValue.
  base::DictValue status = about_signin_internals->GetSigninStatus();
  base::JSONWriter::WriteWithOptions(
      status, base::JSONWriter::OPTIONS_PRETTY_PRINT, &signin_status_);

  task_runner_for_redaction_tool->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&DetectPII, signin_status_, redaction_tool_container),
      base::BindOnce(&SigninDataCollector::OnPIIDetected,
                     weak_ptr_factory_.GetWeakPtr(),
                     std::move(on_data_collected_callback)));
}

void SigninDataCollector::OnPIIDetected(
    DataCollectorDoneCallback on_data_collected_callback,
    PIIMap piiMap) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  pii_map_ = piiMap;
  std::move(on_data_collected_callback).Run(/*error=*/std::nullopt);
}

void SigninDataCollector::ExportCollectedDataWithPII(
    std::set<redaction::PIIType> pii_types_to_keep,
    base::FilePath target_directory,
    scoped_refptr<base::SequencedTaskRunner> task_runner_for_redaction_tool,
    scoped_refptr<redaction::RedactionToolContainer> redaction_tool_container,
    DataCollectorDoneCallback on_exported_callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (signin_status_.empty()) {
    SupportToolError error = {
        SupportToolErrorCode::kDataCollectorError,
        "SigninDataCollector: Status is empty. Can't export empty status."};
    std::move(on_exported_callback).Run(error);
    return;
  }

  task_runner_for_redaction_tool->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&RedactPII, pii_types_to_keep, target_directory,
                     redaction_tool_container, std::move(signin_status_)),
      base::BindOnce(&SigninDataCollector::OnPIIRedacted,
                     weak_ptr_factory_.GetWeakPtr(), target_directory,
                     std::move(on_exported_callback)));
}

void SigninDataCollector::OnPIIRedacted(
    base::FilePath target_directory,
    DataCollectorDoneCallback on_exported_callback,
    std::string json) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock()},
      base::BindOnce(&WriteFile, json, target_directory),
      base::BindOnce(&SigninDataCollector::OnFileWritten,
                     weak_ptr_factory_.GetWeakPtr(),
                     std::move(on_exported_callback)));
}

void SigninDataCollector::OnFileWritten(
    DataCollectorDoneCallback on_exported_callback,
    bool success) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!success) {
    SupportToolError error = {SupportToolErrorCode::kDataCollectorError,
                              "SigninDataCollector failed on data export."};
    std::move(on_exported_callback).Run(error);
    return;
  }
  std::move(on_exported_callback).Run(/*error=*/std::nullopt);
}
