// Copyright 2024 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/ai/ai_summarizer.h"

#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/protobuf_matchers.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/types/expected.h"
#include "chrome/browser/ai/ai_test_utils.h"
#include "chrome/browser/ai/features.h"
#include "chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
#include "components/optimization_guide/core/model_execution/manifest_broker/test/fake_manifest_broker.h"
#include "components/optimization_guide/core/model_execution/manifest_broker/test/scenario_builder.h"
#include "components/optimization_guide/core/model_execution/test/mock_on_device_capability.h"
#include "components/optimization_guide/core/model_execution/test/substitution_builder.h"
#include "components/optimization_guide/core/optimization_guide_proto_util.h"
#include "components/optimization_guide/core/optimization_guide_switches.h"
#include "components/optimization_guide/core/optimization_guide_util.h"
#include "components/optimization_guide/proto/feature_configs.pb.h"
#include "components/optimization_guide/proto/features/summarize.pb.h"
#include "components/optimization_guide/proto/string_value.pb.h"
#include "content/public/browser/web_contents.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
#include "services/on_device_model/public/cpp/features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features_generated.h"
#include "third_party/blink/public/mojom/ai/ai_manager.mojom.h"
#include "third_party/blink/public/mojom/ai/model_streaming_responder.mojom.h"

namespace {

namespace proto = ::optimization_guide::proto;

using ::base::test::TestFuture;
using ::blink::mojom::AILanguageCode;
using ::blink::mojom::AILanguageCodePtr;
using ::on_device_model::mojom::PerformanceClass;
using ::optimization_guide::FieldSubstitution;
using ::optimization_guide::ForbidUnsafe;
using ::optimization_guide::ProtoField;
using ::optimization_guide::StringValueField;
using ::optimization_guide::proto::SummarizeRequest;
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;

constexpr char kSharedContextString[] = "test shared context";
constexpr char kContextString[] = "test context";
constexpr char kInputString[] = "input string";

struct Error {
  blink::mojom::AIManagerCreateClientError error;
  blink::mojom::QuotaErrorInfoPtr quota_error_info;
};

using CreateSummarizerResult =
    base::expected<mojo::PendingRemote<blink::mojom::AISummarizer>, Error>;

class TestCreateSummarizerClient
    : public blink::mojom::AIManagerCreateSummarizerClient {
 public:
  TestCreateSummarizerClient() = default;
  ~TestCreateSummarizerClient() override = default;
  TestCreateSummarizerClient(const TestCreateSummarizerClient&) = delete;
  TestCreateSummarizerClient& operator=(const TestCreateSummarizerClient&) =
      delete;

  mojo::PendingRemote<blink::mojom::AIManagerCreateSummarizerClient>
  BindNewPipeAndPassRemote() {
    return receiver_.BindNewPipeAndPassRemote();
  }

  void OnResult(
      mojo::PendingRemote<::blink::mojom::AISummarizer> summarizer) override {
    result_.SetValue(std::move(summarizer));
  }

  void OnError(blink::mojom::AIManagerCreateClientError error,
               blink::mojom::QuotaErrorInfoPtr quota_error_info) override {
    result_.SetValue(
        base::unexpected(Error{error, std::move(quota_error_info)}));
  }

  TestFuture<CreateSummarizerResult>& result() { return result_; }

 private:
  TestFuture<CreateSummarizerResult> result_;
  mojo::Receiver<blink::mojom::AIManagerCreateSummarizerClient> receiver_{this};
};

blink::mojom::AISummarizerCreateOptionsPtr GetDefaultOptions() {
  return blink::mojom::AISummarizerCreateOptions::New(
      /*shared_context=*/"", blink::mojom::AISummarizerType::kKeyPoints,
      blink::mojom::AISummarizerFormat::kMarkDown,
      blink::mojom::AISummarizerLength::kShort,
      blink::mojom::PerformancePreference::kAuto,
      /*expected_input_languages=*/std::vector<AILanguageCodePtr>(),
      /*expected_context_languages=*/std::vector<AILanguageCodePtr>(),
      /*output_language=*/AILanguageCode::New(""));
}

#if !BUILDFLAG(IS_ANDROID)
// This method is not used on Android as Android doesn't support text safety
// yet. crbug.com/442914748
optimization_guide::proto::FeatureTextSafetyConfiguration CreateSafetyConfig() {
  optimization_guide::proto::FeatureTextSafetyConfiguration safety_config;
  safety_config.set_feature(
      optimization_guide::proto::MODEL_EXECUTION_FEATURE_SUMMARIZE);
  safety_config.mutable_safety_category_thresholds()->Add(ForbidUnsafe());
  {
    auto* check = safety_config.add_request_check();
    check->mutable_input_template()->Add(FieldSubstitution(
        "%s", ProtoField({SummarizeRequest::kArticleFieldNumber})));
  }
  {
    auto* check = safety_config.add_request_check();
    check->mutable_input_template()->Add(FieldSubstitution(
        "%s", ProtoField({SummarizeRequest::kContextFieldNumber})));
  }
  return safety_config;
}
#endif

class AISummarizerTest : public AITestUtils::AITestBase {
 public:
  AISummarizerTest() {
    scoped_feature_list_.InitWithFeatures(
        {blink::features::kAISummarizationAPI,
         blink::features::kAISummarizationPerformancePreference},
        {});
  }

 protected:
  proto::SolutionConfig CreateSolution() override {
    proto::OnDeviceModelExecutionFeatureConfig config;
    config.set_can_skip_text_safety(true);
    config.set_feature(proto::ModelExecutionFeature::
                           MODEL_EXECUTION_FEATURE_SUMMARIZE);

    auto& input_config = *config.mutable_input_config();
    input_config.set_request_base_name(SummarizeRequest().GetTypeName());

    *input_config.add_execute_substitutions() = FieldSubstitution(
        "%s", ProtoField({SummarizeRequest::kArticleFieldNumber}));
    *input_config.add_execute_substitutions() = FieldSubstitution(
        "%s", ProtoField({SummarizeRequest::kContextFieldNumber}));

    auto& output_config = *config.mutable_output_config();
    output_config.set_proto_type(
        proto::StringValue().GetTypeName());
    *output_config.mutable_proto_field() = StringValueField();

    proto::SolutionConfig solution_config;
    *solution_config.mutable_feature() = config;
#if !BUILDFLAG(IS_ANDROID)
    *solution_config.mutable_safety() = CreateSafetyConfig();
#endif
    return solution_config;
  }

