// 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 "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
#include "chrome/browser/optimization_guide/model_execution/optimization_guide_global_state.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/metrics_services_manager/metrics_services_manager.h"
#include "components/optimization_guide/core/feature_registry/feature_registration.h"
#include "components/optimization_guide/core/model_execution/feature_keys.h"
#include "components/optimization_guide/core/model_execution/manifest_broker/test/manifest_builder.h"
#include "components/optimization_guide/core/model_execution/model_execution_features.h"
#include "components/optimization_guide/core/model_execution/model_execution_manager.h"
#include "components/optimization_guide/core/model_execution/model_execution_prefs.h"
#include "components/optimization_guide/core/model_execution/optimization_guide_model_execution_error.h"
#include "components/optimization_guide/core/model_execution/performance_class.h"
#include "components/optimization_guide/core/model_execution/test/fake_model_assets.h"
#include "components/optimization_guide/core/model_execution/test/feature_config_builder.h"
#include "components/optimization_guide/core/model_quality/model_execution_logging_wrappers.h"
#include "components/optimization_guide/core/model_quality/model_quality_log_entry.h"
#include "components/optimization_guide/core/model_quality/model_quality_logs_uploader_service.h"
#include "components/optimization_guide/core/optimization_guide_features.h"
#include "components/optimization_guide/core/optimization_guide_logger.h"
#include "components/optimization_guide/core/optimization_guide_switches.h"
#include "components/optimization_guide/core/optimization_guide_util.h"
#include "components/optimization_guide/proto/manifest.pb.h"
#include "components/optimization_guide/proto/model_quality_service.pb.h"
#include "components/optimization_guide/public/mojom/model_broker.mojom-shared.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/account_capabilities_test_mutator.h"
#include "components/variations/variations_switches.h"
#include "content/public/test/browser_test.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/tflite/buildflags.h"

namespace optimization_guide {

namespace {

enum class ModelExecutionRemoteResponseType {
  kSuccessful = 0,
  kUnsuccessful = 1,
  kMalformed = 2,
  kErrorFiltered = 3,
  kUnsupportedLanguage = 4,
};

proto::ExecuteResponse BuildComposeResponse(const std::string& output) {
  proto::ComposeResponse compose_response;
  compose_response.set_output(output);
  proto::ExecuteResponse execute_response;
  proto::Any* any_metadata = execute_response.mutable_response_metadata();
  any_metadata->set_type_url(
      base::StrCat({"type.googleapis.com/", compose_response.GetTypeName()}));
  compose_response.SerializeToString(any_metadata->mutable_value());
  auto response_data = ParsedAnyMetadata<proto::ComposeResponse>(*any_metadata);
  EXPECT_TRUE(response_data);
  return execute_response;
}

proto::ExecuteResponse BuildTestErrorExecuteResponse(
    const proto::ErrorState& state) {
  proto::ExecuteResponse execute_response;
  execute_response.mutable_error_response()->set_error_state(state);
  return execute_response;
}

class ScopedSetMetricsConsent {
 public:
  // Enables or disables metrics consent based off of |consent|.
  explicit ScopedSetMetricsConsent(bool consent) : consent_(consent) {
    ChromeMetricsServiceAccessor::SetMetricsAndCrashReportingForTesting(
        &consent_);
  }

  ScopedSetMetricsConsent(const ScopedSetMetricsConsent&) = delete;
  ScopedSetMetricsConsent& operator=(const ScopedSetMetricsConsent&) = delete;

  ~ScopedSetMetricsConsent() {
    ChromeMetricsServiceAccessor::SetMetricsAndCrashReportingForTesting(
        nullptr);
  }

 private:
  const bool consent_;
};

}  // namespace

class ModelExecutionBrowserTestBase : public InProcessBrowserTest {
 public:
  ModelExecutionBrowserTestBase() = default;
  ~ModelExecutionBrowserTestBase() override = default;

  ModelExecutionBrowserTestBase(const ModelExecutionBrowserTestBase&) = delete;
  ModelExecutionBrowserTestBase& operator=(
      const ModelExecutionBrowserTestBase&) = delete;

  void SetUp() override {
    InitializeFeatureList();
    model_execution_server_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);
    net::EmbeddedTestServer::ServerCertificateConfig cert_config;
    cert_config.dns_names = {
        GetModelExecutionServiceURL().GetHost(),
    };
    model_execution_server_->SetSSLConfig(cert_config);
    model_execution_server_->RegisterRequestHandler(base::BindRepeating(
        &ModelExecutionBrowserTestBase::HandleGetModelExecutionRequest,
        base::Unretained(this)));

    // Start ModelQualityLogsUploaderService to upload the model quality logs on
    // receiving it from model execution.
    model_quality_logs_server_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);
    cert_config.dns_names = {
        GetModelQualityLogsUploaderServiceURL().GetHost(),
    };
    model_quality_logs_server_->SetSSLConfig(cert_config);
    model_quality_logs_server_->RegisterRequestHandler(base::BindRepeating(
        &ModelExecutionBrowserTestBase::HandleGetModelQualityLogsUploadRequest,
        base::Unretained(this)));
    num_logs_requests_ = 0;

