// Copyright 2014 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/safe_browsing/incident_reporting/preference_validation_delegate.h"

#include <string>
#include <utility>
#include <vector>

#include "base/json/json_writer.h"
#include "chrome/browser/safe_browsing/incident_reporting/incident_receiver.h"
#include "chrome/browser/safe_browsing/incident_reporting/tracked_preference_incident.h"
#include "components/safe_browsing/core/common/proto/csd.pb.h"
#include "services/preferences/public/mojom/tracked_preference_validation_delegate.mojom.h"

namespace safe_browsing {

namespace {

typedef ClientIncidentReport_IncidentData_TrackedPreferenceIncident TPIncident;
typedef ClientIncidentReport_IncidentData_TrackedPreferenceIncident_ValueState
    TPIncident_ValueState;

using ValueState =
    prefs::mojom::TrackedPreferenceValidationDelegate::ValueState;

// Maps a primary PrefHashStoreTransaction::ValueState and an external
// validation state to a TrackedPreferenceIncident::ValueState.
TPIncident_ValueState MapValueState(
    ValueState value_state,
    ValueState external_validation_value_state) {
  switch (value_state) {
    case ValueState::CLEARED:
      return TPIncident::CLEARED;
    case ValueState::CHANGED:
      return TPIncident::CHANGED;
    case ValueState::UNTRUSTED_UNKNOWN_VALUE:
      return TPIncident::UNTRUSTED_UNKNOWN_VALUE;
    default:
      switch (external_validation_value_state) {
        case ValueState::CLEARED:
          return TPIncident::BYPASS_CLEARED;
        case ValueState::CHANGED:
          return TPIncident::BYPASS_CHANGED;
        default:
          return TPIncident::UNKNOWN;
      }
  }
}

}  // namespace

PreferenceValidationDelegate::PreferenceValidationDelegate(
    Profile* profile,
    std::unique_ptr<IncidentReceiver> incident_receiver)
    : profile_(profile), incident_receiver_(std::move(incident_receiver)) {
  profile_observation_.Observe(profile);
}

PreferenceValidationDelegate::~PreferenceValidationDelegate() = default;

void PreferenceValidationDelegate::OnAtomicPreferenceValidation(
    const std::string& pref_path,
    std::optional<base::Value> value,
    ValueState value_state,
    ValueState external_validation_value_state,
    bool is_personal) {
  // profile_ can be null if it is already destroyed during shutdown.
  if (!profile_) {
    return;
  }
  TPIncident_ValueState proto_value_state =
      MapValueState(value_state, external_validation_value_state);
  if (proto_value_state != TPIncident::UNKNOWN) {
    std::unique_ptr<TPIncident> incident(
        new ClientIncidentReport_IncidentData_TrackedPreferenceIncident());
    incident->set_path(pref_path);
    if (!value) {
      incident->clear_atomic_value();
    } else if (value->is_string()) {
      *incident->mutable_atomic_value() = value->GetString();
    } else if (!base::JSONWriter::Write(*value,
                                        incident->mutable_atomic_value())) {
      incident->clear_atomic_value();
    }
    incident->set_value_state(proto_value_state);
    incident_receiver_->AddIncidentForProfile(
        profile_, std::make_unique<TrackedPreferenceIncident>(
                      std::move(incident), is_personal));
  }
}

void PreferenceValidationDelegate::OnSplitPreferenceValidation(
    const std::string& pref_path,
    const std::vector<std::string>& invalid_keys,
    const std::vector<std::string>& external_validation_invalid_keys,
    ValueState value_state,
    ValueState external_validation_value_state,
    bool is_personal) {
  TPIncident_ValueState proto_value_state =
      MapValueState(value_state, external_validation_value_state);
  if (proto_value_state != TPIncident::UNKNOWN) {
    std::unique_ptr<ClientIncidentReport_IncidentData_TrackedPreferenceIncident>
        incident(
            new ClientIncidentReport_IncidentData_TrackedPreferenceIncident());
    incident->set_path(pref_path);
    if (proto_value_state == TPIncident::BYPASS_CLEARED ||
        proto_value_state == TPIncident::BYPASS_CHANGED) {
      for (auto scan(external_validation_invalid_keys.begin());
           scan != external_validation_invalid_keys.end(); ++scan) {
        incident->add_split_key(*scan);
      }
    } else {
      for (auto scan(invalid_keys.begin()); scan != invalid_keys.end();
           ++scan) {
        incident->add_split_key(*scan);
      }
    }
    incident->set_value_state(proto_value_state);
    incident_receiver_->AddIncidentForProfile(
        profile_, std::make_unique<TrackedPreferenceIncident>(
                      std::move(incident), is_personal));
  }
}

void PreferenceValidationDelegate::OnProfileWillBeDestroyed(Profile* profile) {
  profile_ = nullptr;
  profile_observation_.Reset();
}

}  // namespace safe_browsing