  mojo::Remote<blink::mojom::AISummarizer> GetAISummarizerRemote(
      blink::mojom::AISummarizerCreateOptionsPtr options =
          GetDefaultOptions()) {
    TestCreateSummarizerClient create_summarizer_client;
    GetAIManagerRemote()->CreateSummarizer(
        create_summarizer_client.BindNewPipeAndPassRemote(), std::move(options),
        /*monitor=*/mojo::NullRemote());

    CreateSummarizerResult result = create_summarizer_client.result().Take();
    EXPECT_OK(result);
    return mojo::Remote<blink::mojom::AISummarizer>(std::move(result.value()));
  }

  void RunSimpleSummarizeTest(blink::mojom::AISummarizerType type,
                              blink::mojom::AISummarizerFormat format,
                              blink::mojom::AISummarizerLength length) {
    SetExecuteResult({"Result text"});

    const auto options = blink::mojom::AISummarizerCreateOptions::New(
        /*shared_context=*/"", type, format, length,
        blink::mojom::PerformancePreference::kAuto,
        /*expected_input_languages=*/std::vector<AILanguageCodePtr>(),
        /*expected_context_languages=*/std::vector<AILanguageCodePtr>(),
        /*output_language=*/AILanguageCode::New(""));
    mojo::Remote<blink::mojom::AISummarizer> summarizer_remote =
        GetAISummarizerRemote(options.Clone());

    EXPECT_THAT(Summarize(*summarizer_remote, kInputString, kContextString),
                ElementsAreArray({"Result text"}));
  }

  std::vector<std::string> Summarize(blink::mojom::AISummarizer& summarizer,
                                     const std::string& input,
                                     const std::string& context) {
    AITestUtils::TestStreamingResponder responder;
    summarizer.Summarize(input, context, responder.BindRemote());
    EXPECT_TRUE(responder.WaitForCompletion());
    // Return Summarizer's response without the final empty string chunk.
    return responder.responses_without_last();
  }

  void EnsureModelIsReady() {
    TestCreateSummarizerClient summarizer_client;
    GetAIManagerRemote()->CreateSummarizer(
        summarizer_client.BindNewPipeAndPassRemote(), GetDefaultOptions(),
        /*monitor=*/mojo::NullRemote());

    auto result = summarizer_client.result().Take();
    EXPECT_OK(result);
  }

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

TEST(AISummarizerStandaloneTest, CombineContexts) {
  EXPECT_EQ("", AISummarizer::CombineContexts("", ""));
  EXPECT_EQ("a\n", AISummarizer::CombineContexts("a", ""));
  EXPECT_EQ("b\n", AISummarizer::CombineContexts("", "b"));
  EXPECT_EQ("a b\n", AISummarizer::CombineContexts("a", "b"));
}

TEST_F(AISummarizerTest, CanCreateDefaultOptions) {
  {
    base::test::TestFuture<blink::mojom::ModelAvailabilityCheckResult> future;
    GetAIManagerInterface()->CanCreateSummarizer(GetDefaultOptions(),
                                                 future.GetCallback());
    EXPECT_EQ(future.Get(),
              blink::mojom::ModelAvailabilityCheckResult::kDownloadable);
  }

  // After model is ready, `CanCreateSummarizer` should return available.
  EnsureModelIsReady();

  {
    base::test::TestFuture<blink::mojom::ModelAvailabilityCheckResult> future;
    GetAIManagerInterface()->CanCreateSummarizer(GetDefaultOptions(),
                                                 future.GetCallback());
    EXPECT_EQ(future.Get(),
              blink::mojom::ModelAvailabilityCheckResult::kAvailable);
  }
}

TEST_F(AISummarizerTest, CanCreateIsLanguagesSupported) {
  EnsureModelIsReady();

  auto options = GetDefaultOptions();
  options->output_language = AILanguageCode::New("en");
  options->expected_input_languages =
      AITestUtils::ToMojoLanguageCodes({"en-US", ""});
  options->expected_context_languages =
      AITestUtils::ToMojoLanguageCodes({"en-GB", ""});

  base::test::TestFuture<blink::mojom::ModelAvailabilityCheckResult> future;
  GetAIManagerInterface()->CanCreateSummarizer(std::move(options),
                                               future.GetCallback());
  EXPECT_EQ(future.Get(),
            blink::mojom::ModelAvailabilityCheckResult::kAvailable);
}

TEST_F(AISummarizerTest, CanCreateUnIsLanguagesSupported) {
  auto options = GetDefaultOptions();
  options->output_language = AILanguageCode::New("es-ES");
  options->expected_input_languages =
      AITestUtils::ToMojoLanguageCodes({"en", "tlh", "ja"});
  options->expected_context_languages =
      AITestUtils::ToMojoLanguageCodes({"ar", "zh", "hi"});
  base::MockCallback<AIManager::CanCreateSummarizerCallback> callback;
  EXPECT_CALL(callback, Run(blink::mojom::ModelAvailabilityCheckResult::
                                kUnavailableUnsupportedLanguage));
  GetAIManagerInterface()->CanCreateSummarizer(std::move(options),
                                               callback.Get());
}

TEST_F(AISummarizerTest, ToProtoOptionsLanguagesSupported) {
  // Summarizer proto expects a limited set of BCP 47 base language codes.
  std::vector<std::pair<std::string, std::string>> languages = {
      {"en", "en"}, {"en-us", "en"}, {"en-uk", "en"},
      {"es", "es"}, {"es-sp", "es"}, {"es-mx", "es"},
      {"ja", "ja"}, {"ja-jp", "ja"}, {"ja-foo", "ja"},
  };
  blink::mojom::AISummarizerCreateOptionsPtr options = GetDefaultOptions();
  for (const auto& language : languages) {
    options->output_language = AILanguageCode::New(language.first);
    const auto proto_options = AISummarizer::ToProtoOptions(options);
    EXPECT_EQ(proto_options->output_language(), language.second);
  }
}

TEST_F(AISummarizerTest, CreateSummarizerNoService) {
  SetupNullOptimizationGuideKeyedService();

  TestCreateSummarizerClient create_summarizer_client;
  GetAIManagerRemote()->CreateSummarizer(
      create_summarizer_client.BindNewPipeAndPassRemote(), GetDefaultOptions(),
      /*monitor=*/mojo::NullRemote());

  CreateSummarizerResult result = create_summarizer_client.result().Take();
  EXPECT_FALSE(result.has_value());
  EXPECT_EQ(result.error().error,
            blink::mojom::AIManagerCreateClientError::kUnableToCreateSession);
}

TEST_F(AISummarizerTest, SummarizeTelemetry) {
  base::HistogramTester histogram_tester;
  EXPECT_CALL(*mock_optimization_guide_keyed_service_,
              GetOnDeviceModelEligibility(
                  optimization_guide::mojom::OnDeviceFeature::kSummarize))
      .WillRepeatedly(testing::Return(
          optimization_guide::OnDeviceModelEligibilityReason::kSuccess));
  EnsureModelIsReady();
  GetAISummarizerRemote();

  histogram_tester.ExpectUniqueSample(
      "OptimizationGuide.ModelExecution."
      "OnDeviceModelEligibilityReason.Summarize",
      optimization_guide::OnDeviceModelEligibilityReason::kSuccess, 2);
}

TEST_F(AISummarizerTest, CreateSummarizerModelNotEligible) {
#if BUILDFLAG(IS_ANDROID)
  UnInstallBaseModel();
  fake_broker_->java_helper().settings().SetDefaultStatusCheckResult(
      on_device_model::ModelDownloaderAndroid::ModelStatus::kUnavailable);
#else
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      {optimization_guide::features::kOnDeviceModelPerformanceParams},
      {on_device_model::features::kOnDeviceModelCpuBackend});