    ASSERT_TRUE(model_execution_server_->Start());
    ASSERT_TRUE(model_quality_logs_server_->Start());
    InProcessBrowserTest::SetUp();
  }

  void SetUpCommandLine(base::CommandLine* cmd) override {
    cmd->AppendSwitchASCII(
        kOptimizationGuideServiceModelExecutionURLSwitch,
        model_execution_server_
            ->GetURL(GetModelExecutionServiceURL().GetHost(), "/")
            .spec());
    cmd->AppendSwitchASCII(
        switches::kModelQualityServiceURL,
        model_quality_logs_server_
            ->GetURL(GetModelQualityLogsUploaderServiceURL().GetHost(), "/")
            .spec());
    // Add a dummy variation ID so that the X-Client-Data header is appended to
    // eligible requests to select Google servers.
    cmd->AppendSwitchASCII(variations::switches::kForceVariationIds, "113355");
  }

  void SetUpBrowserContextKeyedServices(
      content::BrowserContext* context) override {
    InProcessBrowserTest::SetUpBrowserContextKeyedServices(context);
    IdentityTestEnvironmentProfileAdaptor::
        SetIdentityTestEnvironmentFactoriesOnBrowserContext(context);
  }

  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();
    identity_test_env_adaptor_ =
        std::make_unique<IdentityTestEnvironmentProfileAdaptor>(
            browser()->GetProfile());
    host_resolver()->AddRule("*", "127.0.0.1");
  }

  void TearDownOnMainThread() override {
    EXPECT_TRUE(model_execution_server_->ShutdownAndWaitUntilComplete());
    EXPECT_TRUE(model_quality_logs_server_->ShutdownAndWaitUntilComplete());
    InProcessBrowserTest::TearDownOnMainThread();
  }

  void EnableSignin() {
    auto account_info =
        identity_test_env_adaptor_->identity_test_env()
            ->MakePrimaryAccountAvailable("user@gmail.com",
                                          signin::ConsentLevel::kSignin);
    AccountCapabilitiesTestMutator mutator(&account_info);
    mutator.set_can_use_model_execution_features(true);
    identity_test_env_adaptor_->identity_test_env()
        ->UpdateAccountInfoForAccount(account_info);
    identity_test_env_adaptor_->identity_test_env()
        ->SetAutomaticIssueOfAccessTokens(true);
  }

  bool IsSignedIn() const {
    return identity_test_env_adaptor_->identity_test_env()
        ->identity_manager()
        ->HasPrimaryAccount(signin::ConsentLevel::kSignin);
  }

  OptimizationGuideKeyedService* GetOptimizationGuideKeyedService(
      Profile* profile = nullptr) {
    if (!profile) {
      profile = browser()->GetProfile();
    }
    return OptimizationGuideKeyedServiceFactory::GetForProfile(profile);
  }

  // Executes the model for the feature, waits until the response is received,
  // and returns the response.
  void ExecuteModel(UserVisibleFeatureKey feature,
                    const proto::ComposeRequest& request_metadata,
                    Profile* profile = nullptr) {
    if (!profile) {
      profile = browser()->GetProfile();
    }
    base::RunLoop run_loop;
    ExecuteModelWithLogging(
        GetOptimizationGuideKeyedService(profile),
        ToModelBasedCapabilityKey(feature), request_metadata,
        /*execution_timeout=*/std::nullopt,
        base::BindOnce(&ModelExecutionBrowserTestBase::OnModelExecutionResponse,
                       base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();
  }

  void SetExpectedBearerAccessToken(
      const std::string& expected_bearer_access_token) {
    expected_bearer_access_token_ = expected_bearer_access_token;
  }

  void SetResponseType(ModelExecutionRemoteResponseType response_type) {
    response_type_ = response_type;
  }

  void SetMetricsConsent(bool consent) {
    scoped_metrics_consent_.emplace(consent);
  }

  void WaitForModelQualityLogsUpload(int expected_num_logs_requests) {
    while (num_logs_requests_ < expected_num_logs_requests) {
      base::RunLoop run_loop;
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
          FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(100));
      run_loop.Run();
    }
    EXPECT_EQ(num_logs_requests_, expected_num_logs_requests);
  }

 protected:
  void OnModelExecutionResponse(
      base::OnceClosure on_model_execution_closure,
      OptimizationGuideModelExecutionResult result,
      std::unique_ptr<proto::ComposeLoggingData> logging_data) {
    ModelQualityLogsUploaderService* logs_uploader =
        GetOptimizationGuideKeyedService()
            ->GetModelQualityLogsUploaderService();
    base::WeakPtr<ModelQualityLogsUploaderService> logs_uploader_weak_ptr;
    if (logs_uploader) {
      logs_uploader_weak_ptr = logs_uploader->GetWeakPtr();
    }
    auto log_entry =
        std::make_unique<ModelQualityLogEntry>(logs_uploader_weak_ptr);
    *log_entry->log_ai_data_request()->mutable_compose() = *logging_data;
    if (result.response.has_value() ||
        result.response.error().error() ==
            OptimizationGuideModelExecutionError::ModelExecutionError::
                kFiltered ||
        result.response.error().error() ==
            OptimizationGuideModelExecutionError::ModelExecutionError::
                kUnsupportedLanguage) {
      EXPECT_TRUE(logging_data->has_request());
    }

    if (result.response.has_value()) {
      EXPECT_TRUE(logging_data->has_response());
    }
    model_execution_result_.emplace(std::move(result));
    ModelQualityLogEntry::Upload(std::move(log_entry));
    std::move(on_model_execution_closure).Run();
  }

  std::unique_ptr<net::test_server::HttpResponse>
  HandleGetModelExecutionRequest(const net::test_server::HttpRequest& request) {
    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
    EXPECT_EQ(request.method, net::test_server::METHOD_POST);
    EXPECT_NE(request.headers.end(), request.headers.find("X-Client-Data"));

    // Access token should be set.
    EXPECT_TRUE(
        request.headers.contains(net::HttpRequestHeaders::kAuthorization));
    EXPECT_EQ(expected_bearer_access_token_,
              request.headers.at(net::HttpRequestHeaders::kAuthorization));

    if (response_type_ == ModelExecutionRemoteResponseType::kSuccessful) {
      std::string serialized_response;
      proto::ExecuteResponse execute_response =
          BuildComposeResponse("foo response");
      execute_response.SerializeToString(&serialized_response);
      response->set_code(net::HTTP_OK);
      response->set_content(serialized_response);
    } else if (response_type_ ==
               ModelExecutionRemoteResponseType::kUnsuccessful) {
      response->set_code(net::HTTP_NOT_FOUND);
    } else if (response_type_ == ModelExecutionRemoteResponseType::kMalformed) {
      response->set_code(net::HTTP_OK);
      response->set_content("Not a proto");
    } else if (response_type_ ==
               ModelExecutionRemoteResponseType::kErrorFiltered) {
      std::string serialized_response;
      proto::ExecuteResponse execute_response = BuildTestErrorExecuteResponse(
          proto::ErrorState::ERROR_STATE_FILTERED);
      execute_response.SerializeToString(&serialized_response);
      response->set_code(net::HTTP_OK);
      response->set_content(serialized_response);
    } else if (response_type_ ==
               ModelExecutionRemoteResponseType::kUnsupportedLanguage) {
      std::string serialized_response;
      proto::ExecuteResponse execute_response = BuildTestErrorExecuteResponse(
          proto::ErrorState::ERROR_STATE_UNSUPPORTED_LANGUAGE);
      execute_response.SerializeToString(&serialized_response);
      response->set_code(net::HTTP_OK);
      response->set_content(serialized_response);
    } else {
      NOTREACHED();
    }

    return std::move(response);
  }

  std::unique_ptr<net::test_server::HttpResponse>
  HandleGetModelQualityLogsUploadRequest(
      const net::test_server::HttpRequest& request) {
    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
    EXPECT_EQ(request.method, net::test_server::METHOD_POST);
    EXPECT_NE(request.headers.end(), request.headers.find("X-Client-Data"));

    // Access token should not be set.
    EXPECT_FALSE(
        request.headers.contains(net::HttpRequestHeaders::kAuthorization));

    std::string serialized_response;
    response->set_code(net::HTTP_OK);
    response->set_content(serialized_response);

    num_logs_requests_++;
    return std::move(response);
  }

  // Virtualize for testing different feature configurations.
  virtual void InitializeFeatureList() {}

  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<net::EmbeddedTestServer> model_execution_server_;
  std::unique_ptr<net::EmbeddedTestServer> model_quality_logs_server_;
  base::HistogramTester histogram_tester_;

  ModelExecutionRemoteResponseType response_type_ =
      ModelExecutionRemoteResponseType::kSuccessful;

  // The last model execution response received.
  std::optional<OptimizationGuideModelExecutionResult> model_execution_result_;

  // Identity test support.
  std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
      identity_test_env_adaptor_;

  std::optional<ScopedSetMetricsConsent> scoped_metrics_consent_;

  // The expected authorization header holding the bearer access token.
  std::string expected_bearer_access_token_;

  // The number of requests received by the model quality logs server.
  std::atomic<int> num_logs_requests_ = 0;
};

