// 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 "components/update_client/op_download.h"

#include <stdint.h>

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

#include "base/check_deref.h"
#include "base/files/file_util.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/types/expected.h"
#include "base/values.h"
#include "components/prefs/testing_pref_service.h"
#include "components/update_client/crx_downloader.h"
#include "components/update_client/crx_downloader_factory.h"
#include "components/update_client/persisted_data.h"
#include "components/update_client/test_configurator.h"
#include "components/update_client/update_engine.h"
#include "crypto/sha2.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace update_client {

namespace {

class FakeDownloader : public CrxDownloader {
 public:
  FakeDownloader(const base::FilePath& dest,
                 base::expected<std::string, int> result,
                 const CrxDownloader::DownloadMetrics& metrics)
      : CrxDownloader(nullptr),
        dest_(dest),
        result_(result),
        metrics_(metrics) {}

  base::OnceClosure DoStartDownload(const GURL& url) override {
    if (result_.has_value()) {
      base::WriteFile(dest_, result_.value());
      OnDownloadComplete(true, {.response = dest_}, metrics_);
    } else {
      OnDownloadComplete(true, {.error = result_.error()}, metrics_);
    }
    return base::DoNothing();
  }

 protected:
  ~FakeDownloader() override = default;

 private:
  const base::FilePath dest_;
  const base::expected<std::string, int> result_;
  const CrxDownloader::DownloadMetrics metrics_;
};

class FakeFactory : public CrxDownloaderFactory {
 public:
  FakeFactory(const base::FilePath dest,
              base::expected<std::string, int> result,
              const CrxDownloader::DownloadMetrics& metrics)
      : dest_(dest), result_(result), metrics_(metrics) {}

  scoped_refptr<CrxDownloader> MakeCrxDownloader(
      const std::string& prod_id,
      bool background_download_enabled) const override {
    return base::MakeRefCounted<FakeDownloader>(dest_, result_, metrics_);
  }

 protected:
  ~FakeFactory() override = default;

 private:
  const base::FilePath dest_;
  const base::expected<std::string, int> result_;
  const CrxDownloader::DownloadMetrics metrics_;
};

}  // namespace

class OpDownloadTest : public testing::Test {
 public:
  OpDownloadTest() { RegisterPersistedDataPrefs(pref_->registry()); }

  // Overrides from testing::Test.
  void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }

  scoped_refptr<TestConfigurator> MakeConfigurator(
      base::expected<std::string, int> result,
      const CrxDownloader::DownloadMetrics& metrics) {
    scoped_refptr<TestConfigurator> config =
        base::MakeRefCounted<TestConfigurator>(pref_.get());
    config->SetCrxDownloaderFactory(base::MakeRefCounted<FakeFactory>(
        temp_dir_.GetPath().Append(FILE_PATH_LITERAL("OpDownloadTest_File")),
        result, metrics));
    return config;
  }

  CrxDownloader::ProgressCallback MakeProgressCallback() {
    return base::DoNothing();
  }

  base::RepeatingCallback<void(base::DictValue)> MakePingCallback() {
    return base::BindLambdaForTesting(
        [&](base::DictValue ping) { pings_.push_back(std::move(ping)); });
  }

  base::OnceCallback<void(base::expected<base::FilePath, CategorizedError>)>
  MakeDoneCallback() {
    return base::BindLambdaForTesting(
        [&](base::expected<base::FilePath, CategorizedError> outcome) {
          outcome_ = outcome;
          runloop_.Quit();
        });
  }

  void Download(scoped_refptr<Configurator> config,
                int64_t length,
                const std::string& hash) {
    DownloadOperation(config, "appid",
                      /*is_foreground=*/false, {GURL("http://localhost:111")},
                      length, hash, MakePingCallback(), base::DoNothing(),
                      MakeProgressCallback(), {}, MakeDoneCallback());
    runloop_.Run();
  }

  base::test::TaskEnvironment task_environment_;
  std::unique_ptr<TestingPrefServiceSimple> pref_ =
      std::make_unique<TestingPrefServiceSimple>();
  base::ScopedTempDir temp_dir_;
  base::RunLoop runloop_;

  std::vector<base::DictValue> pings_;
  base::expected<base::FilePath, CategorizedError> outcome_;
};

TEST_F(OpDownloadTest, DownloadSuccess) {
  const std::string data = "data";
  Download(
      MakeConfigurator(
          data, {.url = GURL("http://test"),
                 .downloader =
                     CrxDownloader::DownloadMetrics::Downloader::kUrlFetcher,
                 .error = 0,
                 .extra_code1 = 34,
                 .downloaded_bytes = 56,
                 .total_bytes = 78,
                 .download_time_ms = 90}),
      data.length(), base::HexEncode(crypto::SHA256HashString(data)));
  EXPECT_TRUE(outcome_.has_value());
  ASSERT_EQ(pings_.size(), 1u);
  EXPECT_EQ(pings_[0].FindInt("eventtype"), 14);
  EXPECT_EQ(pings_[0].FindInt("eventresult"), 1);
  EXPECT_EQ(pings_[0].Find("errorcode"), nullptr);
  EXPECT_EQ(pings_[0].FindInt("extracode1"), 34);
  EXPECT_EQ(pings_[0].FindDouble("downloaded"), 56);
  EXPECT_EQ(pings_[0].FindDouble("total"), 78);
  EXPECT_EQ(pings_[0].FindDouble("download_time_ms"), 90);
  EXPECT_EQ(CHECK_DEREF(pings_[0].FindString("downloader")), "direct");
}

TEST_F(OpDownloadTest, DownloadFailure) {
  const std::string data = "data";
  Download(MakeConfigurator(
               base::unexpected(404),
               {.url = GURL("http://test"),
                .downloader =
                    CrxDownloader::DownloadMetrics::Downloader::kUrlFetcher,
                .error = 404,
                .extra_code1 = 34,
                .downloaded_bytes = 56,
                .total_bytes = 78,
                .download_time_ms = 90}),
           data.length(), base::HexEncode(crypto::SHA256HashString(data)));
  EXPECT_FALSE(outcome_.has_value());
  ASSERT_EQ(pings_.size(), 1u);
  EXPECT_EQ(pings_[0].FindInt("eventtype"), 14);
  EXPECT_EQ(pings_[0].FindInt("eventresult"), 0);
  EXPECT_EQ(pings_[0].FindInt("errorcode"), 404);
  EXPECT_EQ(pings_[0].FindInt("extracode1"), 34);
  EXPECT_EQ(pings_[0].FindDouble("downloaded"), 56);
  EXPECT_EQ(pings_[0].FindDouble("total"), 78);
  EXPECT_EQ(pings_[0].FindDouble("download_time_ms"), 90);
  EXPECT_EQ(CHECK_DEREF(pings_[0].FindString("downloader")), "direct");
}

}  // namespace update_client