  fake_broker_->settings().performance_class =
      on_device_model::mojom::PerformanceClass::kVeryLow;
#endif  // BUILDFLAG(IS_ANDROID)

  TestCreateSummarizerClient create_summarizer_client;
  GetAIManagerRemote()->CreateSummarizer(
      create_summarizer_client.BindNewPipeAndPassRemote(), GetDefaultOptions(),
      /*monitor=*/mojo::NullRemote());

  CreateSummarizerResult result = create_summarizer_client.result().Take();
  EXPECT_FALSE(result.has_value());
  EXPECT_EQ(result.error().error,
            blink::mojom::AIManagerCreateClientError::kUnableToCreateSession);
}

#if BUILDFLAG(IS_ANDROID)
// Android doesn't support text safety yet. crbug.com/442914748
TEST_F(AISummarizerTest, CreateSummarizerWithTextSafetyCheck) {
  SetSolutionConfig([&]() {
    auto solution_config = CreateSolution();
    solution_config.mutable_feature()->set_can_skip_text_safety(false);
    return solution_config;
  }());

  TestCreateSummarizerClient create_summarizer_client;
  GetAIManagerRemote()->CreateSummarizer(
      create_summarizer_client.BindNewPipeAndPassRemote(), GetDefaultOptions(),
      /*monitor=*/mojo::NullRemote());

  CreateSummarizerResult result = create_summarizer_client.result().Take();
  EXPECT_FALSE(result.has_value());
  EXPECT_EQ(result.error().error,
            blink::mojom::AIManagerCreateClientError::kUnableToCreateSession);
}
#endif  // BUILDFLAG(IS_ANDROID)

TEST_F(AISummarizerTest, CreateSummarizerUnableToCalculateTokenSize) {
  // Incorrect `request_base_name` cause session to fail constructing input
  // string and checking token size.
  SetSolutionConfig([&]() {
    auto solution_config = CreateSolution();
    solution_config.mutable_feature()
        ->mutable_input_config()
        ->set_request_base_name("InvalidRequestBaseName");
    return solution_config;
  }());

  TestCreateSummarizerClient create_summarizer_client;
  auto options = GetDefaultOptions();
  options->shared_context = kSharedContextString;
  GetAIManagerRemote()->CreateSummarizer(
      create_summarizer_client.BindNewPipeAndPassRemote(), std::move(options),
      /*monitor=*/mojo::NullRemote());

  CreateSummarizerResult result = create_summarizer_client.result().Take();
  EXPECT_FALSE(result.has_value());
  EXPECT_EQ(
      result.error().error,
      blink::mojom::AIManagerCreateClientError::kUnableToCalculateTokenSize);
}

TEST_F(AISummarizerTest, CreateSummarizerContextLimitExceededError) {
  SetSizeInTokens(blink::mojom::kWritingAssistanceMaxInputTokenSize + 1);

  TestCreateSummarizerClient create_summarizer_client;
  auto options = GetDefaultOptions();
  options->shared_context = kSharedContextString;
  GetAIManagerRemote()->CreateSummarizer(
      create_summarizer_client.BindNewPipeAndPassRemote(), std::move(options),
      /*monitor=*/mojo::NullRemote());

  CreateSummarizerResult result = create_summarizer_client.result().Take();
  EXPECT_FALSE(result.has_value());
  EXPECT_EQ(result.error().error,
            blink::mojom::AIManagerCreateClientError::kInitialInputTooLarge);
  EXPECT_EQ(result.error().quota_error_info->requested,
            blink::mojom::kWritingAssistanceMaxInputTokenSize + 1);
  EXPECT_EQ(result.error().quota_error_info->quota,
            blink::mojom::kWritingAssistanceMaxInputTokenSize);
}

TEST_F(AISummarizerTest, SummarizeDefault) {
  RunSimpleSummarizeTest(blink::mojom::AISummarizerType::kTLDR,
                         blink::mojom::AISummarizerFormat::kPlainText,
                         blink::mojom::AISummarizerLength::kMedium);
}

TEST_F(AISummarizerTest, SummarizeWithOptions) {
  blink::mojom::AISummarizerType types[]{
      blink::mojom::AISummarizerType::kTLDR,
      blink::mojom::AISummarizerType::kKeyPoints,
      blink::mojom::AISummarizerType::kTeaser,
      blink::mojom::AISummarizerType::kHeadline,
  };
  blink::mojom::AISummarizerFormat formats[]{
      blink::mojom::AISummarizerFormat::kPlainText,
      blink::mojom::AISummarizerFormat::kMarkDown,
  };
  blink::mojom::AISummarizerLength lengths[]{
      blink::mojom::AISummarizerLength::kShort,
      blink::mojom::AISummarizerLength::kMedium,
      blink::mojom::AISummarizerLength::kLong,
  };
  for (const auto& type : types) {
    for (const auto& format : formats) {
      for (const auto& length : lengths) {
        SCOPED_TRACE(testing::Message()
                     << type << " " << format << " " << length);
        RunSimpleSummarizeTest(type, format, length);
      }
    }
  }
}

TEST_F(AISummarizerTest, InputLimitExceededError) {
  auto summarizer_remote = GetAISummarizerRemote();

  SetSizeInTokens(blink::mojom::kWritingAssistanceMaxInputTokenSize + 1);

  AITestUtils::TestStreamingResponder responder;
  summarizer_remote->Summarize(kInputString, kContextString,
                               responder.BindRemote());
  EXPECT_FALSE(responder.WaitForCompletion());
  EXPECT_EQ(responder.error_status(),
            blink::mojom::ModelStreamingResponseStatus::kErrorInputTooLarge);
  ASSERT_EQ(responder.quota_error_info().requested,
            blink::mojom::kWritingAssistanceMaxInputTokenSize + 1);
  ASSERT_EQ(responder.quota_error_info().quota,
            blink::mojom::kWritingAssistanceMaxInputTokenSize);
}

TEST_F(AISummarizerTest, SummarizeMultipleResponse) {
  auto summarizer_remote = GetAISummarizerRemote();

  std::vector<std::string> result = {"Result ", "text"};
  SetExecuteResult(result);
  EXPECT_THAT(Summarize(*summarizer_remote, kInputString, kContextString),
              ElementsAreArray(result));
}

TEST_F(AISummarizerTest, MultipleSummarize) {
  auto summarizer_remote = GetAISummarizerRemote();

  std::vector<std::string> result = {"Result ", "text"};
  SetExecuteResult(result);
  EXPECT_THAT(Summarize(*summarizer_remote, kInputString, kContextString),
              ElementsAreArray(result));

  std::vector<std::string> result2 = {"Result ", "text ", "2"};
  SetExecuteResult(result2);
  EXPECT_THAT(Summarize(*summarizer_remote, "input string 2", "test context 2"),
              ElementsAreArray(result2));
}

TEST_F(AISummarizerTest, MeasureUsage) {
  auto options = GetDefaultOptions();
  options->shared_context = kSharedContextString;
  auto summarizer_remote = GetAISummarizerRemote(std::move(options));

  base::test::TestFuture<std::optional<uint32_t>> measure_future;
  summarizer_remote->MeasureUsage(kInputString, kContextString,
                                  measure_future.GetCallback());

  std::string context =
      AISummarizer::CombineContexts(kSharedContextString, kContextString);
  EXPECT_EQ(measure_future.Get(),
            std::string(kInputString).size() + context.size());
}

#if !BUILDFLAG(IS_ANDROID)
TEST_F(AISummarizerTest, Priority) {
  SetExecuteResult({"hi"});
  auto summarizer_remote = GetAISummarizerRemote();

  EXPECT_THAT(Summarize(*summarizer_remote, kInputString, kContextString),
              ElementsAre("hi"));

  web_contents()->WasHidden();
  EXPECT_THAT(Summarize(*summarizer_remote, kInputString, kContextString),
              ElementsAre("Priority: background", "hi"));

  web_contents()->WasShown();
  EXPECT_THAT(Summarize(*summarizer_remote, kInputString, kContextString),
              ElementsAre("hi"));
}

// Android doesn't support text safety yet. crbug.com/442914748
TEST_F(AISummarizerTest, TextSafetyInput) {
  SetSolutionConfig([&]() {
    auto solution_config = CreateSolution();
    solution_config.mutable_feature()->set_can_skip_text_safety(false);
    return solution_config;
  }());

  SetExecuteResult({"hi"});
  auto summarizer_remote = GetAISummarizerRemote();
  EXPECT_THAT(Summarize(*summarizer_remote, kInputString, kContextString),
              ElementsAre("hi"));

  AITestUtils::TestStreamingResponder responder;
  summarizer_remote->Summarize("unsafe", kContextString,
                               responder.BindRemote());
  EXPECT_FALSE(responder.WaitForCompletion());
  EXPECT_EQ(responder.error_status(),
            blink::mojom::ModelStreamingResponseStatus::kErrorFiltered);
}

TEST_F(AISummarizerTest, TextSafetyContext) {
  SetSolutionConfig([&]() {
    auto solution_config = CreateSolution();
    solution_config.mutable_feature()->set_can_skip_text_safety(false);
    return solution_config;
  }());

  SetExecuteResult({"hi"});
  auto summarizer_remote = GetAISummarizerRemote();
  EXPECT_THAT(Summarize(*summarizer_remote, kInputString, kContextString),
              ElementsAre("hi"));

  AITestUtils::TestStreamingResponder responder;
  summarizer_remote->Summarize(kInputString, "unsafe", responder.BindRemote());
  EXPECT_FALSE(responder.WaitForCompletion());
  EXPECT_EQ(responder.error_status(),
            blink::mojom::ModelStreamingResponseStatus::kErrorFiltered);
}

TEST_F(AISummarizerTest, TextSafetySharedContext) {
  SetSolutionConfig([&]() {
    auto solution_config = CreateSolution();
    solution_config.mutable_feature()->set_can_skip_text_safety(false);
    return solution_config;
  }());

  const auto options = blink::mojom::AISummarizerCreateOptions::New(
      "unsafe", blink::mojom::AISummarizerType::kTLDR,
      blink::mojom::AISummarizerFormat::kPlainText,
      blink::mojom::AISummarizerLength::kMedium,
      blink::mojom::PerformancePreference::kAuto,
      /*expected_input_languages=*/std::vector<AILanguageCodePtr>(),
      /*expected_context_languages=*/std::vector<AILanguageCodePtr>(),
      /*output_language=*/AILanguageCode::New(""));

  mojo::Remote<blink::mojom::AISummarizer> summarizer_remote =
      GetAISummarizerRemote(options.Clone());
  AITestUtils::TestStreamingResponder responder;
  summarizer_remote->Summarize(kInputString, kContextString,
                               responder.BindRemote());
  EXPECT_FALSE(responder.WaitForCompletion());
  EXPECT_EQ(responder.error_status(),
            blink::mojom::ModelStreamingResponseStatus::kErrorFiltered);
}

TEST_F(AISummarizerTest, TextSafetyOutput) {
  SetSolutionConfig([&]() {
    auto solution_config = CreateSolution();
    solution_config.mutable_feature()->set_can_skip_text_safety(false);
    solution_config.mutable_safety()
        ->mutable_partial_output_checks()
        ->set_minimum_tokens(1000);
    return solution_config;
  }());

  // Fake text safety checker looks for the string "unsafe".
  SetExecuteResult({"a", "b", "c", "d", "e", "f", "g", "unsafe", "h"});
  auto summarizer_remote = GetAISummarizerRemote();
  AITestUtils::TestStreamingResponder responder;
  summarizer_remote->Summarize(kInputString, kContextString,
                               responder.BindRemote());
  EXPECT_FALSE(responder.WaitForCompletion());
  EXPECT_EQ(responder.error_status(),
            blink::mojom::ModelStreamingResponseStatus::kErrorFiltered);
  EXPECT_TRUE(responder.responses().empty());
}

TEST_F(AISummarizerTest, TextSafetyOutputPartial) {
  SetSolutionConfig([&]() {
    auto solution_config = CreateSolution();
    solution_config.mutable_feature()->set_can_skip_text_safety(false);
    solution_config.mutable_safety()
        ->mutable_partial_output_checks()
        ->set_minimum_tokens(3);
    solution_config.mutable_safety()
        ->mutable_partial_output_checks()
        ->set_token_interval(2);
    return solution_config;
  }());

  // Fake text safety checker looks for the string "unsafe".
  SetExecuteResult({"a", "b", "c", "d", "e", "f", "g", "unsafe", "h"});
  auto summarizer_remote = GetAISummarizerRemote();
  AITestUtils::TestStreamingResponder responder;
  summarizer_remote->Summarize(kInputString, kContextString,
                               responder.BindRemote());
  EXPECT_FALSE(responder.WaitForCompletion());
  EXPECT_EQ(responder.error_status(),
            blink::mojom::ModelStreamingResponseStatus::kErrorFiltered);
  // Partial checks should still allow some output to stream.
  EXPECT_THAT(responder.responses(), ElementsAre("abc", "de", "fg"));
}

TEST_F(AISummarizerTest, ServiceCrash) {
  SetExecuteResult({"hi"});

  auto summarizer_remote = GetAISummarizerRemote();
  EXPECT_THAT(Summarize(*summarizer_remote, kInputString, kContextString),
              ElementsAre("hi"));

  AITestUtils::TestStreamingResponder responder;
  summarizer_remote->Summarize(kInputString, kContextString,
                               responder.BindRemote());
  fake_broker_->launcher().CrashService();

  EXPECT_FALSE(responder.WaitForCompletion());
  // TODO(crbug.com/494980521): Crashes should be yield kErrorSessionDestroyed.
  EXPECT_EQ(
      responder.error_status(),
      blink::mojom::ModelStreamingResponseStatus::kErrorFailedToCountTokens);

  summarizer_remote = GetAISummarizerRemote();
  EXPECT_THAT(Summarize(*summarizer_remote, kInputString, kContextString),
              ElementsAre("hi"));
}

TEST_F(AISummarizerTest, CrashRecoveryMeasureInputUsage) {
  auto options = GetDefaultOptions();
  options->shared_context = kSharedContextString;
  auto summarizer_remote = GetAISummarizerRemote(std::move(options));
  fake_broker_->launcher().CrashService();

  base::test::TestFuture<std::optional<uint32_t>> measure_future;
  summarizer_remote->MeasureUsage(kInputString, kContextString,
                                  measure_future.GetCallback());

  std::string context =
      AISummarizer::CombineContexts(kSharedContextString, kContextString);
  EXPECT_EQ(measure_future.Get(),
            std::string(kInputString).size() + context.size());
}
#endif  // !BUILDFLAG(IS_ANDROID)

TEST_F(AISummarizerTest, CanCreatePermissionsPolicyDisabled) {
  DisablePolicy(network::mojom::PermissionsPolicyFeature::kSummarizer);
  mojo::test::BadMessageObserver observer;
  GetAIManagerRemote()->CanCreateSummarizer(GetDefaultOptions(),
                                            base::DoNothing());
  EXPECT_EQ(observer.WaitForBadMessage(), "Permissions policy disabled");
}

TEST_F(AISummarizerTest, CreatePermissionsPolicyDisabled) {
  DisablePolicy(network::mojom::PermissionsPolicyFeature::kSummarizer);
  mojo::test::BadMessageObserver observer;
  TestCreateSummarizerClient create_summarizer_client;
  GetAIManagerRemote()->CreateSummarizer(
      create_summarizer_client.BindNewPipeAndPassRemote(), GetDefaultOptions(),
      /*monitor=*/mojo::NullRemote());
  EXPECT_EQ(observer.WaitForBadMessage(), "Policy or user setting disabled");
}

TEST_F(AISummarizerTest, CreateBuiltInAIAPIsEnterprisePolicyDisabled) {
  SetBuiltInAIAPIsEnterprisePolicy(false);
  base::test::TestFuture<blink::mojom::ModelAvailabilityCheckResult> future;
  GetAIManagerInterface()->CanCreateSummarizer(GetDefaultOptions(),
                                               future.GetCallback());
  EXPECT_EQ(future.Get(), blink::mojom::ModelAvailabilityCheckResult::
                              kUnavailableEnterprisePolicyDisabled);

  mojo::test::BadMessageObserver observer;
  TestCreateSummarizerClient create_summarizer_client;
  GetAIManagerRemote()->CreateSummarizer(
      create_summarizer_client.BindNewPipeAndPassRemote(), GetDefaultOptions(),
      /*monitor=*/mojo::NullRemote());
  EXPECT_EQ(observer.WaitForBadMessage(), "Policy or user setting disabled");
  SetBuiltInAIAPIsEnterprisePolicy(true);
}

TEST_F(AISummarizerTest, CreateGenAILocalEnterprisePolicyDisabled) {
  SetGenAILocalEnterprisePolicy(false);
  base::test::TestFuture<blink::mojom::ModelAvailabilityCheckResult> future;
  GetAIManagerInterface()->CanCreateSummarizer(GetDefaultOptions(),
                                               future.GetCallback());
  EXPECT_EQ(future.Get(), blink::mojom::ModelAvailabilityCheckResult::
                              kUnavailableEnterprisePolicyDisabled);

  mojo::test::BadMessageObserver observer;
  TestCreateSummarizerClient create_summarizer_client;
  GetAIManagerRemote()->CreateSummarizer(
      create_summarizer_client.BindNewPipeAndPassRemote(), GetDefaultOptions(),
      /*monitor=*/mojo::NullRemote());
  EXPECT_EQ(observer.WaitForBadMessage(), "Policy or user setting disabled");
  SetGenAILocalEnterprisePolicy(true);
}

TEST_F(AISummarizerTest, CreateOnDeviceAiUserSettingDisabled) {
  SetOnDeviceAiUserSetting(false);
  base::test::TestFuture<blink::mojom::ModelAvailabilityCheckResult> future;
  GetAIManagerInterface()->CanCreateSummarizer(GetDefaultOptions(),
                                               future.GetCallback());
  EXPECT_EQ(future.Get(), blink::mojom::ModelAvailabilityCheckResult::
                              kUnavailableFeatureNotEnabled);

  mojo::test::BadMessageObserver observer;
  TestCreateSummarizerClient create_summarizer_client;
  GetAIManagerRemote()->CreateSummarizer(
      create_summarizer_client.BindNewPipeAndPassRemote(), GetDefaultOptions(),
      /*monitor=*/mojo::NullRemote());
  EXPECT_EQ(observer.WaitForBadMessage(), "Policy or user setting disabled");
  SetOnDeviceAiUserSetting(true);
}

#if !BUILDFLAG(IS_ANDROID)
// Android doesn't support constraints yet. crbug.com/515155969
TEST_F(AISummarizerTest, DynamicConstraints) {
  SetSolutionConfig([&]() {
    auto solution_config = CreateSolution();
    optimization_guide::proto::SummarizeMetadata metadata;
    metadata.mutable_constraints()->mutable_tldr_constraint()->set_regex(
        "^TLDR:.*");

    *solution_config.mutable_feature()->mutable_feature_metadata() =
        optimization_guide::AnyWrapProto(metadata);
    return solution_config;
  }());

  SetExecuteResult({"TLDR: Result text"});

  auto options = GetDefaultOptions();
  options->type = blink::mojom::AISummarizerType::kTLDR;
  mojo::Remote<blink::mojom::AISummarizer> summarizer_remote =
      GetAISummarizerRemote(std::move(options));

  EXPECT_THAT(
      Summarize(*summarizer_remote, kInputString, kContextString),
      ElementsAreArray({"Hint: constrained_decoding ",
                        "Constraint: regex ^TLDR:.*", "TLDR: Result text"}));
}

TEST_F(AISummarizerTest, NoConstraints) {
  SetSolutionConfig([&]() {
    auto solution_config = CreateSolution();
    optimization_guide::proto::SummarizeMetadata metadata;

    *solution_config.mutable_feature()->mutable_feature_metadata() =
        optimization_guide::AnyWrapProto(metadata);
    return solution_config;
  }());

  SetExecuteResult({"Result text"});

  mojo::Remote<blink::mojom::AISummarizer> summarizer_remote =
      GetAISummarizerRemote(GetDefaultOptions());

  EXPECT_THAT(Summarize(*summarizer_remote, kInputString, kContextString),
              ElementsAreArray({"Result text"}));
}

TEST_F(AISummarizerTest, NoMetadata) {
  SetSolutionConfig([&]() {
    return CreateSolution();
  }());

  SetExecuteResult({"Result text"});

  mojo::Remote<blink::mojom::AISummarizer> summarizer_remote =
      GetAISummarizerRemote(GetDefaultOptions());

  EXPECT_THAT(Summarize(*summarizer_remote, kInputString, kContextString),
              ElementsAreArray({"Result text"}));
}

class AISummarizerWithFeatureConfigTest : public AISummarizerTest {
 public:
  void SetupBroker() override {
    proto::SummarizerFeatureConfig summarizer_cfg;
    summarizer_cfg.set_default_use_case("summarizer_api");
    (*summarizer_cfg.mutable_preference_use_cases())["speed"] =
        "summarizer_small_expert_model";
    (*summarizer_cfg.mutable_preference_use_cases())["capability"] =
        "summarizer_api";
    (*summarizer_cfg.mutable_experimental_use_cases())["v4"] =
        "summarizer_gemma4";

    // Explicit BaseModelRecipeArgs and empty FakeBaseModelAsset::Content are
    // needed: ScenarioBuilder::AddBaseModel(name) defaults to 100 max_tokens
    // and non-empty cache weights (1015, 1016, 1017), which causes
    // FakeOnDeviceModel to emit dummy cache weight response chunks.
    constexpr uint32_t kDefaultMaxTokens = 8096;
    proto::SolutionConfig default_solution = CreateSolution();

    fake_broker_ = std::make_unique<optimization_guide::FakeManifestBroker>();
    optimization_guide::ScenarioBuilder(fake_broker_->component_state())
        .AddBaseModel(
            "base",
            optimization_guide::BaseModelRecipeArgs(
                proto::BaseModelRecipe::BACKEND_TYPE_GPU,
                proto::BaseModelRecipe::PERFORMANCE_HINT_HIGHEST_QUALITY,
                {}, kDefaultMaxTokens),
            optimization_guide::FakeBaseModelAsset::Content{}, "1.0.0.0")
        .AddBaseModel(
            "gemma4_base",
            optimization_guide::BaseModelRecipeArgs(
                proto::BaseModelRecipe::BACKEND_TYPE_GPU,
                proto::BaseModelRecipe::PERFORMANCE_HINT_HIGHEST_QUALITY,
                {}, kDefaultMaxTokens),
            optimization_guide::FakeBaseModelAsset::Content{}, "1.0.0.0")
        .AddBaseModel(
            "small_expert_base",
            optimization_guide::BaseModelRecipeArgs(
                proto::BaseModelRecipe::BACKEND_TYPE_CPU,
                proto::BaseModelRecipe::PERFORMANCE_HINT_UNSPECIFIED,
                {}, kDefaultMaxTokens),
            optimization_guide::FakeBaseModelAsset::Content{}, "1.0.0.0")
        .AddSafetyModel("safety")
        .AddSafeSolution("summarizer_api", "base", "safety", default_solution)
        .AddSafeSolution("summarizer_small_expert_model", "small_expert_base",
                         "safety", default_solution)
        .AddSafeSolution("summarizer_gemma4", "gemma4_base", "safety",
                         default_solution)
        .SetFeatureConfig("summarizer_api",
                          optimization_guide::AnyWrapProto(summarizer_cfg))
        .Finish();

    fake_broker_->settings().performance_class =
        on_device_model::mojom::PerformanceClass::kHigh;
    fake_broker_->Startup();
  }
};

TEST_F(AISummarizerWithFeatureConfigTest,
       CanCreateAndCreateWithManifestSpeedPreference) {
  auto options = GetDefaultOptions();
  options->preference = blink::mojom::PerformancePreference::kSpeed;
  options->output_language = blink::mojom::AILanguageCode::New("en");

  fake_broker_->client().RequestAssetsFor(
      "summarizer_small_expert_model");
  ASSERT_TRUE(base::test::RunUntil([&] {
    base::test::TestFuture<blink::mojom::ModelAvailabilityCheckResult> future;
    GetAIManagerInterface()->CanCreateSummarizer(options.Clone(),
                                                 future.GetCallback());
    return future.Get() ==
           blink::mojom::ModelAvailabilityCheckResult::kAvailable;
  }));

  TestCreateSummarizerClient summarizer_client;
  GetAIManagerRemote()->CreateSummarizer(
      summarizer_client.BindNewPipeAndPassRemote(), std::move(options),
      /*monitor=*/mojo::NullRemote());

  auto result = summarizer_client.result().Take();
  EXPECT_TRUE(result.has_value());
}

TEST_F(AISummarizerWithFeatureConfigTest,
       CanCreateSummarizerWithSpeedPreferenceDownloadable) {
  auto options = GetDefaultOptions();
  options->preference = blink::mojom::PerformancePreference::kSpeed;
  options->output_language = blink::mojom::AILanguageCode::New("en");

  base::test::TestFuture<blink::mojom::ModelAvailabilityCheckResult> future;
  GetAIManagerInterface()->CanCreateSummarizer(options.Clone(),
                                               future.GetCallback());
  EXPECT_EQ(future.Get(),
            blink::mojom::ModelAvailabilityCheckResult::kDownloadable);
}

TEST_F(AISummarizerWithFeatureConfigTest,
       CanCreateAndCreateWithManifestAutoPreference) {
  // Even if gemma4 assets are available, it shouldn't use it by default.
  // Since summarizer_api is the default use case, and it's not downloaded yet,
  // it should return kDownloadable.
  fake_broker_->client().RequestAssetsFor("summarizer_gemma4");

  auto options = GetDefaultOptions();

  base::test::TestFuture<blink::mojom::ModelAvailabilityCheckResult>
      gemma4_future;
  GetAIManagerInterface()->CanCreateSummarizer(options.Clone(),
                                               gemma4_future.GetCallback());
  EXPECT_EQ(gemma4_future.Get(),
            blink::mojom::ModelAvailabilityCheckResult::kDownloadable);

  // Now request assets for summarizer_api, and it should return kAvailable.
  fake_broker_->client().RequestAssetsFor("summarizer_api");
  ASSERT_TRUE(base::test::RunUntil([&] {
    base::test::TestFuture<blink::mojom::ModelAvailabilityCheckResult> future;
    GetAIManagerInterface()->CanCreateSummarizer(options.Clone(),
                                                 future.GetCallback());
    return future.Get() ==
           blink::mojom::ModelAvailabilityCheckResult::kAvailable;
  }));

  TestCreateSummarizerClient summarizer_client;
  GetAIManagerRemote()->CreateSummarizer(
      summarizer_client.BindNewPipeAndPassRemote(), std::move(options),
      /*monitor=*/mojo::NullRemote());

  auto result = summarizer_client.result().Take();
  EXPECT_TRUE(result.has_value());
}

// TODO(crbug.com/543507245): Flaky.
TEST_F(AISummarizerWithFeatureConfigTest,
       DISABLED_CanCreateAndCreateWithManifestCapabilityPreference) {
  fake_broker_->client().RequestAssetsFor("summarizer_api");
  auto options = GetDefaultOptions();
  options->preference = blink::mojom::PerformancePreference::kCapability;
  options->output_language = blink::mojom::AILanguageCode::New("en");

  ASSERT_TRUE(base::test::RunUntil([&] {
    base::test::TestFuture<blink::mojom::ModelAvailabilityCheckResult> future;
    GetAIManagerInterface()->CanCreateSummarizer(options.Clone(),
                                                 future.GetCallback());
    return future.Get() ==
           blink::mojom::ModelAvailabilityCheckResult::kAvailable;
  }));

  TestCreateSummarizerClient summarizer_client;
  GetAIManagerRemote()->CreateSummarizer(
      summarizer_client.BindNewPipeAndPassRemote(), std::move(options),
      /*monitor=*/mojo::NullRemote());

  auto result = summarizer_client.result().Take();
  EXPECT_TRUE(result.has_value());
}

TEST_F(AISummarizerWithFeatureConfigTest,
       CanCreateIncompatibleOptionsForSpeedPreference) {
  // Incompatible because speed preference requires kShort or kMedium length,
  // but we use kLong.
  auto options = GetDefaultOptions();
  options->preference = blink::mojom::PerformancePreference::kSpeed;
  options->length = blink::mojom::AISummarizerLength::kLong;

  base::MockCallback<AIManager::CanCreateSummarizerCallback> callback;
  EXPECT_CALL(callback, Run(blink::mojom::ModelAvailabilityCheckResult::
                                kUnavailableIncompatiblePreferenceOptions));

  GetAIManagerInterface()->CanCreateSummarizer(std::move(options),
                                               callback.Get());
}

TEST_F(AISummarizerWithFeatureConfigTest,
       CanCreateIncompatibleFormatForSpeedPreference) {
  auto options = GetDefaultOptions();
  options->preference = blink::mojom::PerformancePreference::kSpeed;
  options->format = blink::mojom::AISummarizerFormat::kMarkDown;

  base::MockCallback<AIManager::CanCreateSummarizerCallback> callback;
  EXPECT_CALL(callback, Run(blink::mojom::ModelAvailabilityCheckResult::
                                kUnavailableIncompatiblePreferenceOptions));

  GetAIManagerInterface()->CanCreateSummarizer(std::move(options),
                                               callback.Get());
}

TEST_F(AISummarizerWithFeatureConfigTest,
       CanCreateIncompatibleTypeForSpeedPreference) {
  auto options = GetDefaultOptions();
  options->preference = blink::mojom::PerformancePreference::kSpeed;
  options->type = blink::mojom::AISummarizerType::kTeaser;

  base::MockCallback<AIManager::CanCreateSummarizerCallback> callback;
  EXPECT_CALL(callback, Run(blink::mojom::ModelAvailabilityCheckResult::
                                kUnavailableIncompatiblePreferenceOptions));

  GetAIManagerInterface()->CanCreateSummarizer(std::move(options),
                                               callback.Get());
}

TEST_F(AISummarizerWithFeatureConfigTest,
       CanCreateIncompatibleOutputLanguageForSpeedPreference) {
  auto options = GetDefaultOptions();
  options->preference = blink::mojom::PerformancePreference::kSpeed;
  options->output_language = blink::mojom::AILanguageCode::New("fr");

  base::MockCallback<AIManager::CanCreateSummarizerCallback> callback;
  EXPECT_CALL(callback, Run(blink::mojom::ModelAvailabilityCheckResult::
                                kUnavailableIncompatiblePreferenceOptions));

  GetAIManagerInterface()->CanCreateSummarizer(std::move(options),
                                               callback.Get());
}

TEST_F(AISummarizerWithFeatureConfigTest,
       CanCreateIncompatibleInputLanguageForSpeedPreference) {
  auto options = GetDefaultOptions();
  options->preference = blink::mojom::PerformancePreference::kSpeed;
  options->expected_input_languages = AITestUtils::ToMojoLanguageCodes({"fr"});

  base::MockCallback<AIManager::CanCreateSummarizerCallback> callback;
  EXPECT_CALL(callback, Run(blink::mojom::ModelAvailabilityCheckResult::
                                kUnavailableIncompatiblePreferenceOptions));

  GetAIManagerInterface()->CanCreateSummarizer(std::move(options),
                                               callback.Get());
}

TEST_F(AISummarizerWithFeatureConfigTest,
       CanCreateIncompatibleContextLanguageForSpeedPreference) {
  auto options = GetDefaultOptions();
  options->preference = blink::mojom::PerformancePreference::kSpeed;
  options->expected_context_languages =
      AITestUtils::ToMojoLanguageCodes({"fr"});

  base::MockCallback<AIManager::CanCreateSummarizerCallback> callback;
  EXPECT_CALL(callback, Run(blink::mojom::ModelAvailabilityCheckResult::
                                kUnavailableIncompatiblePreferenceOptions));

  GetAIManagerInterface()->CanCreateSummarizer(std::move(options),
                                               callback.Get());
}

TEST_F(AISummarizerWithFeatureConfigTest,
       CanCreateIncompatibleSharedContextForSpeedPreference) {
  auto options = GetDefaultOptions();
  options->preference = blink::mojom::PerformancePreference::kSpeed;
  options->shared_context = "non-empty context";

  base::MockCallback<AIManager::CanCreateSummarizerCallback> callback;
  EXPECT_CALL(callback, Run(blink::mojom::ModelAvailabilityCheckResult::
                                kUnavailableIncompatiblePreferenceOptions));

  GetAIManagerInterface()->CanCreateSummarizer(std::move(options),
                                               callback.Get());
}

TEST_F(AISummarizerWithFeatureConfigTest,
       CanCreateSummarizerSpeedPreferenceFeatureDisabled) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(
      blink::features::kAISummarizationPerformancePreference);