class ModelExecutionDisabledBrowserTest : public ModelExecutionBrowserTestBase {
  void InitializeFeatureList() override {
    scoped_feature_list_.InitAndDisableFeature(
        features::kOptimizationGuideModelExecution);
  }
};

IN_PROC_BROWSER_TEST_F(ModelExecutionDisabledBrowserTest,
                       ModelExecutionDisabled) {
  proto::ComposeRequest request;
  request.mutable_generate_params()->set_user_input("a user typed this");
  ExecuteModel(UserVisibleFeatureKey::kCompose, request);
  EXPECT_TRUE(model_execution_result_.has_value());
  EXPECT_FALSE(model_execution_result_->response.has_value());
  EXPECT_EQ(OptimizationGuideModelExecutionError::ModelExecutionError::
                kGenericFailure,
            model_execution_result_->response.error().error());
  EXPECT_TRUE(model_execution_result_->response.error().transient());
}

class ModelExecutionEnabledBrowserTest : public ModelExecutionBrowserTestBase {
 public:
  void InitializeFeatureList() override {
    scoped_feature_list_.InitWithFeatures(
        {features::kOptimizationGuideModelExecution,
         features::kModelQualityLogging},
        {});
  }

  void SetUpLocalStatePrefService(PrefService* local_state) override {
    UpdatePerformanceClassPref(local_state,
                               OnDeviceModelPerformanceClass::kServiceCrash);
  }

