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

#ifndef ASH_SYSTEM_TIME_CALENDAR_UNITTEST_UTILS_H_
#define ASH_SYSTEM_TIME_CALENDAR_UNITTEST_UTILS_H_

#include <array>
#include <list>
#include <memory>
#include <string>

#include "ash/calendar/calendar_client.h"
#include "ash/system/time/calendar_list_model.h"
#include "ash/system/time/calendar_model.h"
#include "ash/system/time/calendar_utils.h"
#include "base/containers/fixed_flat_set.h"
#include "base/functional/callback.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/cstring_view.h"
#include "base/time/time.h"
#include "google_apis/calendar/calendar_api_response_types.h"

namespace ash {

// This list is from "chromeos/ash/components/settings/timezone_settings.cc".
inline constexpr auto kAllTimeZones =
    std::to_array<base::cstring_view>({"Pacific/Midway",
                                       "Pacific/Honolulu",
                                       "America/Anchorage",
                                       "America/Los_Angeles",
                                       "America/Vancouver",
                                       "America/Tijuana",
                                       "America/Phoenix",
                                       "America/Chihuahua",
                                       "America/Denver",
                                       "America/Edmonton",
                                       "America/Mazatlan",
                                       "America/Regina",
                                       "America/Costa_Rica",
                                       "America/Chicago",
                                       "America/Mexico_City",
                                       "America/Tegucigalpa",
                                       "America/Winnipeg",
                                       "Pacific/Easter",
                                       "America/Bogota",
                                       "America/Lima",
                                       "America/New_York",
                                       "America/Toronto",
                                       "America/Caracas",
                                       "America/Barbados",
                                       "America/Halifax",
                                       "America/Manaus",
                                       "America/Santiago",
                                       "America/St_Johns",
                                       "America/Araguaina",
                                       "America/Argentina/Buenos_Aires",
                                       "America/Argentina/San_Luis",
                                       "America/Montevideo",
                                       "America/Santiago",
                                       "America/Sao_Paulo",
                                       "America/Godthab",
                                       "Atlantic/South_Georgia",
                                       "Atlantic/Cape_Verde",
                                       "Etc/GMT",
                                       "Atlantic/Azores",
                                       "Atlantic/Reykjavik",
                                       "Atlantic/St_Helena",
                                       "Africa/Casablanca",
                                       "Atlantic/Faroe",
                                       "Europe/Dublin",
                                       "Europe/Lisbon",
                                       "Europe/London",
                                       "Europe/Amsterdam",
                                       "Europe/Belgrade",
                                       "Europe/Berlin",
                                       "Europe/Bratislava",
                                       "Europe/Brussels",
                                       "Europe/Budapest",
                                       "Europe/Copenhagen",
                                       "Europe/Ljubljana",
                                       "Europe/Madrid",
                                       "Europe/Malta",
                                       "Europe/Oslo",
                                       "Europe/Paris",
                                       "Europe/Prague",
                                       "Europe/Rome",
                                       "Europe/Stockholm",
                                       "Europe/Sarajevo",
                                       "Europe/Tirane",
                                       "Europe/Vaduz",
                                       "Europe/Vienna",
                                       "Europe/Warsaw",
                                       "Europe/Zagreb",
                                       "Europe/Zurich",
                                       "Africa/Windhoek",
                                       "Africa/Lagos",
                                       "Africa/Brazzaville",
                                       "Africa/Cairo",
                                       "Africa/Harare",
                                       "Africa/Maputo",
                                       "Africa/Johannesburg",
                                       "Europe/Kaliningrad",
                                       "Europe/Athens",
                                       "Europe/Bucharest",
                                       "Europe/Chisinau",
                                       "Europe/Helsinki",
                                       "Europe/Istanbul",
                                       "Europe/Kiev",
                                       "Europe/Riga",
                                       "Europe/Sofia",
                                       "Europe/Tallinn",
                                       "Europe/Vilnius",
                                       "Asia/Amman",
                                       "Asia/Beirut",
                                       "Asia/Jerusalem",
                                       "Africa/Nairobi",
                                       "Asia/Baghdad",
                                       "Asia/Riyadh",
                                       "Asia/Kuwait",
                                       "Europe/Minsk",
                                       "Europe/Moscow",
                                       "Asia/Tehran",
                                       "Europe/Samara",
                                       "Asia/Dubai",
                                       "Asia/Tbilisi",
                                       "Indian/Mauritius",
                                       "Asia/Baku",
                                       "Asia/Yerevan",
                                       "Asia/Kabul",
                                       "Asia/Karachi",
                                       "Asia/Aqtobe",
                                       "Asia/Ashgabat",
                                       "Asia/Oral",
                                       "Asia/Yekaterinburg",
                                       "Asia/Calcutta",
                                       "Asia/Colombo",
                                       "Asia/Katmandu",
                                       "Asia/Omsk",
                                       "Asia/Almaty",
                                       "Asia/Dhaka",
                                       "Asia/Novosibirsk",
                                       "Asia/Rangoon",
                                       "Asia/Bangkok",
                                       "Asia/Jakarta",
                                       "Asia/Krasnoyarsk",
                                       "Asia/Novokuznetsk",
                                       "Asia/Ho_Chi_Minh",
                                       "Asia/Phnom_Penh",
                                       "Asia/Vientiane",
                                       "Asia/Shanghai",
                                       "Asia/Hong_Kong",
                                       "Asia/Kuala_Lumpur",
                                       "Asia/Singapore",
                                       "Asia/Manila",
                                       "Asia/Taipei",
                                       "Asia/Ulaanbaatar",
                                       "Asia/Makassar",
                                       "Asia/Irkutsk",
                                       "Asia/Yakutsk",
                                       "Australia/Perth",
                                       "Australia/Eucla",
                                       "Asia/Seoul",
                                       "Asia/Tokyo",
                                       "Asia/Jayapura",
                                       "Asia/Sakhalin",
                                       "Asia/Vladivostok",
                                       "Asia/Magadan",
                                       "Australia/Darwin",
                                       "Australia/Adelaide",
                                       "Pacific/Guam",
                                       "Australia/Brisbane",
                                       "Australia/Hobart",
                                       "Australia/Sydney",
                                       "Asia/Anadyr",
                                       "Pacific/Port_Moresby",
                                       "Asia/Kamchatka",
                                       "Pacific/Fiji",
                                       "Pacific/Majuro",
                                       "Pacific/Auckland",
                                       "Pacific/Tongatapu",
                                       "Pacific/Apia",
                                       "Pacific/Kiritimati"});

// These are from "third_party/fontconfig/include/fc-lang/fclang.h" data.
// Locales "und-zmth" and "und-zsye" are omitted since they cannot be set.
inline constexpr auto kLocales = std::to_array<base::cstring_view>(
    {"aa",     "ab",  "af",    "ak",    "am",    "an",    "ar",    "as",
     "ast",    "av",  "ay",    "az-az", "az-ir", "ba",    "be",    "ber-dz",
     "ber-ma", "bg",  "bh",    "bho",   "bi",    "bin",   "bm",    "bn",
     "bo",     "br",  "brx",   "bs",    "bua",   "byn",   "ca",    "ce",
     "ch",     "chm", "chr",   "co",    "crh",   "cs",    "csb",   "cu",
     "cv",     "cy",  "da",    "de",    "doi",   "dv",    "dz",    "ee",
     "el",     "en",  "eo",    "es",    "et",    "eu",    "fa",    "fat",
     "ff",     "fi",  "fil",   "fj",    "fo",    "fr",    "fur",   "fy",
     "ga",     "gd",  "gez",   "gl",    "gn",    "gu",    "gv",    "ha",
     "haw",    "he",  "hi",    "hne",   "ho",    "hr",    "ht",    "hu",
     "hy",     "hz",  "ia",    "id",    "ie",    "ig",    "ii",    "ik",
     "io",     "is",  "it",    "iu",    "ja",    "jv",    "ka",    "kaa",
     "kab",    "ki",  "kj",    "kk",    "kl",    "km",    "kn",    "ko",
     "kok",    "kr",  "ks",    "ku-am", "ku-iq", "ku-ir", "ku-tr", "kum",
     "kv",     "kw",  "kwm",   "ky",    "la",    "lah",   "lb",    "lez",
     "lg",     "li",  "ln",    "lo",    "lt",    "lv",    "mai",   "mg",
     "mh",     "mi",  "mk",    "ml",    "mn-cn", "mn-mn", "mni",   "mo",
     "mr",     "ms",  "mt",    "my",    "na",    "nb",    "nds",   "ne",
     "ng",     "nl",  "nn",    "no",    "nr",    "nso",   "nv",    "ny",
     "oc",     "om",  "or",    "os",    "ota",   "pa",    "pa-pk", "pap-an",
     "pap-aw", "pl",  "ps-af", "ps-pk", "pt",    "qu",    "quz",   "rm",
     "rn",     "ro",  "ru",    "rw",    "sa",    "sah",   "sat",   "sc",
     "sco",    "sd",  "se",    "sel",   "sg",    "sh",    "shs",   "si",
     "sid",    "sk",  "sl",    "sm",    "sma",   "smj",   "smn",   "sms",
     "sn",     "so",  "sq",    "sr",    "ss",    "st",    "su",    "sv",
     "sw",     "syr", "ta",    "te",    "tg",    "th",    "ti-er", "ti-et",
     "tig",    "tk",  "tl",    "tn",    "to",    "tr",    "ts",    "tt",
     "tw",     "ty",  "tyv",   "ug",    "uk",    "ur",    "uz",    "ve",
     "vi",     "vo",  "vot",   "wa",    "wal",   "wen",   "wo",    "xh",
     "yap",    "yi",  "yo",    "za",    "zh-cn", "zh-hk", "zh-mo", "zh-sg",
     "zh-tw",  "zu"});

inline constexpr auto kLocalesWithUniqueNumerals =
    base::MakeFixedFlatSet<base::cstring_view>({"bn", "fa", "mr", "pa-pk"});

namespace calendar_test_utils {

// Used for over-riding the locale that `base::Time` uses. Copied from
// `time_unittest.cc`.
class ScopedLibcTimeZone {
 public:
  explicit ScopedLibcTimeZone(const std::string& timezone);
  ~ScopedLibcTimeZone();