  auto options = GetDefaultOptions();
  options->preference = blink::mojom::PerformancePreference::kSpeed;

  mojo::test::BadMessageObserver observer;
  GetAIManagerRemote()->CanCreateSummarizer(std::move(options),
                                            base::DoNothing());
  EXPECT_EQ(observer.WaitForBadMessage(),
            "Speed preference requested but feature disabled");
}

TEST_F(AISummarizerWithFeatureConfigTest,
       CanCreateSummarizerNoServiceWithManifestBroker) {
  SetupNullOptimizationGuideKeyedService();

  base::test::TestFuture<blink::mojom::ModelAvailabilityCheckResult> future;
  GetAIManagerInterface()->CanCreateSummarizer(GetDefaultOptions(),
                                               future.GetCallback());
  EXPECT_EQ(future.Get(), blink::mojom::ModelAvailabilityCheckResult::
                              kUnavailableServiceNotRunning);
}

TEST_F(AISummarizerWithFeatureConfigTest,
       CreateIncompatibleOptionsForSpeedPreference) {
  // Incompatible because speed preference requires kShort or kMedium length,
  // but we use kLong.
  auto options = GetDefaultOptions();
  options->preference = blink::mojom::PerformancePreference::kSpeed;
  options->length = blink::mojom::AISummarizerLength::kLong;

  TestCreateSummarizerClient create_summarizer_client;
  mojo::test::BadMessageObserver observer;
  GetAIManagerRemote()->CreateSummarizer(
      create_summarizer_client.BindNewPipeAndPassRemote(), std::move(options),
      /*monitor=*/mojo::NullRemote());

  EXPECT_EQ(observer.WaitForBadMessage(),
            "Incompatible speed preference options");
}

