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

#include <memory>

#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "chrome/browser/background_fetch/background_fetch_delegate_impl.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/offline_items_collection/offline_content_aggregator_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_key.h"
#include "chrome/test/base/android/android_browser_test.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "components/background_fetch/job_details.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/download/public/background_service/features.h"
#include "components/offline_items_collection/core/offline_content_aggregator.h"
#include "components/offline_items_collection/core/offline_content_provider.h"
#include "components/offline_items_collection/core/offline_item.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "services/network/public/cpp/features.h"
#include "third_party/blink/public/common/features.h"
#include "url/origin.h"

using offline_items_collection::ContentId;
using offline_items_collection::OfflineContentAggregator;
using offline_items_collection::OfflineContentProvider;
using offline_items_collection::OfflineItem;
using offline_items_collection::OfflineItemState;

namespace {

const char kHelperPage[] = "/background_fetch/background_fetch.html";

class TestOfflineContentProviderObserver
    : public OfflineContentProvider::Observer {
 public:
  TestOfflineContentProviderObserver() = default;
  ~TestOfflineContentProviderObserver() override = default;

  void set_delegate(BackgroundFetchDelegateImpl* delegate) {
    delegate_ = delegate;
  }

  void WaitForState(OfflineItemState state) {
    if (latest_item_.state == state ||
        latest_item_.state == OfflineItemState::COMPLETE ||
        latest_item_.state == OfflineItemState::CANCELLED ||
        latest_item_.state == OfflineItemState::FAILED) {
      return;
    }
    target_state_ = state;
    run_loop_ = std::make_unique<base::RunLoop>();
    run_loop_->Run();
  }

  void OnItemsAdded(
      const OfflineContentProvider::OfflineItemList& items) override {
    for (const auto& item : items) {
      if (item.id.name_space == "background_fetch") {
        latest_item_ = item;
        if (delegate_ && latest_item_.state != OfflineItemState::PAUSED) {
          delegate_->PauseDownload(latest_item_.id);
        }
        CheckState();
      }
    }
  }

  void OnItemRemoved(const ContentId& id) override {}

  void OnItemUpdated(const OfflineItem& item,
                     const std::optional<offline_items_collection::UpdateDelta>&
                         update_delta) override {
    if (item.id.name_space == "background_fetch") {
      latest_item_ = item;
      CheckState();
    }
  }

  void OnContentProviderGoingDown() override {}

  const OfflineItem& latest_item() const { return latest_item_; }

 private:
  void CheckState() {
    if (run_loop_ && target_state_.has_value()) {
      if (latest_item_.state == target_state_.value() ||
          latest_item_.state == OfflineItemState::COMPLETE ||
          latest_item_.state == OfflineItemState::CANCELLED ||
          latest_item_.state == OfflineItemState::FAILED) {
        run_loop_->Quit();
      }
    }
  }

  std::unique_ptr<base::RunLoop> run_loop_;
  std::optional<OfflineItemState> target_state_;
  OfflineItem latest_item_;
  raw_ptr<BackgroundFetchDelegateImpl> delegate_ = nullptr;
};

}  // namespace

class BackgroundFetchAndroidBrowserTest : public AndroidBrowserTest {
 public:
  BackgroundFetchAndroidBrowserTest() = default;
  ~BackgroundFetchAndroidBrowserTest() override = default;

  void SetUpOnMainThread() override {
    https_server_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);
    https_server_->RegisterRequestHandler(
        base::BindRepeating(&BackgroundFetchAndroidBrowserTest::HandleRequest,
                            base::Unretained(this)));
    https_server_->AddDefaultHandlers(GetChromeTestDataDir());
    ASSERT_TRUE(https_server_->Start());

    OfflineContentAggregatorFactory::GetInstance()
        ->GetForKey(GetProfile()->GetProfileKey())
        ->AddObserver(&offline_observer_);

    delegate_ = static_cast<BackgroundFetchDelegateImpl*>(
        GetProfile()->GetBackgroundFetchDelegate());

    // Load the helper page.
    content::WebContents* web_contents =
        chrome_test_utils::GetActiveWebContents(this);
    ASSERT_TRUE(content::NavigateToURL(web_contents,
                                       https_server_->GetURL(kHelperPage)));