  ScopedLibcTimeZone(const ScopedLibcTimeZone& other) = delete;
  ScopedLibcTimeZone& operator=(const ScopedLibcTimeZone& other) = delete;

  bool is_success() const { return success_; }

 private:
  static constexpr char kTimeZoneEnvVarName[] = "TZ";

  bool success_ = true;
  std::optional<std::string> old_timezone_;
};

class CalendarListFetchWaiter : public CalendarListModel::Observer {
 public:
  explicit CalendarListFetchWaiter(CalendarListModel* calendar_list_model);
  CalendarListFetchWaiter(const CalendarListFetchWaiter&) = delete;
  CalendarListFetchWaiter& operator=(const CalendarListFetchWaiter&) = delete;
  ~CalendarListFetchWaiter() override;

  void Wait();

 private:
  // CalendarListModel::Observer:
  void OnCalendarListFetchComplete() override;

  bool complete_ = false;
  base::RunLoop run_loop_;
  base::ScopedObservation<CalendarListModel, CalendarListModel::Observer>
      scoped_observation_{this};
};

class CalendarEventsFetchWaiter : public CalendarModel::Observer {
 public:
  CalendarEventsFetchWaiter(CalendarModel* calendar_model,
                            base::Time start_of_month);
  CalendarEventsFetchWaiter(CalendarModel* calendar_model,
                            base::RepeatingCallback<bool()> complete_predicate);
  CalendarEventsFetchWaiter(const CalendarEventsFetchWaiter&) = delete;
  CalendarEventsFetchWaiter& operator=(const CalendarEventsFetchWaiter&) =
      delete;
  ~CalendarEventsFetchWaiter() override;

