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

#import "ios/web/public/browsing_data/system_cookie_store_util.h"

#import <WebKit/WebKit.h>

#import <optional>

#import "base/functional/bind.h"
#import "base/functional/callback_helpers.h"
#import "base/run_loop.h"
#import "ios/net/cookies/cookie_store_ios_test_util.h"
#import "ios/net/cookies/system_cookie_store.h"
#import "ios/web/net/cookies/wk_cookie_util.h"
#import "ios/web/public/test/web_task_environment.h"
#import "ios/web/public/test/web_test.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"

namespace web {

namespace {

// Returns a callback that store its parameter to `output`. `output` must
// outlive the returned callback invocation.
template <typename T>
base::OnceCallback<void(T)> CaptureOutput(T* output) {
  return base::BindOnce([](T* output, T param) { *output = param; }, output);
}

// Checks if `system_cookie` for `url` is present in `cookies`.
bool IsCookieSetInCookies(NSHTTPCookie* cookie,
                          NSURL* url,
                          NSArray<NSHTTPCookie*>* cookies) {
  for (NSHTTPCookie* item in cookies) {
    if ([item.path isEqualToString:url.path] &&
        [item.domain isEqualToString:url.host] &&
        [item.name isEqualToString:cookie.name]) {
      return [item.value isEqualToString:cookie.value];
    }
  }
  return false;
}

// Checks if `system_cookie` was set in WKHTTPCookieStore `cookie_store`.
bool IsCookieSetInWKCookieStore(NSHTTPCookie* system_cookie,
                                NSURL* url,
                                WKHTTPCookieStore* cookie_store) {
  bool present = false;

  base::RunLoop run_loop;
  base::OnceCallback<void(NSArray<NSHTTPCookie*>*)> callback =
      base::BindOnce(&IsCookieSetInCookies, system_cookie, url)
          .Then(CaptureOutput(&present))
          .Then(run_loop.QuitClosure());
  [cookie_store getAllCookies:base::CallbackToBlock(std::move(callback))];
  run_loop.Run();

  return present;
}

// Sets `cookie` in SystemCookieStore `store`, returning whether the operation
// was a success or not (i.e. timeout).
bool SetCookieInCookieStore(NSHTTPCookie* cookie,
                            net::SystemCookieStore* store) {
  bool success = false;

  base::RunLoop run_loop;
  store->SetCookieAsync(cookie, /*optional_creation_time=*/std::nullopt,
                        base::ReturnValueOnce(true)
                            .Then(CaptureOutput(&success))
                            .Then(run_loop.QuitClosure()));
  run_loop.Run();

  return success;
}

// Clears all cookies in SystemCookieStore `store`, returning whether the
// operation was a success or not (i.e. timeout).
bool ClearCookiesInCookieStore(net::SystemCookieStore* store) {
  bool success = false;

  base::RunLoop run_loop;
  store->ClearStoreAsync(base::ReturnValueOnce(true).Then(
      CaptureOutput(&success).Then(run_loop.QuitClosure())));
  run_loop.Run();

  return success;
}

}  // namespace

using SystemCookieStoreUtilTest = PlatformTest;

// Tests that web::CreateSystemCookieStore returns a SystemCookieStore object
// that is backed by the correct internal cookiestore based on the iOS version.
TEST_F(SystemCookieStoreUtilTest, CreateSystemCookieStore) {
  web::WebTaskEnvironment task_environment;
  net::ScopedTestingCookieStoreIOSClient scoped_cookie_store_ios_client(
      std::make_unique<net::TestCookieStoreIOSClient>());

  web::FakeBrowserState browser_state;
  browser_state.SetOffTheRecord(true);
  NSURL* test_cookie_url = [NSURL URLWithString:@"http://foo.google.com/bar"];
  NSHTTPCookie* test_cookie = [NSHTTPCookie cookieWithProperties:@{
    NSHTTPCookiePath : test_cookie_url.path,
    NSHTTPCookieName : @"x",
    NSHTTPCookieValue : @"d",
    NSHTTPCookieDomain : test_cookie_url.host,
  }];
  auto pair = web::CreateSystemCookieStore(&browser_state);
  auto system_cookie_store = std::move(pair.first);

  WKHTTPCookieStore* wk_cookie_store =
      web::WKCookieStoreForBrowserState(&browser_state);
  EXPECT_FALSE(IsCookieSetInWKCookieStore(test_cookie, test_cookie_url,
                                          wk_cookie_store));
  EXPECT_TRUE(SetCookieInCookieStore(test_cookie, system_cookie_store.get()));
  EXPECT_TRUE(IsCookieSetInWKCookieStore(test_cookie, test_cookie_url,
                                         wk_cookie_store));

  // Clear cookies that was set in the test.
  EXPECT_TRUE(ClearCookiesInCookieStore(system_cookie_store.get()));
}

}  // namespace web