    // Register the Service Worker if needed.
    bool sw_registered =
        RunScript(
            "navigator.serviceWorker.getRegistration().then(reg => !!reg)")
            .ExtractBool();
    if (!sw_registered) {
      ASSERT_EQ("ok - service worker registered",
                RunScript("RegisterServiceWorker()"));
    }
  }

  void TearDownOnMainThread() override {
    OfflineContentAggregatorFactory::GetInstance()
        ->GetForKey(GetProfile()->GetProfileKey())
        ->RemoveObserver(&offline_observer_);
  }

 protected:
  Profile* GetProfile() {
    return Profile::FromBrowserContext(
        chrome_test_utils::GetActiveWebContents(this)->GetBrowserContext());
  }

  content::EvalJsResult RunScript(const std::string& script) {
    return content::EvalJs(
        chrome_test_utils::GetActiveWebContents(this)->GetPrimaryMainFrame(),
        script);
  }

  std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
      const net::test_server::HttpRequest& request) {
    if (request.GetURL().GetPath() == "/background_fetch/upload") {
      auto response = std::make_unique<net::test_server::BasicHttpResponse>();
      response->set_code(net::HTTP_OK);
      return response;
    }
    if (request.GetURL().GetPath() == "/hung") {
      return std::make_unique<net::test_server::HungResponse>();
    }
    return nullptr;
  }

  std::unique_ptr<net::EmbeddedTestServer> https_server_;
  TestOfflineContentProviderObserver offline_observer_;
  raw_ptr<BackgroundFetchDelegateImpl> delegate_ = nullptr;
};

IN_PROC_BROWSER_TEST_F(BackgroundFetchAndroidBrowserTest,
                       PRE_FetchCanBePausedRestartedAndResumed) {
  // Pass the delegate so it can be paused as soon as it's added.
  offline_observer_.set_delegate(delegate_);

  // Start the background fetch.
  std::string script =
      "navigator.serviceWorker.ready.then(reg => { "
      "  return reg.backgroundFetch.fetch('bg-fetch-id', '/hung');"
      "}).then(() => 'ok');";
  ASSERT_EQ("ok", RunScript(script));

  // Wait until the offline item is in a paused state.
  offline_observer_.WaitForState(OfflineItemState::PAUSED);

  // Give some time for DB to flush.
  content::RunAllTasksUntilIdle();
}

// Verifies that a Background Fetch which was paused in a previous browser
// session (setup in PRE_FetchCanBePausedRestartedAndResumed) is correctly
// loaded upon browser restart. It then resumes the fetch and verifies it
// completes successfully.
IN_PROC_BROWSER_TEST_F(BackgroundFetchAndroidBrowserTest,
                       FetchCanBePausedRestartedAndResumed) {
  // Ensure the BackgroundFetchContext is initialized.
  GetProfile()->GetDefaultStoragePartition();

  // First, verify the items are loaded by the aggregator.
  OfflineContentAggregator* aggregator =
      OfflineContentAggregatorFactory::GetInstance()->GetForKey(
          GetProfile()->GetProfileKey());

  std::vector<OfflineItem> items;
  {
    base::RunLoop run_loop;
    aggregator->GetAllItems(base::BindOnce(
        [](base::OnceClosure quit_closure, std::vector<OfflineItem>* out_items,
           const std::vector<OfflineItem>& items) {
          *out_items = items;
          std::move(quit_closure).Run();
        },
        run_loop.QuitClosure(), &items));
    run_loop.Run();
  }

  bool found = false;
  OfflineItem bg_fetch_item;
  for (const auto& item : items) {
    if (item.id.name_space == "background_fetch") {
      found = true;
      bg_fetch_item = item;
      break;
    }
  }

  // Wait for the item to be added (when not found) and in the PAUSED state.
  if (!found || bg_fetch_item.state != OfflineItemState::PAUSED) {
    offline_observer_.WaitForState(OfflineItemState::PAUSED);
    bg_fetch_item = offline_observer_.latest_item();
  }

  // Resume the download.
  if (bg_fetch_item.state == OfflineItemState::PAUSED) {
    delegate_->ResumeDownload(bg_fetch_item.id);
  }

  // Wait until it completes or fails.
  if (offline_observer_.latest_item().state != OfflineItemState::COMPLETE &&
      offline_observer_.latest_item().state != OfflineItemState::CANCELLED &&
      offline_observer_.latest_item().state != OfflineItemState::FAILED) {
    offline_observer_.WaitForState(OfflineItemState::COMPLETE);
  }
}

// TODO: crbug.com/455486148 - Add LNA-specific resumption tests.