  void Wait();

 private:
  // CalendarModel::Observer:
  void OnEventsFetched(const CalendarModel::FetchingStatus status,
                       const base::Time start_time) override;

  bool IsComplete();

  bool match_successful_month_ = false;
  base::Time start_of_month_;
  base::RepeatingCallback<bool()> complete_predicate_;
  bool complete_ = false;
  base::RunLoop run_loop_;
  base::ScopedObservation<CalendarModel, CalendarModel::Observer>
      scoped_observation_{this};
};

// A duration to let the animation finish and pass the cool down duration in
// tests.
constexpr base::TimeDelta kAnimationSettleDownDuration = base::Seconds(3);

// A duration which is smaller than any of the animation duration. So if there's
// an animation, the view should be in the middle of the animation.
constexpr base::TimeDelta kAnimationStartBufferDuration =
    base::Milliseconds(90);

// Creates a `google_apis::calendar::SingleCalendar` for testing only.
std::unique_ptr<google_apis::calendar::SingleCalendar> CreateCalendar(
    const std::string& id,
    const std::string& summary,
    const std::string& color_id,
    bool selected,
    bool primary);

std::unique_ptr<google_apis::calendar::CalendarList> CreateMockCalendarList(
    std::list<std::unique_ptr<google_apis::calendar::SingleCalendar>>
        calendars);

// Creates a `google_apis::calendar::CalendarEvent` for testing, that converts
// start/end time strings to `google_apis::calendar::DateTime`.
std::unique_ptr<google_apis::calendar::CalendarEvent> CreateEvent(
    const char* id,
    const char* summary,
    const char* start_time,
    const char* end_time,
    const google_apis::calendar::CalendarEvent::EventStatus event_status =
        google_apis::calendar::CalendarEvent::EventStatus::kConfirmed,
    const google_apis::calendar::CalendarEvent::ResponseStatus
        self_response_status =
            google_apis::calendar::CalendarEvent::ResponseStatus::kAccepted,
    bool all_day_event = false,
    GURL video_conference_url = GURL());

// Creates a `google_apis::calendar::CalendarEvent` for testing, that converts
// start/end `base::Time` objects to `google_apis::calendar::DateTime`.
std::unique_ptr<google_apis::calendar::CalendarEvent> CreateEvent(
    const char* id,
    const char* summary,
    base::Time start_time,
    base::Time end_time,
    const google_apis::calendar::CalendarEvent::EventStatus event_status =
        google_apis::calendar::CalendarEvent::EventStatus::kConfirmed,
    const google_apis::calendar::CalendarEvent::ResponseStatus
        self_response_status =
            google_apis::calendar::CalendarEvent::ResponseStatus::kAccepted,
    bool all_day_event = false,
    GURL video_conference_url = GURL());

std::unique_ptr<google_apis::calendar::EventList> CreateMockEventList(
    std::list<std::unique_ptr<google_apis::calendar::CalendarEvent>> events);

// Checks if the two exploded are in the same month.
bool IsTheSameMonth(const base::Time date_a, const base::Time date_b);

// Returns the `base:Time` from the given string.
base::Time GetTimeFromString(const char* start_time);

// A mock `CalendarClient` which uses `SetEventList` and `SetError` to set the
// response. This mock client's `GetEventList` waits for a short duration to
// mock the fetching process. This client can be used in calendar unittests to
// mock the process of fetching events and getting back event list or error
// message. It needs to be registered to a calendar client in `SetUp`.
class CalendarClientTestImpl : public CalendarClient {
 public:
  CalendarClientTestImpl();
  CalendarClientTestImpl(const CalendarClientTestImpl& other) = delete;
  CalendarClientTestImpl& operator=(const CalendarClientTestImpl& other) =
      delete;
  ~CalendarClientTestImpl() override;