  OptimizationGuideKeyedService* GetOptGuideKeyedService() {
    return OptimizationGuideKeyedServiceFactory::GetForProfile(
        browser()->GetProfile());
  }

  bool IsSettingVisible(UserVisibleFeatureKey feature) {
    return GetOptGuideKeyedService()->IsSettingVisible(feature);
  }

  bool ShouldFeatureBeCurrentlyEnabledForUser(UserVisibleFeatureKey feature) {
    return GetOptGuideKeyedService()
        ->model_execution_features_controller_
        ->ShouldFeatureBeCurrentlyEnabledForUser(feature);
  }

  bool ShouldFeatureBeCurrentlyAllowedForLogging(
      proto::LogAiDataRequest::FeatureCase feature) {
    const MqlsFeatureMetadata* metadata =
        MqlsFeatureRegistry::GetInstance().GetFeature(feature);
    return GetOptGuideKeyedService()
        ->model_execution_features_controller_
        ->ShouldFeatureBeCurrentlyAllowedForLogging(metadata);
  }
};

IN_PROC_BROWSER_TEST_F(ModelExecutionEnabledBrowserTest,
                       ModelExecutionDisabledInIncognito) {
  BrowserWindowInterface* otr_browser =
      CreateIncognitoBrowser(browser()->GetProfile());
  proto::ComposeRequest request;
  request.mutable_generate_params()->set_user_input("a user typed this");
  ExecuteModel(UserVisibleFeatureKey::kCompose, request,
               otr_browser->GetProfile());
  EXPECT_TRUE(model_execution_result_.has_value());
  EXPECT_FALSE(model_execution_result_->response.has_value());
  EXPECT_EQ(OptimizationGuideModelExecutionError::ModelExecutionError::
                kPermissionDenied,
            model_execution_result_->response.error().error());
  EXPECT_FALSE(model_execution_result_->response.error().transient());

  // The logs shouldn't be uploaded because model execution is disabled for
  // incognito and we wouldn't be receiving any log entry.
  histogram_tester_.ExpectTotalCount(
      "OptimizationGuide.ModelQualityLogsUploaderService.UploadStatus", 0);
}

IN_PROC_BROWSER_TEST_F(ModelExecutionEnabledBrowserTest,
                       ModelExecutionFailsNoUserSignIn) {
  proto::ComposeRequest request;
  request.mutable_generate_params()->set_user_input("a user typed this");
  ExecuteModel(UserVisibleFeatureKey::kCompose, request);
  EXPECT_TRUE(model_execution_result_.has_value());
  EXPECT_FALSE(model_execution_result_->response.has_value());
  EXPECT_EQ(OptimizationGuideModelExecutionError::ModelExecutionError::
                kPermissionDenied,
            model_execution_result_->response.error().error());
  EXPECT_FALSE(model_execution_result_->response.error().transient());

  // The logs shouldn't be uploaded because model execution is denied without
  // user signin, also model quality logs.
  histogram_tester_.ExpectTotalCount(
      "OptimizationGuide.ModelQualityLogsUploaderService.UploadStatus", 0);
}

IN_PROC_BROWSER_TEST_F(ModelExecutionEnabledBrowserTest,
                       ModelExecutionSuccess_WithoutMetricsConsent) {
  EnableSignin();
  SetMetricsConsent(false);
  SetExpectedBearerAccessToken("Bearer access_token");

  proto::ComposeRequest request;
  request.mutable_generate_params()->set_user_input("a user typed this");
  ExecuteModel(UserVisibleFeatureKey::kCompose, request);
  EXPECT_TRUE(model_execution_result_.has_value());
  EXPECT_TRUE(model_execution_result_->response.has_value());
  auto response = ParsedAnyMetadata<proto::ComposeResponse>(
      model_execution_result_->response.value());
  EXPECT_EQ("foo response", response->output());

  // The logs shouldn't be uploaded because there is no metrics consent.
  histogram_tester_.ExpectUniqueSample(
      "OptimizationGuide.ModelQualityLogsUploaderService.UploadStatus.Compose",
      ModelQualityLogsUploadStatus::kMetricsReportingDisabled, 1);
}