TEST_F(AISummarizerWithFeatureConfigTest,
       SummarizeWithSpeedPreferenceAndContextFails) {
  auto options = GetDefaultOptions();
  options->preference = blink::mojom::PerformancePreference::kSpeed;
  options->output_language = blink::mojom::AILanguageCode::New("en");

  fake_broker_->client().RequestAssetsFor(
      "summarizer_small_expert_model");

  TestCreateSummarizerClient summarizer_client;
  GetAIManagerRemote()->CreateSummarizer(
      summarizer_client.BindNewPipeAndPassRemote(), std::move(options),
      /*monitor=*/mojo::NullRemote());

  auto result = summarizer_client.result().Take();
  ASSERT_TRUE(result.has_value());

  mojo::Remote<blink::mojom::AISummarizer> summarizer_remote(
      std::move(result.value()));

  AITestUtils::TestStreamingResponder responder;
  summarizer_remote->Summarize("input", "non-empty context",
                               responder.BindRemote());
  EXPECT_FALSE(responder.WaitForCompletion());
  EXPECT_EQ(responder.error_status(),
            blink::mojom::ModelStreamingResponseStatus::kErrorInvalidRequest);
}

TEST_F(AISummarizerWithFeatureConfigTest,
       InputLimitExceededErrorSpeedPreference) {
  auto options = GetDefaultOptions();
  options->preference = blink::mojom::PerformancePreference::kSpeed;
  options->output_language = blink::mojom::AILanguageCode::New("en");

  fake_broker_->client().RequestAssetsFor(
      "summarizer_small_expert_model");

  TestCreateSummarizerClient summarizer_client;
  GetAIManagerRemote()->CreateSummarizer(
      summarizer_client.BindNewPipeAndPassRemote(), std::move(options),
      /*monitor=*/mojo::NullRemote());

  auto result = summarizer_client.result().Take();
  ASSERT_TRUE(result.has_value());

  mojo::Remote<blink::mojom::AISummarizer> summarizer_remote(
      std::move(result.value()));

  fake_broker_->settings().set_size_in_tokens(
      blink::mojom::kTinyModelMaxInputTokenSize + 1);

  AITestUtils::TestStreamingResponder responder;
  summarizer_remote->Summarize("input", "", responder.BindRemote());
  EXPECT_FALSE(responder.WaitForCompletion());
  EXPECT_EQ(responder.error_status(),
            blink::mojom::ModelStreamingResponseStatus::kErrorInputTooLarge);
  ASSERT_EQ(responder.quota_error_info().requested,
            blink::mojom::kTinyModelMaxInputTokenSize + 1);
  ASSERT_EQ(responder.quota_error_info().quota,
            blink::mojom::kTinyModelMaxInputTokenSize);
}