  void set_is_disabled_by_admin(bool is_disabled_by_admin) {
    is_disabled_by_admin_ = is_disabled_by_admin;
  }

  // CalendarClient:
  bool IsDisabledByAdmin() const override;
  base::OnceClosure GetCalendarList(
      google_apis::calendar::CalendarListCallback callback) override;
  base::OnceClosure GetEventList(
      google_apis::calendar::CalendarEventListCallback callback,
      const base::Time start_time,
      const base::Time end_time) override;
  base::OnceClosure GetEventList(
      google_apis::calendar::CalendarEventListCallback callback,
      const base::Time start_time,
      const base::Time end_time,
      const std::string& calendar_id,
      const std::string& calendar_color_id) override;

  // Sets `calendars` as the fetched calendar list.
  void SetCalendarList(
      std::unique_ptr<google_apis::calendar::CalendarList> calendars);

  // Sets `events` as the fetched event list.
  void SetEventList(std::unique_ptr<google_apis::calendar::EventList> events);

  // Sets `error` as the error message. The value of `error` is
  // `google_apis::HTTP_SUCCESS` by default.
  void SetError(google_apis::ApiErrorCode error) { error_ = error; }

  // Sets `delay` as the response delay. By default, the response delay is
  // `kAnimationSettleDownDuration` plus 2 seconds.
  void SetResponseDelay(const base::TimeDelta delay) { task_delay_ = delay; }

  // Force the task to take longer than the default timeout, causing an internal
  // error to be propagated.
  void ForceTimeout() {
    task_delay_ = calendar_utils::kCalendarDataFetchTimeout + base::Seconds(1);
  }

 private:
  bool is_disabled_by_admin_ = false;
  google_apis::ApiErrorCode error_ = google_apis::HTTP_SUCCESS;
  std::unique_ptr<google_apis::calendar::CalendarList> calendars_;
  std::unique_ptr<google_apis::calendar::EventList> events_;
  base::TimeDelta task_delay_ = kAnimationSettleDownDuration + base::Seconds(2);
};

}  // namespace calendar_test_utils

}  // namespace ash

#endif  // ASH_SYSTEM_TIME_CALENDAR_UNITTEST_UTILS_H_