IN_PROC_BROWSER_TEST_F(ModelExecutionEnabledBrowserTest,
                       ModelExecutionSuccess_WithMetricsConsent) {
  EnableSignin();
  SetMetricsConsent(true);
  SetExpectedBearerAccessToken("Bearer access_token");

  proto::ComposeRequest request;
  request.mutable_generate_params()->set_user_input("a user typed this");
  ExecuteModel(UserVisibleFeatureKey::kCompose, request);
  EXPECT_TRUE(model_execution_result_.has_value());
  EXPECT_TRUE(model_execution_result_->response.has_value());
  auto response = ParsedAnyMetadata<proto::ComposeResponse>(
      model_execution_result_->response.value());
  EXPECT_EQ("foo response", response->output());

  WaitForModelQualityLogsUpload(1);
  histogram_tester_.ExpectUniqueSample(
      "OptimizationGuide.ModelQualityLogsUploaderService.UploadStatus.Compose",
      ModelQualityLogsUploadStatus::kUploadSuccessful, 1);
}

IN_PROC_BROWSER_TEST_F(ModelExecutionEnabledBrowserTest,
                       ModelExecutionFailsForUnsuccessfulResponse) {
  EnableSignin();
  SetExpectedBearerAccessToken("Bearer access_token");
  SetResponseType(ModelExecutionRemoteResponseType::kUnsuccessful);

  // Enable metrics consent for logging.
  SetMetricsConsent(true);
  ASSERT_TRUE(
      g_browser_process->GetMetricsServicesManager()->IsMetricsConsentGiven());

  proto::ComposeRequest request;
  request.mutable_generate_params()->set_user_input("a user typed this");
  ExecuteModel(UserVisibleFeatureKey::kCompose, request);
  EXPECT_TRUE(model_execution_result_.has_value());
  EXPECT_FALSE(model_execution_result_->response.has_value());
  EXPECT_EQ(OptimizationGuideModelExecutionError::ModelExecutionError::
                kGenericFailure,
            model_execution_result_->response.error().error());
  EXPECT_TRUE(model_execution_result_->response.error().transient());

  // The logs shouldn't be uploaded when model execution fails for unsuccessful
  // response.
  histogram_tester_.ExpectTotalCount(
      "OptimizationGuide.ModelQualityLogsUploaderService.UploadStatus", 0);
}

IN_PROC_BROWSER_TEST_F(ModelExecutionEnabledBrowserTest,
                       ModelExecutionFailsForMalformedResponse) {
  EnableSignin();
  SetExpectedBearerAccessToken("Bearer access_token");
  SetResponseType(ModelExecutionRemoteResponseType::kMalformed);

  proto::ComposeRequest request;
  request.mutable_generate_params()->set_user_input("a user typed this");
  ExecuteModel(UserVisibleFeatureKey::kCompose, request);
  EXPECT_TRUE(model_execution_result_.has_value());
  EXPECT_FALSE(model_execution_result_->response.has_value());
  EXPECT_EQ(OptimizationGuideModelExecutionError::ModelExecutionError::
                kGenericFailure,
            model_execution_result_->response.error().error());
  EXPECT_TRUE(model_execution_result_->response.error().transient());
}

IN_PROC_BROWSER_TEST_F(ModelExecutionEnabledBrowserTest,
                       ModelExecutionFailsForErrorFilteredResponse) {
  EnableSignin();
  SetExpectedBearerAccessToken("Bearer access_token");
  SetResponseType(ModelExecutionRemoteResponseType::kErrorFiltered);

  proto::ComposeRequest request;
  request.mutable_generate_params()->set_user_input("a user typed this");
  ExecuteModel(UserVisibleFeatureKey::kCompose, request);
  EXPECT_TRUE(model_execution_result_.has_value());
  EXPECT_FALSE(model_execution_result_->response.has_value());
  EXPECT_EQ(
      OptimizationGuideModelExecutionError::ModelExecutionError::kFiltered,
      model_execution_result_->response.error().error());
}

IN_PROC_BROWSER_TEST_F(ModelExecutionEnabledBrowserTest,
                       ModelExecutionFailsForUnsupportedLanguageResponse) {
  EnableSignin();
  auto* prefs = browser()->GetProfile()->GetPrefs();
  prefs->SetInteger(
      prefs::GetSettingEnabledPrefName(UserVisibleFeatureKey::kCompose),
      static_cast<int>(prefs::FeatureOptInState::kEnabled));
  SetExpectedBearerAccessToken("Bearer access_token");
  SetResponseType(ModelExecutionRemoteResponseType::kUnsupportedLanguage);

  // Enable metrics consent for logging.
  SetMetricsConsent(true);
  ASSERT_TRUE(
      g_browser_process->GetMetricsServicesManager()->IsMetricsConsentGiven());

  proto::ComposeRequest request;
  request.mutable_generate_params()->set_user_input("a user typed this");
  ExecuteModel(UserVisibleFeatureKey::kCompose, request);
  EXPECT_TRUE(model_execution_result_.has_value());
  EXPECT_FALSE(model_execution_result_->response.has_value());
  EXPECT_EQ(OptimizationGuideModelExecutionError::ModelExecutionError::
                kUnsupportedLanguage,
            model_execution_result_->response.error().error());

  // There should be no error-status reports about log uploading (we don't try
  // to upload logs in this test, so there's no success report either).
  histogram_tester_.ExpectTotalCount(
      "OptimizationGuide.ModelQualityLogsUploaderService.UploadStatus.Compose",
      0);
}