TEST_F(AISummarizerWithFeatureConfigTest,
       CanCreateAndCreateWithManifestGemma4) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeatureWithParameters(
      kAIApiFoundationalModel, {{"model_version", "v4"}});

  fake_broker_->client().RequestAssetsFor("summarizer_gemma4");
  ASSERT_TRUE(base::test::RunUntil([&] {
    base::test::TestFuture<blink::mojom::ModelAvailabilityCheckResult> future;
    ai_manager_->CanCreateSummarizer(GetDefaultOptions(), future.GetCallback());
    return future.Get() ==
           blink::mojom::ModelAvailabilityCheckResult::kAvailable;
  }));

  // Verify CreateSummarizer can retrieve the model successfully.
  TestCreateSummarizerClient create_summarizer_client;
  GetAIManagerRemote()->CreateSummarizer(
      create_summarizer_client.BindNewPipeAndPassRemote(), GetDefaultOptions(),
      /*monitor=*/mojo::NullRemote());

  auto result = create_summarizer_client.result().Take();
  EXPECT_TRUE(result.has_value());
}

TEST_F(AISummarizerWithFeatureConfigTest,
       CanCreateBeforeDownloadGemma4) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeatureWithParameters(
      kAIApiFoundationalModel, {{"model_version", "v4"}});

  // Assets are requested for summarizer_api, but since gemma4 is the configured
  // model_version, we should get kDownloadable for gemma4.
  fake_broker_->client().RequestAssetsFor("summarizer_api");

  base::test::TestFuture<blink::mojom::ModelAvailabilityCheckResult> future;
  ai_manager_->CanCreateSummarizer(GetDefaultOptions(), future.GetCallback());
  EXPECT_EQ(future.Get(),
            blink::mojom::ModelAvailabilityCheckResult::kDownloadable);
}
#endif  // !BUILDFLAG(IS_ANDROID)

}  // namespace