class ModelExecutionInternalsPageBrowserTest
    : public ModelExecutionEnabledBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* cmd) override {
    ModelExecutionEnabledBrowserTest::SetUpCommandLine(cmd);
    cmd->AppendSwitch(switches::kDebugLoggingEnabled);
  }
  void CheckInternalsLog(std::string_view message) {
    auto* logger =
        GetOptimizationGuideKeyedService()->GetOptimizationGuideLogger();
    EXPECT_THAT(logger->recent_log_messages_,
                testing::Contains(testing::Field(
                    &OptimizationGuideLogger::LogMessage::message,
                    testing::HasSubstr(message))));
  }
};

IN_PROC_BROWSER_TEST_F(ModelExecutionInternalsPageBrowserTest,
                       LoggedInInternalsPage) {
  EnableSignin();
  SetExpectedBearerAccessToken("Bearer access_token");

  proto::ComposeRequest request;
  request.mutable_generate_params()->set_user_input("foo");
  ExecuteModel(UserVisibleFeatureKey::kCompose, request);
  EXPECT_TRUE(model_execution_result_.has_value());
  EXPECT_TRUE(model_execution_result_->response.has_value());
  CheckInternalsLog("ExecuteModel");
  // CheckInternalsLog("TabOrganization Request");
  CheckInternalsLog("OnModelExecutionResponse");
}

class ModelExecutionEnabledBrowserTestWithExplicitBrowserSignin
    : public ModelExecutionEnabledBrowserTest {
 public:
  void InitializeFeatureList() override {
    scoped_feature_list_.InitWithFeatures(
        {features::internal::kHistorySearchSettingsVisibility},
        {});
  }
};

IN_PROC_BROWSER_TEST_F(
    ModelExecutionEnabledBrowserTestWithExplicitBrowserSignin,
    PRE_EnableFeatureViaPref) {
  EnableSignin();
  auto* prefs = browser()->GetProfile()->GetPrefs();
  prefs->SetInteger(
      prefs::GetSettingEnabledPrefName(UserVisibleFeatureKey::kWallpaperSearch),
      static_cast<int>(prefs::FeatureOptInState::kEnabled));

  histogram_tester_.ExpectUniqueSample(
      "OptimizationGuide.ModelExecution.FeatureEnabledAtStartup.Compose", false,
      1);
  histogram_tester_.ExpectUniqueSample(
      "OptimizationGuide.ModelExecution.FeatureEnabledAtStartup."
      "WallpaperSearch",
      false, 1);
  histogram_tester_.ExpectTotalCount(
      "OptimizationGuide.ModelExecution.FeatureEnabledAtSettingsChange.Compose",
      0);
  histogram_tester_.ExpectUniqueSample(
      "OptimizationGuide.ModelExecution.FeatureEnabledAtSettingsChange."
      "WallpaperSearch",
      true, 1);
}

IN_PROC_BROWSER_TEST_F(
    ModelExecutionEnabledBrowserTestWithExplicitBrowserSignin,
    EnableFeatureViaPref) {
#if !BUILDFLAG(IS_CHROMEOS)
  EXPECT_TRUE(IsSignedIn());
#endif
  histogram_tester_.ExpectUniqueSample(
      "OptimizationGuide.ModelExecution.FeatureEnabledAtStartup.Compose", false,
      1);
  histogram_tester_.ExpectUniqueSample(
      "OptimizationGuide.ModelExecution.FeatureEnabledAtStartup."
      "WallpaperSearch",
      false, 1);
  histogram_tester_.ExpectTotalCount(
      "OptimizationGuide.ModelExecution.FeatureEnabledAtSettingsChange.Compose",
      0);
  histogram_tester_.ExpectTotalCount(
      "OptimizationGuide.ModelExecution.FeatureEnabledAtSettingsChange."
      "WallpaperSearch",
      0);
}

IN_PROC_BROWSER_TEST_F(
    ModelExecutionEnabledBrowserTestWithExplicitBrowserSignin,
    PRE_HistorySearchRecordsSyntheticFieldTrial) {
  EnableSignin();
#if BUILDFLAG(BUILD_TFLITE_WITH_XNNPACK)
  EXPECT_TRUE(IsSettingVisible(UserVisibleFeatureKey::kHistorySearch));
#else
  EXPECT_FALSE(IsSettingVisible(UserVisibleFeatureKey::kHistorySearch));
#endif

  browser()->GetProfile()->GetPrefs()->SetInteger(
      prefs::GetSettingEnabledPrefName(UserVisibleFeatureKey::kHistorySearch),
      static_cast<int>(prefs::FeatureOptInState::kEnabled));
  EXPECT_TRUE(variations::IsInSyntheticTrialGroup(
      "SyntheticModelExecutionFeatureHistorySearch", "Disabled"));
}

IN_PROC_BROWSER_TEST_F(
    ModelExecutionEnabledBrowserTestWithExplicitBrowserSignin,
    HistorySearchRecordsSyntheticFieldTrial) {
#if !BUILDFLAG(IS_CHROMEOS)
  EXPECT_TRUE(IsSignedIn());
#endif
  EXPECT_TRUE(ShouldFeatureBeCurrentlyEnabledForUser(
      UserVisibleFeatureKey::kHistorySearch));
  EXPECT_TRUE(variations::IsInSyntheticTrialGroup(
      "SyntheticModelExecutionFeatureHistorySearch", "Enabled"));
}

class ModelExecutionComposeLoggingDisabledTest
    : public ModelExecutionEnabledBrowserTest {
 public:
  void InitializeFeatureList() override {
    scoped_feature_list_.InitWithFeaturesAndParameters(
        {{features::kOptimizationGuideModelExecution, {}},
         {features::kModelQualityLogging, {}}},
        {features::kComposeMqlsLogging});
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_F(ModelExecutionComposeLoggingDisabledTest,
                       LoggingForFeatureNotEnabled) {
  EnableSignin();
  SetExpectedBearerAccessToken("Bearer access_token");

  // Enable metrics consent for logging.
  SetMetricsConsent(true);
  ASSERT_TRUE(
      g_browser_process->GetMetricsServicesManager()->IsMetricsConsentGiven());

  proto::ComposeRequest request;
  request.mutable_generate_params()->set_user_input("a user typed this");
  ExecuteModel(UserVisibleFeatureKey::kCompose, request);
  EXPECT_TRUE(model_execution_result_.has_value());
  EXPECT_TRUE(model_execution_result_->response.has_value());
  auto response = ParsedAnyMetadata<proto::ComposeResponse>(
      model_execution_result_->response.value());
  EXPECT_EQ("foo response", response->output());

  // The logs shouldn't be uploaded because the feature is not enabled for
  // logging.
  histogram_tester_.ExpectUniqueSample(
      "OptimizationGuide.ModelQualityLogsUploaderService.UploadStatus.Compose",
      ModelQualityLogsUploadStatus::kLoggingNotEnabled, 1);
}

class ModelExecutionNewFeaturesEnabledAutomaticallyTest
    : public ModelExecutionEnabledBrowserTest {
 public:
  void InitializeFeatureList() override {
    std::vector<base::test::FeatureRefAndParams> enabled_features = {
        {features::kOptimizationGuideModelExecution, {}},
        {features::internal::kWallpaperSearchSettingsVisibility, {}}};
    std::vector<base::test::FeatureRef> disabled_features = {
        features::internal::kWallpaperSearchGraduated,
        features::internal::kComposeGraduated};

    std::string test_name =
        ::testing::UnitTest::GetInstance()->current_test_info()->name();
    // Make the new feature visible in the second start of the test.
    if (!base::StartsWith(test_name, "PRE_")) {
      enabled_features.push_back(
          {features::internal::kComposeSettingsVisibility, {}});
      enabled_features.push_back(
          {features::internal::kHistorySearchSettingsVisibility,
           {{"enable_feature_when_main_toggle_on", "false"}}});
    } else {
      disabled_features.push_back(
          features::internal::kHistorySearchSettingsVisibility);
    }

    scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features,
                                                       disabled_features);
  }
};

#if !BUILDFLAG(IS_ANDROID)

class ModelExecutionEnterprisePolicyBrowserTest
    : public ModelExecutionEnabledBrowserTest {
 public:
  void SetUp() override {
    policy_provider_.SetDefaultReturns(
        /*is_initialization_complete_return=*/true,
        /*is_first_policy_load_complete_return=*/true);
    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
        &policy_provider_);
    ModelExecutionEnabledBrowserTest::SetUp();
  }

  void InitializeFeatureList() override {
    std::vector<base::test::FeatureRef> enabled_features = {
        features::kOptimizationGuideModelExecution,
        features::kModelQualityLogging,
        features::internal::kWallpaperSearchSettingsVisibility};
    std::vector<base::test::FeatureRef> disabled_features = {
        features::internal::kComposeGraduated,
        features::internal::kComposeSettingsVisibility,
        features::internal::kWallpaperSearchGraduated};

    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
  }

 protected:
  testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
};

IN_PROC_BROWSER_TEST_F(ModelExecutionEnterprisePolicyBrowserTest,
                       EnableComposeWithoutLogging) {
  EnableSignin();

  SetExpectedBearerAccessToken("Bearer access_token");
  SetResponseType(ModelExecutionRemoteResponseType::kUnsupportedLanguage);

  // Enable metrics consent for logging.
  SetMetricsConsent(true);
  ASSERT_TRUE(
      g_browser_process->GetMetricsServicesManager()->IsMetricsConsentGiven());

  auto* prefs = browser()->GetProfile()->GetPrefs();
  prefs->SetInteger(
      prefs::GetSettingEnabledPrefName(UserVisibleFeatureKey::kCompose),
      static_cast<int>(optimization_guide::prefs::FeatureOptInState::kEnabled));
  base::RunLoop().RunUntilIdle();

  // Enable without logging via the enterprise policy.
  policy::PolicyMap policies;
  policies.Set(policy::key::kHelpMeWriteSettings,
               policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
               policy::POLICY_SOURCE_CLOUD,
               base::Value(static_cast<int>(
                   model_execution::prefs::ModelExecutionEnterprisePolicyValue::
                       kAllowWithoutLogging)),
               nullptr);
  policy_provider_.UpdateChromePolicy(policies);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(IsSettingVisible(UserVisibleFeatureKey::kCompose));
  EXPECT_TRUE(
      ShouldFeatureBeCurrentlyEnabledForUser(UserVisibleFeatureKey::kCompose));

  proto::ComposeRequest request_1;
  request_1.mutable_generate_params()->set_user_input("a user typed this");
  ExecuteModel(UserVisibleFeatureKey::kCompose, request_1);

  // The logs should be disabled via enterprise policy.
  histogram_tester_.ExpectUniqueSample(
      "OptimizationGuide.ModelQualityLogsUploaderService.UploadStatus.Compose",
      ModelQualityLogsUploadStatus::kDisabledDueToEnterprisePolicy, 1);

  // Enable via the enterprise policy and check upload.
  policies.Set(
      policy::key::kHelpMeWriteSettings, policy::POLICY_LEVEL_MANDATORY,
      policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
      base::Value(static_cast<int>(
          model_execution::prefs::ModelExecutionEnterprisePolicyValue::kAllow)),
      nullptr);
  policy_provider_.UpdateChromePolicy(policies);
  prefs->SetInteger(
      prefs::GetSettingEnabledPrefName(UserVisibleFeatureKey::kCompose),
      static_cast<int>(prefs::FeatureOptInState::kEnabled));
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(IsSettingVisible(UserVisibleFeatureKey::kCompose));
  EXPECT_TRUE(
      ShouldFeatureBeCurrentlyEnabledForUser(UserVisibleFeatureKey::kCompose));

  proto::ComposeRequest request_2;
  request_2.mutable_generate_params()->set_user_input("a user typed this");
  ExecuteModel(UserVisibleFeatureKey::kCompose, request_2);

  // No new blocked logs samples should have been recorded.
  histogram_tester_.ExpectUniqueSample(
      "OptimizationGuide.ModelQualityLogsUploaderService.UploadStatus.Compose",
      optimization_guide::ModelQualityLogsUploadStatus::
          kDisabledDueToEnterprisePolicy,
      1);
}

IN_PROC_BROWSER_TEST_F(ModelExecutionEnterprisePolicyBrowserTest,
                       DisableThenEnableWallpaperSearch) {
  EnableSignin();

  auto* prefs = browser()->GetProfile()->GetPrefs();
  prefs->SetInteger(
      prefs::GetSettingEnabledPrefName(UserVisibleFeatureKey::kWallpaperSearch),
      static_cast<int>(prefs::FeatureOptInState::kEnabled));
  base::RunLoop().RunUntilIdle();

  // Default policy value allows the feature.
  EXPECT_TRUE(IsSettingVisible(UserVisibleFeatureKey::kWallpaperSearch));
  EXPECT_TRUE(ShouldFeatureBeCurrentlyEnabledForUser(
      UserVisibleFeatureKey::kWallpaperSearch));

  // Disable via the enterprise policy.
  policy::PolicyMap policies;
  policies.Set(policy::key::kCreateThemesSettings,
               policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
               policy::POLICY_SOURCE_CLOUD,
               base::Value(static_cast<int>(
                   model_execution::prefs::ModelExecutionEnterprisePolicyValue::
                       kDisable)),
               nullptr);
  policy_provider_.UpdateChromePolicy(policies);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(IsSettingVisible(UserVisibleFeatureKey::kWallpaperSearch));
  EXPECT_FALSE(ShouldFeatureBeCurrentlyEnabledForUser(
      UserVisibleFeatureKey::kWallpaperSearch));

  // Enable via the enterprise policy.
  policies.Set(
      policy::key::kCreateThemesSettings, policy::POLICY_LEVEL_MANDATORY,
      policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
      base::Value(static_cast<int>(
          model_execution::prefs::ModelExecutionEnterprisePolicyValue::kAllow)),
      nullptr);
  policy_provider_.UpdateChromePolicy(policies);
  prefs->SetInteger(
      prefs::GetSettingEnabledPrefName(UserVisibleFeatureKey::kWallpaperSearch),
      static_cast<int>(prefs::FeatureOptInState::kEnabled));
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(IsSettingVisible(UserVisibleFeatureKey::kWallpaperSearch));
  EXPECT_TRUE(ShouldFeatureBeCurrentlyEnabledForUser(
      UserVisibleFeatureKey::kWallpaperSearch));
}

#endif  //  !BUILDFLAG(IS_ANDROID)

}  // namespace optimization_guide
