// Copyright (C) 2013 Rob Caelers <robc@krandor.nl>
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <gtest/gtest.h>

#include <boost/signals2.hpp>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/tokenizer.hpp>

#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/pattern_formatter.h>
#include <spdlog/cfg/env.h>

#include <filesystem>
#include <iostream>
#include <fstream>
#include <map>
#include <chrono>
#include <unistd.h>
using namespace std::chrono_literals;

#include "core/CoreTypes.hh"
#include "core/CoreConfig.hh"
#include "core/ICore.hh"
#include "core/IApp.hh"
#include "core/IBreak.hh"
#include "input-monitor/InputMonitorFactoryStub.hh"

#include "config/Config.hh"
#include "config/SettingCache.hh"

#include "utils/Paths.hh"
#include "utils/TimeSource.hh"
#include "debug.hh"

#include "Timer.hh"
#include "ICoreTestHooks.hh"
#include "Core.hh"
#include "stats/IStatistics.hh"
#include "IStatisticsStore.hh"

#include "SimulatedTime.hh"
#include "ActivityMonitorStub.hh"

using namespace workrave::config;

namespace workrave
{
  std::ostream &operator<<(std::ostream &stream, ::workrave::OperationMode e)
  {
    stream << workrave::utils::enum_to_string(e);
    return stream;
  }

  std::ostream &operator<<(std::ostream &stream, ::workrave::UsageMode e)
  {
    stream << workrave::utils::enum_to_string(e);
    return stream;
  }

  std::ostream &operator<<(std::ostream &stream, ::workrave::BreakEvent e)
  {
    stream << workrave::utils::enum_to_string(e);
    return stream;
  }

  std::ostream &operator<<(std::ostream &stream, ::workrave::IApp::PreludeStage e)
  {
    stream << workrave::utils::enum_to_string(e);
    return stream;
  }

  std::ostream &operator<<(std::ostream &stream, ::workrave::IApp::PreludeProgressText e)
  {
    stream << workrave::utils::enum_to_string(e);
    return stream;
  }
} // namespace workrave

class test_time_formatter_flag : public spdlog::custom_flag_formatter
{
public:
  void format(const spdlog::details::log_msg &msg, const std::tm &tm, spdlog::memory_buf_t &dest) override
  {
    auto timer_text = std::to_string(timer);
    timer_text = std::string(std::max(0, static_cast<int>(padinfo_.width_ - timer_text.size())), ' ') + timer_text;
    dest.append(timer_text.data(), timer_text.data() + timer_text.size());
  }

  std::unique_ptr<custom_flag_formatter> clone() const override
  {
    return spdlog::details::make_unique<test_time_formatter_flag>();
  }

  static int timer;
};

int test_time_formatter_flag::timer = 0;

class GlobalFixture : public ::testing::Environment
{
public:
  void SetUp() override
  {
    portable_directory = std::filesystem::temp_directory_path()
                         / ("workrave-core-integration-test-" + std::to_string(::getpid()));
    std::filesystem::remove_all(portable_directory);
    workrave::utils::Paths::set_portable_directory(portable_directory.string());

    const auto *log_file = "workrave-core-integration-test.log";

    auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_file, false);

    auto logger{std::make_shared<spdlog::logger>("workrave", file_sink)};
    spdlog::set_default_logger(logger);

    spdlog::set_level(spdlog::level::info);
    spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%n] [%^%-5l%$] %v");

    auto formatter = std::make_unique<spdlog::pattern_formatter>();
    formatter->add_flag<test_time_formatter_flag>('*').set_pattern("[%Y-%m-%d %H:%M:%S.%e %4*] [%n] [%^%-5l%$] %v");
    spdlog::set_formatter(std::move(formatter));
    spdlog::cfg::load_env_levels();
  }

  void TearDown() override
  {
    std::error_code ec;
    std::filesystem::remove_all(portable_directory, ec);
  }

private:
  std::filesystem::path portable_directory;
};

template<typename It>
struct range
{
  It begin_, end_;
  It begin() const
  {
    return begin_;
  }
  It end() const
  {
    return end_;
  }
};
template<typename It>
range<It>
as_range(const std::pair<It, It> &p)
{
  return {p.first, p.second};
}

class Observation
{
public:
  Observation(uint64_t time, std::string event, std::string params = "")
    : time(time)
    , event(event)
    , params(params)
  {
  }

  bool operator==(const Observation &rhs) const
  {
    return (time == rhs.time && event == rhs.event && params == rhs.params);
  }

  friend std::ostream &operator<<(std::ostream &out, Observation &o);

  uint64_t time;
  std::string event;
  std::string params;
  bool seen{};
};

std::ostream &
operator<<(std::ostream &out, const Observation &o)
{
  out << "[time=" << o.time << " event=" << o.event << " arg=" << o.params << "]";
  return out;
}

namespace
{
  using Date = workrave::stats::IStatistics::Date;

  //! The number of archived days, which is every day but the one in progress.
  size_t history_size(workrave::stats::IStatistics *statistics)
  {
    using namespace std::chrono;
    std::vector<Date> dates = statistics->get_dates(year{1} / 1 / 1, year{9999} / 12 / 31);

    // A day in progress that hasn't seen any activity yet has no start of its own,
    // so its date is only known through the current wall clock.
    const auto *today = statistics->get_current_day();
    const Date today_date = (today != nullptr && today->start.has_value())
                              ? workrave::stats::date_of(*today->start)
                              : workrave::stats::date_of(workrave::stats::local_now());
    std::erase(dates, today_date);

    return dates.size();
  }
} // namespace

class Backend : public workrave::IApp
{
public:
  Backend()
    : active_break{workrave::BREAK_ID_NONE}
    , active_prelude{workrave::BREAK_ID_NONE}
    , max_preludes{3}
  {
    for (int &i: prelude_count)
      {
        i = 0;
      }

    // Statistics are now kept in a database that survives whatever an earlier
    // test case left behind, so each test case starts from an empty state
    // directory.
    std::error_code ec;
    std::filesystem::remove_all(workrave::utils::Paths::get_state_directory(), ec);
  }

  Backend(std::ofstream out,
          ICore *core,
          IConfigurator::Ptr config,
          SimulatedTime::Ptr sim,
          ActivityMonitorStub::Ptr monitor,
          std::multimap<uint64_t, Observation> expected_results,
          std::multimap<uint64_t, Observation> actual_results)
    : out(std::move(out))
    , core(core)
    , config(std::move(config))
    , sim(std::move(sim))
    , monitor(std::move(monitor))
    , expected_results(std::move(expected_results))
    , actual_results(std::move(actual_results))
  {
  }
  ~Backend() override
  {
    Core::reset_instance();
    out.close();
  }

  void init_log_file()
  {
    std::string test_name = ::testing::UnitTest::GetInstance()->current_test_info()->name();
    std::filesystem::path result_file_name;
    result_file_name /= "results";
    std::filesystem::create_directory(result_file_name);
    result_file_name /= test_name + ".txt";
    out.open(result_file_name.string().c_str());
  }

  void init_core()
  {
    TRACE_ENTRY();
    workrave::config::SettingCache::reset();
    Core::set_configurator(create_configurator());
    core = Core::get_instance();

    ICoreHooks::Ptr hooks = core->get_hooks();
    ICoreTestHooks::Ptr test_hooks = std::dynamic_pointer_cast<ICoreTestHooks>(hooks);

    test_hooks->hook_create_monitor() = std::bind(&Backend::on_create_monitor, this);
    if (install_load_timer_state_hook)
      {
        test_hooks->hook_load_timer_state() = std::bind(&Backend::on_load_timer_state, this, std::placeholders::_1);
      }

    core->init(0, nullptr, this, "");

    for (int i = 0; i < workrave::BREAK_ID_SIZEOF; i++)
      {
        auto *b = core->get_break(workrave::BreakId(i));
        b->signal_break_event().connect(std::bind(&Backend::on_break_event, this, workrave::BreakId(i), std::placeholders::_1));
        prelude_count[i] = 0;
      }

    TRACE_MSG("pretest");
    pretest_verify();

    core->set_operation_mode(workrave::OperationMode::Normal);
    core->set_usage_mode(workrave::UsageMode::Normal);

    core->signal_operation_mode_changed().connect(std::bind(&Backend::on_operation_mode_changed, this, std::placeholders::_1));
    core->signal_usage_mode_changed().connect(std::bind(&Backend::on_usage_mode_changed, this, std::placeholders::_1));
  }

  void init()
  {
    sim = SimulatedTime::create();
    sim->reset();

    workrave::utils::TimeSource::sync();
    start_time = sim->get_real_time_usec();
    test_time_formatter_flag::timer = timer;
    init_log_file();
    init_core();
  }

  void init_with_timer_state(const std::string &state, bool install_load_hook = false)
  {
    sim = SimulatedTime::create();
    sim->reset();

    workrave::utils::TimeSource::sync();
    start_time = sim->get_real_time_usec();
    test_time_formatter_flag::timer = timer;
    init_log_file();

    auto state_path = workrave::utils::Paths::get_state_directory() / "state";
    std::ofstream state_file(state_path);
    state_file << state;
    state_file.close();

    install_load_timer_state_hook = install_load_hook;
    load_timer_state_result = false;
    init_core();
  }

  void init_without_timer_state()
  {
    sim = SimulatedTime::create();
    sim->reset();

    workrave::utils::TimeSource::sync();
    start_time = sim->get_real_time_usec();
    test_time_formatter_flag::timer = timer;
    init_log_file();

    auto state_path = workrave::utils::Paths::get_state_directory() / "state";
    std::filesystem::remove(state_path);

    install_load_timer_state_hook = false;
    init_core();
  }

  void pretest_verify() const
  {
    for (int i = 0; i < workrave::BREAK_ID_SIZEOF; i++)
      {
        auto *b = core->get_break(workrave::BreakId(i));

        if (i != workrave::BREAK_ID_DAILY_LIMIT)
          {
            EXPECT_TRUE(b->is_auto_reset_enabled());
          }
        EXPECT_TRUE(b->is_limit_enabled());
      }
    EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Quiet);
    EXPECT_EQ(core->get_usage_mode(), workrave::UsageMode::Reading);
  }

  void tick()
  {
    tick(user_active, 1, [](int) {});
  }

  void tick(bool active, int count = 1)
  {
    tick(active, count, [](int) {});
  }

  void tick(bool active, int seconds, const std::function<void(int)> &check_func)
  {
    for (int i = 0; i < seconds; i++)
      {
        try
          {
            if (user_active != active)
              {
                log(active ? "active" : "idle");
                user_active = active;
              }
            monitor->set_active(user_active);
            did_refresh = false;
            need_refresh = false;
            workrave::utils::TimeSource::sync();
            core->heartbeat();

            for (int j = 0; j < workrave::BREAK_ID_SIZEOF; j++)
              {
                auto *b = core->get_break(workrave::BreakId(j));
                spdlog::debug("{}: elapsed={} idle={}", j, b->get_elapsed_time(), b->get_elapsed_idle_time());
              }

            {
              SCOPED_TRACE("Timer");
              SCOPED_TRACE(::testing::Message() << "Count:" << i);

              if (active_break != workrave::BREAK_ID_NONE || active_prelude != workrave::BREAK_ID_NONE)
                {
                  EXPECT_TRUE(!need_refresh || did_refresh);
                }

              if (active_break != workrave::BREAK_ID_NONE)
                {
                  check_break_progress();
                }

              for (int j = 0; j < workrave::BREAK_ID_SIZEOF; j++)
                {
                  auto *b = core->get_break(workrave::BreakId(j));
                  EXPECT_TRUE(j == active_break ? b->is_taking() : !b->is_taking());
                }

              if (active_prelude != workrave::BREAK_ID_NONE)
                {
                  check_prelude_progress();
                }

              check_func(i);
            }
            sim->current_time += 1000000;
            timer++;
            test_time_formatter_flag::timer = (sim->current_time - start_time) / 1000000;
          }
        catch (std::exception &e)
          {
            std::cout << "error at : " << ((sim->current_time - start_time) / 1000000) << " " << i << "\n";
            std::cout << e.what() << "\n";
            throw;
          }
        catch (...)
          {
            std::cout << "error at : " << ((sim->current_time - start_time) / 1000000) << " " << i << "\n";
            throw;
          }
      }
  }

  void log_actual(const std::string &event, const std::string &param = "")
  {
    uint64_t time = (sim->get_monotonic_time_usec() - start_time) / 1000000;

    out << time << ",Y,";
    out << event << ",";
    out << param << std::endl;

    auto observation = Observation(time, event, param);
    actual_results.insert(std::make_pair(time, observation));
    spdlog::debug("Observation: time={} event={} arg={}", observation.time, observation.event, observation.params);
  }

  void log(const std::string &event, const std::string &param = "")
  {
    uint64_t time = (sim->get_monotonic_time_usec() - start_time) / 1000000;

    out << time << ",N,";
    out << event << ",";
    out << param << std::endl;
  }

  void expect(uint64_t time, const std::string &event, const std::string &param = "")
  {
    expected_results.insert(std::make_pair(time, Observation(time, event, param)));
  }

  void verify()
  {
    for (auto &expected: expected_results)
      {
        auto matching_actual_results = actual_results.equal_range(expected.first);
        for (auto &actual: as_range(matching_actual_results))
          {
            if (expected.second == actual.second && !actual.second.seen)
              {
                actual.second.seen = true;
                expected.second.seen = true;
                break;
              }
          }
      }

    std::list<std::pair<int, Observation>> report;
    std::transform(expected_results.begin(), expected_results.end(), std::back_inserter(report), [](const auto &o) {
      return std::make_pair(0, o.second);
    });
    std::transform(actual_results.begin(), actual_results.end(), std::back_inserter(report), [](const auto &o) {
      return std::make_pair(1, o.second);
    });
    report.sort([](auto &a, auto &b) { return a.second.time < b.second.time; });

    for (auto [what, observation]: report)
      {
        if (what == 0)
          {
            EXPECT_TRUE(observation.seen) << boost::format("Observation %1% missing.") % observation;
          }
        if (what == 1)
          {
            EXPECT_TRUE(observation.seen) << boost::format("Observation %1% extra.") % observation;
          }
      }
  }

  void check_break_progress() const
  {
    auto *b = core->get_break(active_break);
    if (fake_break)
      {
        EXPECT_EQ(last_max_value, b->get_auto_reset());
        EXPECT_EQ(last_value, timer + fake_break_delta);
      }
    else
      {
        EXPECT_EQ(last_max_value, b->get_auto_reset());

        if (active_break != workrave::BREAK_ID_DAILY_LIMIT)
          {
            // FIXME: check why this fails for daly limit.
            EXPECT_EQ(last_value, b->get_elapsed_idle_time());
          }
      }
  }

  void check_prelude_progress() const
  {
    EXPECT_EQ(last_max_value, 29);
    if (timer == 0)
      {
        // FIXME: this is weird behaviour.
        EXPECT_TRUE(last_value == 0 || last_value == 1);
      }
    else if (timer < 30)
      {
        EXPECT_EQ(last_value, timer + 1);
      }
    else
      {
        EXPECT_EQ(last_value, 30);
      }
  }

  void create_prelude_window(workrave::BreakId break_id) override
  {
    log_actual("prelude", boost::str(boost::format("break_id=%1%") % CoreConfig::get_break_name(break_id)));

    auto *b = core->get_break(break_id);
    EXPECT_EQ(b->get_name(), CoreConfig::get_break_name(break_id));

    bool rest_break_advanced = false;
    if (break_id == workrave::BREAK_ID_REST_BREAK)
      {
        auto *mb = core->get_break(workrave::BREAK_ID_MICRO_BREAK);

        if (mb->get_elapsed_time() >= mb->get_limit() && b->get_elapsed_time() + 30 >= b->get_limit())
          {
            rest_break_advanced = true;
          }
      }

    EXPECT_TRUE(rest_break_advanced || b->get_elapsed_time() >= b->get_limit());
    EXPECT_EQ(active_break, workrave::BREAK_ID_NONE);
    EXPECT_EQ(active_prelude, workrave::BREAK_ID_NONE);

    active_prelude = break_id;
    prelude_count[break_id]++;
    timer = 0;
    prelude_stage_set = false;
    prelude_text_set = false;
    prelude_progress_set = false;
    last_value = -1;
    last_max_value = 0;
  }

  void create_break_window(workrave::BreakId break_id, workrave::utils::Flags<workrave::BreakHint> break_hint) override
  {
    log_actual("break",
               boost::str(boost::format("break_id=%1% break_hint=%2%") % CoreConfig::get_break_name(break_id) % break_hint));

    auto *b = core->get_break(break_id);

    bool rest_break_advanced = false;
    if (break_id == workrave::BREAK_ID_REST_BREAK)
      {
        auto *mb = core->get_break(workrave::BREAK_ID_MICRO_BREAK);

        if (mb->get_elapsed_time() >= mb->get_limit() && b->get_elapsed_time() + 30 >= b->get_limit())
          {
            rest_break_advanced = true;
          }
      }

    if (!fake_break && !forced_break)
      {
        EXPECT_TRUE(rest_break_advanced || b->get_elapsed_time() >= b->get_limit());
      }
    EXPECT_EQ(active_break, workrave::BREAK_ID_NONE);
    EXPECT_EQ(active_prelude, workrave::BREAK_ID_NONE);

    active_break = break_id;
    timer = 0;
    last_value = -1;
    last_max_value = 0;
  }

  void hide_break_window() override
  {
    if (active_break != workrave::BREAK_ID_NONE || active_prelude != workrave::BREAK_ID_NONE)
      {
        log_actual("hide");
      }
    else
      {
        log("hide");
      }
    active_break = workrave::BREAK_ID_NONE;
    active_prelude = workrave::BREAK_ID_NONE;
  }

  void show_break_window() override
  {
    log_actual("show");
    EXPECT_TRUE(active_break != workrave::BREAK_ID_NONE || active_prelude != workrave::BREAK_ID_NONE);
    did_refresh = true; // Implicit refresh... not in next
  }

  void refresh_break_window() override
  {
    log("refresh");

    // TODO: remove forced_break from check after fixing code.
    EXPECT_TRUE(forced_break || active_break != workrave::BREAK_ID_NONE || active_prelude != workrave::BREAK_ID_NONE);

    if (active_prelude != workrave::BREAK_ID_NONE)
      {
        EXPECT_TRUE(prelude_progress_set);
        EXPECT_TRUE(prelude_stage_set);
        EXPECT_TRUE(prelude_text_set);
      }

    if (active_break != workrave::BREAK_ID_NONE)
      {
        EXPECT_TRUE(break_progress_set);
      }

    did_refresh = true;
  }

  void set_break_progress(int value, int max_value) override
  {
    log("progress", boost::str(boost::format("value=%1% max_value=%2%") % value % max_value));

    // TODO: remove forced_break from check after fixing code.
    EXPECT_TRUE(forced_break || active_break != workrave::BREAK_ID_NONE || active_prelude != workrave::BREAK_ID_NONE);

    last_value = value;
    last_max_value = max_value;

    if (active_break != workrave::BREAK_ID_NONE)
      {
        break_progress_set = true;
      }

    if (active_prelude != workrave::BREAK_ID_NONE)
      {
        prelude_progress_set = true;
      }

    need_refresh = true;
  }

  void set_prelude_stage(PreludeStage stage) override
  {
    log("stage", boost::str(boost::format("stage=%1%") % stage));

    EXPECT_TRUE(active_break != workrave::BREAK_ID_NONE || active_prelude != workrave::BREAK_ID_NONE);

    need_refresh = true;
    prelude_stage_set = true;
  }

  void set_prelude_progress_text(PreludeProgressText text) override
  {
    log("text", boost::str(boost::format("text=%1%") % text));

    EXPECT_TRUE(active_break != workrave::BREAK_ID_NONE || active_prelude != workrave::BREAK_ID_NONE);

    if (prelude_count[active_prelude] < max_preludes)
      {
        EXPECT_EQ(text, IApp::PreludeProgressText::DisappearsIn);
      }
    else
      {
        EXPECT_EQ(text, IApp::PreludeProgressText::BreakIn);
      }

    need_refresh = true;
    prelude_text_set = true;
  }

  virtual void on_break_event(workrave::BreakId break_id, BreakEvent event)
  {
    log_actual("break_event", boost::str(boost::format("break_id=%1% event=%2%") % CoreConfig::get_break_name(break_id) % event));

    if (event == BreakEvent::BreakStop)
      {
        prelude_count[break_id] = 0;
      }
  }

  virtual void on_operation_mode_changed(const workrave::OperationMode m)
  {
    log_actual("operationmode", boost::str(boost::format("mode=%1%") % static_cast<int>(m)));
  }

  virtual void on_usage_mode_changed(const workrave::UsageMode m)
  {
    log_actual("usagemode", boost::str(boost::format("mode=%1%") % static_cast<int>(m)));
  }

  bool on_is_user_active(bool dummy) const
  {
    return user_active;
  }

  IActivityMonitor::Ptr on_create_monitor()
  {
    monitor = std::make_shared<ActivityMonitorStub>();
    return monitor;
  }

  IConfigurator::Ptr create_configurator()
  {
    config = ConfiguratorFactory::create(ConfigFileFormat::Ini);

    config->set_value("timers/micro_pause/limit", 300);
    config->set_value("timers/micro_pause/auto_reset", 20);
    config->set_value("timers/micro_pause/reset_pred", "");
    config->set_value("timers/micro_pause/snooze", 150);

    config->set_value("timers/rest_break/limit", 1500);
    config->set_value("timers/rest_break/auto_reset", 300);
    config->set_value("timers/rest_break/reset_pred", "");
    config->set_value("timers/rest_break/snooze", 180);

    config->set_value("timers/daily_limit/limit", 14400);
    config->set_value("timers/daily_limit/auto_reset", 0);
    config->set_value("timers/daily_limit/reset_pred", "day/4:00");
    config->set_value("timers/daily_limit/snooze", 1200);

    config->set_value("breaks/micro_pause/max_preludes", 3);
    config->set_value("breaks/micro_pause/enabled", true);
    config->set_value("breaks/rest_break/max_preludes", 6);
    config->set_value("breaks/rest_break/enabled", true);
    config->set_value("breaks/daily_limit/max_preludes", 3);
    config->set_value("breaks/daily_limit/enabled", true);

    config->set_value("timers/daily_limit/use_microbreak_activity", false);
    config->set_value("general/usage-mode", 1);
    config->set_value("general/operation-mode", 2);

    return config;
  }

  bool on_load_timer_state(Timer *breaks[workrave::BREAK_ID_SIZEOF])
  {
    return load_timer_state_result;
  }

  std::ofstream out;
  ICore *core{};
  IConfigurator::Ptr config;
  SimulatedTime::Ptr sim;
  ActivityMonitorStub::Ptr monitor;
  bool user_active{};
  uint64_t start_time{};
  workrave::BreakId active_break{};
  workrave::BreakId active_prelude{};
  int timer{};
  std::array<int, workrave::BREAK_ID_SIZEOF> prelude_count{};
  bool fake_break{};
  int fake_break_delta{};
  bool forced_break{};
  int max_preludes{};
  bool did_refresh{};
  bool need_refresh{};
  bool prelude_stage_set{};
  bool prelude_text_set{};
  bool prelude_progress_set{};
  bool break_progress_set{};
  int last_value{};
  int last_max_value{};
  bool install_load_timer_state_hook{true};
  bool load_timer_state_result{true};

  std::multimap<uint64_t, Observation> expected_results;
  std::multimap<uint64_t, Observation> actual_results;
};

::testing::Environment *const global_fixture = ::testing::AddGlobalTestEnvironment(new GlobalFixture);

class IntegrationTest : public ::testing::Test, public Backend
{};

TEST_F(IntegrationTest, test_grpc_preferences)
{
  init();

#if defined(HAVE_GRPC) && !defined(HAVE_GRPC_UNIX_SOCKETS)
  EXPECT_TRUE(!CoreConfig::grpc_enabled()());
#else
  EXPECT_TRUE(CoreConfig::grpc_enabled()());
#endif
  EXPECT_EQ(CoreConfig::grpc_transport()(), "unix");
  EXPECT_EQ(workrave::utils::Paths::get_rpc_socket_path(), workrave::utils::Paths::get_state_directory() / "rpc.sock");
  EXPECT_EQ(CoreConfig::grpc_port()(), 50051);

  CoreConfig::grpc_enabled().set(true);
  EXPECT_TRUE(!CoreConfig::disable_grpc_if_unix_sockets_unsupported(true));
  EXPECT_TRUE(CoreConfig::grpc_enabled()());
  EXPECT_TRUE(CoreConfig::disable_grpc_if_unix_sockets_unsupported(false));
  EXPECT_TRUE(!CoreConfig::grpc_enabled()());

  CoreConfig::grpc_enabled().set(false);
  CoreConfig::grpc_transport().set("tcp");
  CoreConfig::grpc_port().set(54321);

  EXPECT_TRUE(!CoreConfig::grpc_enabled()());
  EXPECT_EQ(CoreConfig::grpc_transport()(), "tcp");
  EXPECT_EQ(CoreConfig::grpc_port()(), 54321);

  verify();
}

TEST_F(IntegrationTest, test_operation_mode)
{
  init();

  expect(0, "operationmode", "mode=2");
  core->set_operation_mode(workrave::OperationMode::Quiet);
  core->set_operation_mode(workrave::OperationMode::Quiet);
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Quiet);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Quiet);
  EXPECT_TRUE(!core->is_operation_mode_an_override());

  expect(1, "operationmode", "mode=0");
  core->set_operation_mode(workrave::OperationMode::Normal);
  core->set_operation_mode(workrave::OperationMode::Normal);
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(!core->is_operation_mode_an_override());

  expect(2, "operationmode", "mode=1");
  core->set_operation_mode(workrave::OperationMode::Suspended);
  core->set_operation_mode(workrave::OperationMode::Suspended);
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_TRUE(!core->is_operation_mode_an_override());

  expect(3, "operationmode", "mode=0");
  core->set_operation_mode(workrave::OperationMode::Normal);
  core->set_operation_mode(workrave::OperationMode::Normal);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(!core->is_operation_mode_an_override());

  verify();
}

TEST_F(IntegrationTest, test_operation_mode_via_settings)
{
  init();

  expect(0, "operationmode", "mode=2");
  config->set_value("general/operation-mode", 2);
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Quiet);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Quiet);
  EXPECT_TRUE(!core->is_operation_mode_an_override());

  expect(1, "operationmode", "mode=0");
  config->set_value("general/operation-mode", 0);
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(!core->is_operation_mode_an_override());

  expect(2, "operationmode", "mode=1");
  config->set_value("general/operation-mode", 1);
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_TRUE(!core->is_operation_mode_an_override());

  expect(3, "operationmode", "mode=0");
  config->set_value("general/operation-mode", 0);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(!core->is_operation_mode_an_override());

  verify();
}

TEST_F(IntegrationTest, test_operation_mode_quiet)
{
  init();

  expect(0, "operationmode", "mode=2");
  core->set_operation_mode(workrave::OperationMode::Quiet);
  tick(true, 300);

  expect(300, "operationmode", "mode=0");
  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  core->set_operation_mode(workrave::OperationMode::Normal);
  tick(true, 1);

  verify();
}

TEST_F(IntegrationTest, test_operation_mode_quiet_break_snoozed)
{
  init();

  expect(0, "operationmode", "mode=2");
  core->set_operation_mode(workrave::OperationMode::Quiet);
  tick(true, 302);

  expect(302, "operationmode", "mode=0");
  expect(450, "prelude", "break_id=micro_pause");
  expect(450, "show");
  expect(450, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(450, "break_event", "break_id=micro_pause event=BreakStart");
  core->set_operation_mode(workrave::OperationMode::Normal);
  tick(true, 150);

  verify();
}

TEST_F(IntegrationTest, test_operation_mode_suspended)
{
  init();

  expect(0, "operationmode", "mode=1");
  core->set_operation_mode(workrave::OperationMode::Suspended);
  tick(true, 300);

  expect(300, "operationmode", "mode=0");
  core->set_operation_mode(workrave::OperationMode::Normal);
  tick(true, 1);

  verify();
}

TEST_F(IntegrationTest, test_powersave)
{
  init();

  core->set_powersave(true);
  core->set_powersave(true);
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(core->is_operation_mode_an_override());

  core->set_powersave(false);
  core->set_powersave(false);
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(!core->is_operation_mode_an_override());

  verify();
}

TEST_F(IntegrationTest, test_operation_mode_override)
{
  init();

  core->set_operation_mode_override(workrave::OperationMode::Suspended, "ov1");
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(core->is_operation_mode_an_override());

  core->set_operation_mode_override(workrave::OperationMode::Quiet, "ov2");
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(core->is_operation_mode_an_override());

  core->remove_operation_mode_override("ov2");
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(core->is_operation_mode_an_override());

  core->set_operation_mode_override(workrave::OperationMode::Quiet, "ov2");
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(core->is_operation_mode_an_override());

  core->remove_operation_mode_override("ov1");
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Quiet);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(core->is_operation_mode_an_override());

  core->remove_operation_mode_override("ov2");
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(!core->is_operation_mode_an_override());

  core->set_operation_mode_override(workrave::OperationMode::Normal, "ov3");
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(core->is_operation_mode_an_override());

  verify();
}

TEST_F(IntegrationTest, test_operation_mode_override_change_to_normal_while_overridden)
{
  init();

  expect(0, "operationmode", "mode=2");
  core->set_operation_mode(workrave::OperationMode::Quiet);
  EXPECT_EQ(CoreConfig::operation_mode()(), workrave::OperationMode::Quiet);
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Quiet);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Quiet);
  EXPECT_TRUE(!core->is_operation_mode_an_override());
  EXPECT_EQ(CoreConfig::operation_mode()(), workrave::OperationMode::Quiet);

  core->set_operation_mode_override(workrave::OperationMode::Suspended, "ov1");
  EXPECT_EQ(CoreConfig::operation_mode()(), workrave::OperationMode::Quiet);
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Quiet);
  EXPECT_TRUE(core->is_operation_mode_an_override());

  expect(2, "operationmode", "mode=0");
  core->set_operation_mode(workrave::OperationMode::Normal);
  EXPECT_EQ(CoreConfig::operation_mode()(), workrave::OperationMode::Normal);
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(core->is_operation_mode_an_override());

  core->remove_operation_mode_override("ov1");
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(!core->is_operation_mode_an_override());

  core->remove_operation_mode_override("ov2");
  tick(false, 1);

  verify();
}

TEST_F(IntegrationTest, test_operation_mode_override_revert_while_overridden)
{
  init();

  expect(0, "operationmode", "mode=2");
  core->set_operation_mode(workrave::OperationMode::Quiet);
  EXPECT_EQ(CoreConfig::operation_mode()(), workrave::OperationMode::Quiet);
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Quiet);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Quiet);
  EXPECT_TRUE(!core->is_operation_mode_an_override());

  core->set_operation_mode_override(workrave::OperationMode::Suspended, "ov1");
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Quiet);
  EXPECT_TRUE(core->is_operation_mode_an_override());

  expect(2, "operationmode", "mode=1");
  core->set_operation_mode(workrave::OperationMode::Suspended);
  EXPECT_EQ(CoreConfig::operation_mode()(), workrave::OperationMode::Suspended);
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_TRUE(core->is_operation_mode_an_override());

  core->remove_operation_mode_override("ov1");
  tick(false, 1);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_EQ(core->get_regular_operation_mode(), workrave::OperationMode::Suspended);
  EXPECT_TRUE(!core->is_operation_mode_an_override());

  core->remove_operation_mode_override("ov2");
  tick(false, 1);

  verify();
}

TEST_F(IntegrationTest, test_operation_mode_autoreset)
{
  init();

  expect(0, "operationmode", "mode=1");
  core->set_operation_mode(workrave::OperationMode::Suspended);
  tick(false, 10);
  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Suspended);

  // Revert to normal after 2 minutes
  expect(10, "operationmode", "mode=2");
  core->set_operation_mode_for(workrave::OperationMode::Quiet, std::chrono::minutes(2));
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 2min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == workrave::utils::TimeSource::get_real_time() + 2min);

  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Quiet);
  expect(130, "operationmode", "mode=0");
  tick(false, 190);
  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 0min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == std::chrono::system_clock::time_point{});

  // Option is not persistent
  expect(200, "operationmode", "mode=1");
  core->set_operation_mode(workrave::OperationMode::Suspended);
  tick(false, 200);

  // Change timed operation mode
  expect(400, "operationmode", "mode=2");
  expect(420, "operationmode", "mode=1");
  expect(540, "operationmode", "mode=0");
  core->set_operation_mode_for(workrave::OperationMode::Quiet, std::chrono::minutes(2));
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 2min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == workrave::utils::TimeSource::get_real_time() + 2min);
  tick(false, 20);
  core->set_operation_mode_for(workrave::OperationMode::Suspended, std::chrono::minutes(2));
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 2min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == workrave::utils::TimeSource::get_real_time() + 2min);
  tick(false, 180);
  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 0min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == std::chrono::system_clock::time_point{});

  // Reduce time
  expect(600, "operationmode", "mode=2");
  expect(680, "operationmode", "mode=0");
  core->set_operation_mode_for(workrave::OperationMode::Quiet, std::chrono::minutes(2));
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 2min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == workrave::utils::TimeSource::get_real_time() + 2min);
  tick(false, 20);
  core->set_operation_mode_for(workrave::OperationMode::Quiet, std::chrono::minutes(1));
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 1min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == workrave::utils::TimeSource::get_real_time() + 1min);
  tick(false, 180);
  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Normal);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 0min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == std::chrono::system_clock::time_point{});

  // Turn off
  expect(800, "operationmode", "mode=2");
  core->set_operation_mode_for(workrave::OperationMode::Quiet, std::chrono::minutes(2));
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 2min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == workrave::utils::TimeSource::get_real_time() + 2min);
  tick(false, 20);
  core->set_operation_mode(workrave::OperationMode::Quiet);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 0min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == std::chrono::system_clock::time_point{});
  tick(false, 170);
  expect(990, "operationmode", "mode=0");
  core->set_operation_mode(workrave::OperationMode::Normal);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 0min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == std::chrono::system_clock::time_point{});
  tick(false, 10);

  // Change to non-timed
  expect(1000, "operationmode", "mode=2");
  expect(1020, "operationmode", "mode=1");
  core->set_operation_mode_for(workrave::OperationMode::Quiet, std::chrono::minutes(2));
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 2min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == workrave::utils::TimeSource::get_real_time() + 2min);
  tick(false, 20);
  core->set_operation_mode(workrave::OperationMode::Suspended);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 0min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == std::chrono::system_clock::time_point{});
  tick(false, 180);

  // Change to non-timed
  expect(1200, "operationmode", "mode=2");
  expect(1220, "operationmode", "mode=0");
  core->set_operation_mode_for(workrave::OperationMode::Quiet, std::chrono::minutes(2));
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 2min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == workrave::utils::TimeSource::get_real_time() + 2min);
  tick(false, 20);
  core->set_operation_mode(workrave::OperationMode::Normal);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 0min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == std::chrono::system_clock::time_point{});
  tick(false, 180);

  verify();
}

TEST_F(IntegrationTest, test_operation_mode_autoreset_daily_reset)
{
  init();

  auto *b = core->get_break(workrave::BREAK_ID_DAILY_LIMIT);

  config->set_value("breaks/micro_pause/enabled", false);
  config->set_value("breaks/rest_break/enabled", false);

  config->set_value("timers/daily_limit/reset_pred", "day/23:00");
  config->set_value("timers/daily_limit/limit", 7200);
  config->set_value("timers/daily_limit/snooze", 600);

  expect(0, "operationmode", "mode=1");
  core->set_operation_mode(workrave::OperationMode::Suspended);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 0min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == std::chrono::system_clock::time_point{});
  tick(true, 10);
  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Suspended);

  expect(10, "operationmode", "mode=2");
  core->set_operation_mode_for(workrave::OperationMode::Quiet, std::chrono::minutes(-1));
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == -1min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == std::chrono::system_clock::time_point{});
  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Quiet);
  expect(3600, "operationmode", "mode=0");

  tick(true, 90);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == -1min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == std::chrono::system_clock::time_point{});

  EXPECT_EQ(b->get_elapsed_time(), 89);

  tick(false, 3500);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == -1min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == std::chrono::system_clock::time_point{});

  EXPECT_EQ(b->get_elapsed_time(), 90);
  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Quiet);

  tick(false, 1);

  EXPECT_EQ(b->get_elapsed_time(), 0);
  EXPECT_EQ(core->get_active_operation_mode(), workrave::OperationMode::Normal);

  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_duration()() == 0min);
  EXPECT_TRUE(CoreConfig::operation_mode_auto_reset_time()() == std::chrono::system_clock::time_point{});

  verify();

  // Revert to normal after 2 minutes
}

TEST_F(IntegrationTest, test_usage_mode)
{
  init();

  expect(0, "usagemode", "mode=1");
  core->set_usage_mode(workrave::UsageMode::Reading);
  core->set_usage_mode(workrave::UsageMode::Reading);

  EXPECT_EQ(core->get_usage_mode(), workrave::UsageMode::Reading);

  expect(0, "usagemode", "mode=0");
  core->set_usage_mode(workrave::UsageMode::Normal);
  core->set_usage_mode(workrave::UsageMode::Normal);

  EXPECT_EQ(core->get_usage_mode(), workrave::UsageMode::Normal);

  expect(0, "usagemode", "mode=1");
  core->set_usage_mode(workrave::UsageMode::Reading);
  core->set_usage_mode(workrave::UsageMode::Reading);

  EXPECT_EQ(core->get_usage_mode(), workrave::UsageMode::Reading);

  verify();
}

TEST_F(IntegrationTest, test_usage_mode_via_settings)
{
  init();

  expect(0, "usagemode", "mode=1");
  config->set_value("general/usage-mode", 1);

  EXPECT_EQ(core->get_usage_mode(), workrave::UsageMode::Reading);

  expect(0, "usagemode", "mode=0");
  config->set_value("general/usage-mode", 0);

  EXPECT_EQ(core->get_usage_mode(), workrave::UsageMode::Normal);

  expect(0, "usagemode", "mode=1");
  config->set_value("general/usage-mode", 1);

  EXPECT_EQ(core->get_usage_mode(), workrave::UsageMode::Reading);

  verify();
}

TEST_F(IntegrationTest, test_reading_mode)
{
  init();

  expect(0, "usagemode", "mode=1");
  core->set_usage_mode(workrave::UsageMode::Reading);

  monitor->notify();
  tick(true, 2);

  tick(false, 2300);

  uint64_t t = 300;
  for (int i = 0; i < 4; i++)
    {
      expect(t, "prelude", "break_id=micro_pause");
      expect(t, "show");
      expect(t, "break_event", "break_id=micro_pause event=ShowPrelude");
      expect(t, "break_event", "break_id=micro_pause event=BreakStart");
      expect(t + 9, "hide");
      expect(t + 9, "break", "break_id=micro_pause break_hint=normal");
      expect(t + 9, "show");
      expect(t + 9, "break_event", "break_id=micro_pause event=ShowBreak");
      expect(t + 20, "hide");
      expect(t + 20, "break_event", "break_id=micro_pause event=BreakTaken");
      expect(t + 20, "break_event", "break_id=micro_pause event=BreakIdle");
      expect(t + 20, "break_event", "break_id=micro_pause event=BreakStop");

      t += 321;
    }

  t = 1580; // Next: 1584;
  expect(t, "prelude", "break_id=rest_break");
  expect(t, "show");
  expect(t, "break_event", "break_id=rest_break event=ShowPrelude");
  expect(t, "break_event", "break_id=rest_break event=BreakStart");
  expect(t + 9, "hide");
  expect(t + 9, "break", "break_id=rest_break break_hint=normal");
  expect(t + 9, "show");
  expect(t + 9, "break_event", "break_id=rest_break event=ShowBreak");
  expect(t + 300, "hide");
  expect(t + 300, "break_event", "break_id=rest_break event=BreakTaken");
  expect(t + 300, "break_event", "break_id=rest_break event=BreakIdle");
  expect(t + 300, "break_event", "break_id=rest_break event=BreakStop");

  verify();
}

TEST_F(IntegrationTest, test_reading_mode_active_during_prelude)
{
  init();

  expect(0, "usagemode", "mode=1");
  core->set_usage_mode(workrave::UsageMode::Reading);

  monitor->notify();

  uint64_t t = 300;
  for (int i = 0; i < 4; i++)
    {
      expect(t, "prelude", "break_id=micro_pause");
      expect(t, "show");
      expect(t, "break_event", "break_id=micro_pause event=ShowPrelude");
      expect(t, "break_event", "break_id=micro_pause event=BreakStart");
      expect(t + 15, "hide");
      expect(t + 15, "break", "break_id=micro_pause break_hint=normal");
      expect(t + 15, "show");
      expect(t + 15, "break_event", "break_id=micro_pause event=ShowBreak");
      expect(t + 35, "hide");
      expect(t + 35, "break_event", "break_id=micro_pause event=BreakTaken");
      expect(t + 35, "break_event", "break_id=micro_pause event=BreakIdle");
      expect(t + 35, "break_event", "break_id=micro_pause event=BreakStop");

      tick(i == 0, 5);
      tick(false, 295);
      tick(false, 5);
      tick(true, 10);
      tick(false, 21);

      t += 336;
    }

  verify();
}

TEST_F(IntegrationTest, test_reading_mode_active_while_no_break_or_prelude_active)
{
  init();

  expect(0, "usagemode", "mode=1");
  core->set_usage_mode(workrave::UsageMode::Reading);

  monitor->notify();

  uint64_t t = 300;
  for (int i = 0; i < 4; i++)
    {
      expect(t, "prelude", "break_id=micro_pause");
      expect(t, "show");
      expect(t, "break_event", "break_id=micro_pause event=ShowPrelude");
      expect(t, "break_event", "break_id=micro_pause event=BreakStart");
      expect(t + 9, "hide");
      expect(t + 9, "break", "break_id=micro_pause break_hint=normal");
      expect(t + 9, "show");
      expect(t + 9, "break_event", "break_id=micro_pause event=ShowBreak");
      expect(t + 20, "hide");
      expect(t + 20, "break_event", "break_id=micro_pause event=BreakTaken");
      expect(t + 20, "break_event", "break_id=micro_pause event=BreakIdle");
      expect(t + 20, "break_event", "break_id=micro_pause event=BreakStop");

      tick(i == 0, 5);
      tick(false, 95);
      tick(true, 50);
      tick(false, 50);
      tick(true, 90);
      tick(false, 31);

      t += 321;
    }

  verify();
}

TEST_F(IntegrationTest, test_reading_mode_active_during_micro_break)
{
  init();

  expect(0, "usagemode", "mode=1");
  core->set_usage_mode(workrave::UsageMode::Reading);

  monitor->notify();
  tick(true, 2);

  tick(false, 1584);

  uint64_t t = 300;
  for (int i = 0; i < 4; i++)
    {
      expect(t, "prelude", "break_id=micro_pause");
      expect(t, "show");
      expect(t, "break_event", "break_id=micro_pause event=ShowPrelude");
      expect(t, "break_event", "break_id=micro_pause event=BreakStart");
      expect(t + 9, "hide");
      expect(t + 9, "break", "break_id=micro_pause break_hint=normal");
      expect(t + 9, "show");
      expect(t + 9, "break_event", "break_id=micro_pause event=ShowBreak");
      expect(t + 20, "hide");
      expect(t + 20, "break_event", "break_id=micro_pause event=BreakTaken");
      expect(t + 20, "break_event", "break_id=micro_pause event=BreakIdle");
      expect(t + 20, "break_event", "break_id=micro_pause event=BreakStop");

      t += 321;
    }

  tick(false, 20);
  tick(true, 20);
  tick(false, 400);

  t = 1580;
  expect(t, "prelude", "break_id=rest_break");
  expect(t, "show");
  expect(t, "break_event", "break_id=rest_break event=ShowPrelude");
  expect(t, "break_event", "break_id=rest_break event=BreakStart");
  expect(t + 9, "hide");
  expect(t + 9, "break", "break_id=rest_break break_hint=normal");
  expect(t + 9, "show");
  expect(t + 9, "break_event", "break_id=rest_break event=ShowBreak");
  expect(t + 320, "hide");
  expect(t + 320, "break_event", "break_id=rest_break event=BreakTaken");
  expect(t + 320, "break_event", "break_id=rest_break event=BreakIdle");
  expect(t + 320, "break_event", "break_id=rest_break event=BreakStop");

  verify();
}

TEST_F(IntegrationTest, test_reading_mode_suspend)
{
  init();

  expect(0, "usagemode", "mode=1");
  core->set_usage_mode(workrave::UsageMode::Reading);

  monitor->notify();
  tick(true, 2);

  tick(false, 1576); // Next: 1580

  uint64_t t = 300;
  for (int i = 0; i < 4; i++)
    {
      expect(t, "prelude", "break_id=micro_pause");
      expect(t, "show");
      expect(t, "break_event", "break_id=micro_pause event=ShowPrelude");
      expect(t, "break_event", "break_id=micro_pause event=BreakStart");
      expect(t + 9, "hide");
      expect(t + 9, "break", "break_id=micro_pause break_hint=normal");
      expect(t + 9, "show");
      expect(t + 9, "break_event", "break_id=micro_pause event=ShowBreak");
      expect(t + 20, "hide");
      expect(t + 20, "break_event", "break_id=micro_pause event=BreakTaken");
      expect(t + 20, "break_event", "break_id=micro_pause event=BreakIdle");
      expect(t + 20, "break_event", "break_id=micro_pause event=BreakStop");

      t += 321;
    }

  expect(1578, "operationmode", "mode=1"); // Next: 1582
  core->set_operation_mode(workrave::OperationMode::Suspended);
  tick(true, 100);
  expect(1678, "operationmode", "mode=0"); // Next: 1682
  core->set_operation_mode(workrave::OperationMode::Normal);
  tick(true, 10);
  tick(false, 1400);

  // TODO: restbreak doesn't restart
  // t = 1680; // Next: 1684
  // expect(t, "prelude", "break_id=rest_break");
  // expect(t, "show");
  // expect(t, "break_event", "break_id=rest_break event=ShowPrelude");
  // expect(t, "break_event", "break_id=rest_break event=BreakStart");
  // expect(t + 9, "hide");
  // expect(t + 9, "break", "break_id=rest_break break_hint=normal");
  // expect(t + 9, "show");
  // expect(t + 9, "break_event", "break_id=rest_break event=ShowBreak");
  // expect(t + 300, "hide");
  // expect(t + 300, "break_event", "break_id=rest_break event=BreakTaken");
  // expect(t + 300, "break_event", "break_id=rest_break event=BreakIdle");
  // expect(t + 300, "break_event", "break_id=rest_break event=BreakStop");

  verify();
}

TEST_F(IntegrationTest, test_user_idle)
{
  init();

  tick(false, 50, [this](int) {
    for (int i = 0; i < workrave::BREAK_ID_SIZEOF; i++)
      {
        auto *b = core->get_break(workrave::BreakId(i));
        EXPECT_TRUE(!b->is_running());
      }
  });

  verify();
}

TEST_F(IntegrationTest, test_user_idle_just_before_first_prelude)
{
  init();

  tick(true, 299);

  verify();
}

TEST_F(IntegrationTest, test_user_active)
{
  init();

  tick(true, 50, [this](int) {
    for (int i = 0; i < workrave::BREAK_ID_SIZEOF; i++)
      {
        auto *b = core->get_break(workrave::BreakId(i));
        EXPECT_TRUE(b->is_running());
      }
  });

  verify();
}

TEST_F(IntegrationTest, test_core_services_and_force_idle)
{
  init();

  EXPECT_TRUE(core->get_statistics() != nullptr);
  EXPECT_TRUE(!core->is_user_active());

  tick(true, 1);
  EXPECT_TRUE(core->is_user_active());

  core->force_idle();
  tick(false, 1);
  EXPECT_TRUE(!core->is_user_active());

  verify();
}

TEST_F(IntegrationTest, test_statistics_counters_and_delete_history)
{
  init();

  auto *statistics = dynamic_cast<workrave::stats::IStatistics *>(core->get_statistics());
  ASSERT_TRUE(statistics != nullptr);
  auto *today = statistics->get_current_day();
  ASSERT_TRUE(today != nullptr);

  auto &prompted = today->break_stats[workrave::BREAK_ID_MICRO_BREAK][workrave::stats::BreakStatValue::Prompted];
  prompted.set(4);
  prompted.add(1);
  prompted.add(3);
  EXPECT_EQ(prompted.get(), 8);

  EXPECT_TRUE(statistics->delete_all_history());
  EXPECT_EQ(history_size(statistics), 0);
  ASSERT_TRUE(statistics->get_current_day() != nullptr);
  EXPECT_EQ(
    statistics->get_current_day()->break_stats[workrave::BREAK_ID_MICRO_BREAK][workrave::stats::BreakStatValue::Prompted].get(),
    0);
}

//! Break counters other than TOTAL_OVERDUE are written straight to the store, so they
//! must be durable even without a save_day()/update() in between.
TEST_F(IntegrationTest, test_statistics_break_counter_is_durable_immediately)
{
  init();

  auto *statistics = dynamic_cast<workrave::stats::IStatistics *>(core->get_statistics());
  ASSERT_TRUE(statistics != nullptr);

  statistics->get_current_day()->break_stats[workrave::BREAK_ID_MICRO_BREAK][workrave::stats::BreakStatValue::Prompted].add(1);

  // Read the store directly, bypassing Statistics's own in-memory current_day, to
  // prove the increment reached the store immediately.
  auto store = workrave::stats::StatisticsStoreFactory::create(workrave::utils::Paths::get_state_directory());
  auto today = store->load_today();
  ASSERT_TRUE(today.has_value());
  EXPECT_EQ(
    today->break_stats[workrave::BREAK_ID_MICRO_BREAK][static_cast<size_t>(workrave::stats::BreakStatValue::Prompted)],
    1);
}

TEST_F(IntegrationTest, test_statistics_load_current_day_and_history)
{
  auto state_directory = workrave::utils::Paths::get_state_directory();
  {
    std::ofstream today_file(state_directory / "todaystats");
    today_file << "WorkRaveStats 4\n"
               << "D 8 5 126 10 20 8 5 126 11 30\n"
               << "B 0 9 1 2 3 4 5 6 7 8 9\n"
               << "m 8 10 20 30 40 50 60 70 80\n"
               << "G 42\n";
  }
  {
    std::ofstream history_file(state_directory / "historystats");
    history_file << "WorkRaveStats 3\n"
                 << "D 3 0 120 0 0 3 0 120 1 0\n"
                 << "M 6 1 2 3 4 5 6\n"
                 << "D 1 0 120 0 0 1 0 120 1 0\n"
                 << "G 11\n"
                 << "D 2 0 120 0 0 2 0 120 1 0\n"
                 << "m 6 7 8 9 10 11 12\n";
  }

  init();

  auto *statistics = dynamic_cast<workrave::stats::IStatistics *>(core->get_statistics());
  ASSERT_TRUE(statistics != nullptr);
  auto *today = statistics->get_current_day();
  ASSERT_TRUE(today != nullptr);
  EXPECT_EQ(today->break_stats[workrave::BREAK_ID_MICRO_BREAK][workrave::stats::BreakStatValue::Prompted].get(), 1);
  EXPECT_EQ(today->total_overdue[workrave::BREAK_ID_MICRO_BREAK].get().count(), 7);
  EXPECT_EQ(today->total_active_time.get().count(), 42);

  EXPECT_EQ(history_size(statistics), 3);

  using namespace std::chrono;

  const Date first{year{2020} / 1 / 1};
  const Date second{year{2020} / 1 / 2};
  const Date third{year{2020} / 1 / 3};

  ASSERT_TRUE(statistics->get_day(first).has_value());
  ASSERT_TRUE(statistics->get_day(second).has_value());
  ASSERT_TRUE(statistics->get_day(third).has_value());
  EXPECT_TRUE(!statistics->get_day(year{2020} / 1 / 4).has_value());

  EXPECT_EQ(statistics->get_day(first)->total_active_time.get().count(), 11);
  EXPECT_EQ(statistics->get_day(second)->total_active_time.get().count(), 7);

  EXPECT_TRUE(statistics->get_first_date() == first);
  EXPECT_TRUE(statistics->get_previous_date(second) == first);
  EXPECT_TRUE(statistics->get_next_date(second) == third);

  // The third day only has a broken 'M' record, which counts as no active time.
  EXPECT_EQ(statistics->get_total_active_time(first, year{2020} / 1 / last).count(), 18);
  EXPECT_EQ(statistics->get_total_active_time(first, second).count(), 18);
  EXPECT_EQ(statistics->get_total_active_time(first, first).count(), 11);
}

TEST_F(IntegrationTest, test_statistics_start_new_day)
{
  init();

  auto *statistics = dynamic_cast<workrave::stats::IStatistics *>(core->get_statistics());
  ASSERT_TRUE(statistics != nullptr);
  ASSERT_TRUE(statistics->delete_all_history());

  auto *today = statistics->get_current_day();
  ASSERT_TRUE(today != nullptr);
  today->break_stats[workrave::BREAK_ID_MICRO_BREAK][workrave::stats::BreakStatValue::Prompted].set(7);

  statistics->start_new_day();
  EXPECT_TRUE(statistics->get_current_day() == today);
  EXPECT_EQ(history_size(statistics), 0);
  EXPECT_EQ(today->break_stats[workrave::BREAK_ID_MICRO_BREAK][workrave::stats::BreakStatValue::Prompted].get(), 7);

  auto rollover = [statistics, this](int days_forward, int prompted) {
    auto *old_day = statistics->get_current_day();
    auto archived = history_size(statistics);
    old_day->start = workrave::stats::local_now();
    const Date old_date = workrave::stats::date_of(*old_day->start);
    old_day->break_stats[workrave::BREAK_ID_MICRO_BREAK][workrave::stats::BreakStatValue::Prompted].set(prompted);
    sim->current_time += std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::days{days_forward}).count();
    statistics->start_new_day();
    // start_new_day() may have freed old_day (and its replacement may even reuse
    // the same address), so the archived day is now only inspected through the
    // store, never through the old_day pointer.

    ASSERT_TRUE(statistics->get_current_day() != nullptr);
    EXPECT_EQ(history_size(statistics), archived + 1);

    auto archived_day = statistics->get_day(old_date);
    ASSERT_TRUE(archived_day.has_value());
    EXPECT_EQ(archived_day->break_stats[workrave::BREAK_ID_MICRO_BREAK][workrave::stats::BreakStatValue::Prompted].get(),
                      prompted);
    EXPECT_EQ(
      statistics->get_current_day()->break_stats[workrave::BREAK_ID_MICRO_BREAK][workrave::stats::BreakStatValue::Prompted].get(),
      0);
  };

  // A day, a month and a year forward; any later date must start a new day.
  rollover(1, 7);
  rollover(40, 8);
  rollover(400, 9);
  EXPECT_TRUE(std::filesystem::is_regular_file(workrave::utils::Paths::get_state_directory() / "statistics.db"));
}

TEST_F(IntegrationTest, test_statistics_day_to_history)
{
  init();

  auto *statistics = dynamic_cast<workrave::stats::IStatistics *>(core->get_statistics());
  ASSERT_TRUE(statistics != nullptr);
  ASSERT_TRUE(statistics->delete_all_history());

  const Date first_day = workrave::stats::date_of(workrave::stats::local_now());

  auto archive_current_day = [statistics, this](int days_forward, int prompted) {
    auto *day = statistics->get_current_day();
    day->start = workrave::stats::local_now();
    day->break_stats[workrave::BREAK_ID_MICRO_BREAK][workrave::stats::BreakStatValue::Prompted].set(prompted);
    sim->current_time += std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::days{days_forward}).count();
    statistics->start_new_day();
  };

  archive_current_day(800, 11);
  EXPECT_EQ(history_size(statistics), 1);

  archive_current_day(400, 12);
  EXPECT_EQ(history_size(statistics), 2);

  std::optional<Date> oldest = statistics->get_first_date();
  ASSERT_TRUE(oldest.has_value());
  EXPECT_TRUE(oldest.value() == first_day);
  auto oldest_day = statistics->get_day(oldest.value());
  ASSERT_TRUE(oldest_day.has_value());
  EXPECT_EQ(oldest_day->break_stats[workrave::BREAK_ID_MICRO_BREAK][workrave::stats::BreakStatValue::Prompted].get(), 11);

  std::optional<Date> next = statistics->get_next_date(oldest.value());
  ASSERT_TRUE(next.has_value());
  EXPECT_TRUE(next.value() == Date{std::chrono::local_days{first_day} + std::chrono::days{800}});
  auto next_day = statistics->get_day(next.value());
  ASSERT_TRUE(next_day.has_value());
  EXPECT_EQ(next_day->break_stats[workrave::BREAK_ID_MICRO_BREAK][workrave::stats::BreakStatValue::Prompted].get(), 12);
}

TEST_F(IntegrationTest, test_load_valid_timer_state)
{
  auto simulated_time = SimulatedTime::create();
  simulated_time->reset();
  const auto now = simulated_time->get_real_time_usec() / 1000000;

  std::ostringstream state;
  state << "WorkRaveState 3\n"
        << now << "\n"
        << "unknown " << now << " 1 0 0 0 0 0 0\n"
        << "micro_pause " << now << " 10 0 0 0 0 0 0\n"
        << "rest_break " << now << " 20 0 0 0 0 0 0\n"
        << "daily_limit " << now << " 30 0 0 0 0 0 0\n";
  init_with_timer_state(state.str(), true);

  EXPECT_EQ(core->get_break(workrave::BREAK_ID_MICRO_BREAK)->get_elapsed_time(), 10);
  EXPECT_EQ(core->get_break(workrave::BREAK_ID_REST_BREAK)->get_elapsed_time(), 20);
  EXPECT_EQ(core->get_break(workrave::BREAK_ID_DAILY_LIMIT)->get_elapsed_time(), 30);
}

TEST_F(IntegrationTest, test_load_invalid_timer_state_header)
{
  init_with_timer_state("NotWorkRaveState 3\n");

  EXPECT_EQ(core->get_break(workrave::BREAK_ID_MICRO_BREAK)->get_elapsed_time(), 0);
}

TEST_F(IntegrationTest, test_load_invalid_timer_state_version)
{
  init_with_timer_state("WorkRaveState 4\n");

  EXPECT_EQ(core->get_break(workrave::BREAK_ID_MICRO_BREAK)->get_elapsed_time(), 0);
}

TEST_F(IntegrationTest, test_load_timer_state_version_below_supported_range)
{
  init_with_timer_state("WorkRaveState 0\n");

  EXPECT_EQ(core->get_break(workrave::BREAK_ID_MICRO_BREAK)->get_elapsed_time(), 0);
}

TEST_F(IntegrationTest, test_load_missing_timer_state)
{
  init_without_timer_state();

  EXPECT_EQ(core->get_break(workrave::BREAK_ID_MICRO_BREAK)->get_elapsed_time(), 0);
}

TEST_F(IntegrationTest, test_user_ignores_first_prelude)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  expect(335, "break_event", "break_id=micro_pause event=BreakIgnored");
  expect(335, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(335, "hide");
  tick(true, 335);
  monitor->notify();
  tick(true, 1);

  verify();
}

TEST_F(IntegrationTest, test_user_takes_break_immediately_after_start_of_first_prelude)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  tick(true, 301);

  expect(309, "hide");
  expect(309, "break", "break_id=micro_pause break_hint=normal");
  expect(309, "show");
  expect(309, "break_event", "break_id=micro_pause event=ShowBreak");

  expect(320, "hide");
  expect(320, "break_event", "break_id=micro_pause event=BreakTaken");
  expect(320, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(320, "break_event", "break_id=micro_pause event=BreakStop");
  tick(false, 40);

  verify();
}

TEST_F(IntegrationTest, test_user_takes_break_halfway_first_prelude)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  tick(true, 315);

  expect(315, "hide");
  expect(315, "break", "break_id=micro_pause break_hint=normal");
  expect(315, "show");
  expect(315, "break_event", "break_id=micro_pause event=ShowBreak");
  tick(false, 10);

  expect(335, "hide");
  expect(335, "break_event", "break_id=micro_pause event=BreakTaken");
  expect(335, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(335, "break_event", "break_id=micro_pause event=BreakStop");
  tick(false, 30);

  verify();
}

TEST_F(IntegrationTest, test_user_takes_break_at_end_of_first_prelude)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  tick(true, 329);

  expect(329, "hide");
  expect(329, "break", "break_id=micro_pause break_hint=normal");
  expect(329, "show");
  expect(329, "break_event", "break_id=micro_pause event=ShowBreak");
  expect(349, "hide");
  expect(349, "break_event", "break_id=micro_pause event=BreakTaken");
  expect(349, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(349, "break_event", "break_id=micro_pause event=BreakStop");
  tick(false, 40);

  verify();
}

TEST_F(IntegrationTest, test_user_takes_break_at_end_of_first_prelude_idle_detect_delayed)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  tick(true, 334);

  expect(334, "hide");
  expect(334, "break", "break_id=micro_pause break_hint=normal");
  expect(334, "show");
  expect(334, "break_event", "break_id=micro_pause event=ShowBreak");

  expect(354, "hide");
  expect(354, "break_event", "break_id=micro_pause event=BreakTaken");
  expect(354, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(354, "break_event", "break_id=micro_pause event=BreakStop");
  tick(false, 40);

  verify();
}

TEST_F(IntegrationTest, test_user_ignores_first_prelude_idle_detect_delayed)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  tick(true, 334);
  monitor->notify();

  expect(334, "hide");
  expect(334, "break_event", "break_id=micro_pause event=BreakIgnored");
  expect(334, "break_event", "break_id=micro_pause event=BreakIdle");

  tick(true, 1);

  verify();
}

TEST_F(IntegrationTest, test_user_takes_break_after_first_prelude_active_during_break)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  tick(true, 310);

  expect(310, "hide");
  expect(310, "break", "break_id=micro_pause break_hint=normal");
  expect(310, "show");
  expect(310, "break_event", "break_id=micro_pause event=ShowBreak");
  tick(false, 10);

  tick(true, 20);

  expect(350, "hide");
  expect(350, "break_event", "break_id=micro_pause event=BreakTaken");
  expect(350, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(350, "break_event", "break_id=micro_pause event=BreakStop");
  tick(false, 30);

  verify();
}

TEST_F(IntegrationTest, test_user_active_during_prelude)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  tick(true, 310);

  expect(310, "hide");
  expect(310, "break", "break_id=micro_pause break_hint=normal");
  expect(310, "show");
  expect(310, "break_event", "break_id=micro_pause event=ShowBreak");
  tick(false, 10);
  tick(true, 10);

  expect(340, "hide");
  expect(340, "break_event", "break_id=micro_pause event=BreakTaken");
  expect(340, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(340, "break_event", "break_id=micro_pause event=BreakStop");
  tick(false, 30);

  verify();
}

TEST_F(IntegrationTest, test_forced_break)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  expect(335, "hide");
  expect(335, "break_event", "break_id=micro_pause event=BreakIgnored");
  expect(335, "break_event", "break_id=micro_pause event=BreakIdle");

  expect(451, "prelude", "break_id=micro_pause");
  expect(451, "show");
  expect(451, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(486, "hide");
  expect(486, "break_event", "break_id=micro_pause event=BreakIgnored");
  expect(486, "break_event", "break_id=micro_pause event=BreakIdle");

  expect(602, "prelude", "break_id=micro_pause");
  expect(602, "show");
  expect(602, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(631, "hide");
  expect(631, "break", "break_id=micro_pause break_hint=normal");
  expect(631, "show");
  expect(631, "break_event", "break_id=micro_pause event=ShowBreak");
  tick(true, 335);
  monitor->notify();
  tick(true, 151);
  monitor->notify();
  tick(true, 300);

  verify();
}

TEST_F(IntegrationTest, test_zero_max_preludes_starts_break_immediately)
{
  init();

  config->set_value("breaks/rest_break/enabled", false);
  config->set_value("breaks/daily_limit/enabled", false);
  config->set_value("timers/micro_pause/limit", 10);
  config->set_value("breaks/micro_pause/max_preludes", 0);

  auto b = core->get_break(workrave::BREAK_ID_MICRO_BREAK);
  EXPECT_TRUE(!core->is_taking());

  tick(true, 11);

  EXPECT_EQ(active_prelude, workrave::BREAK_ID_NONE);
  EXPECT_EQ(active_break, workrave::BREAK_ID_MICRO_BREAK);
  EXPECT_TRUE(b->is_max_preludes_reached());
  EXPECT_TRUE(b->is_taking());
  EXPECT_TRUE(core->is_taking());
}

TEST_F(IntegrationTest, test_unlimited_preludes_never_reaches_maximum)
{
  init();

  config->set_value("breaks/rest_break/enabled", false);
  config->set_value("breaks/daily_limit/enabled", false);
  config->set_value("timers/micro_pause/limit", 10);
  config->set_value("breaks/micro_pause/max_preludes", -1);

  auto b = core->get_break(workrave::BREAK_ID_MICRO_BREAK);
  tick(true, 41);

  EXPECT_TRUE(!b->is_max_preludes_reached());
  EXPECT_TRUE(!b->is_taking());
}

TEST_F(IntegrationTest, test_advance_rest_break_does_not_override_unlimited_micro_break_preludes)
{
  init();

  config->set_value("timers/micro_pause/limit", 10);
  config->set_value("timers/rest_break/limit", 20);
  config->set_value("breaks/micro_pause/max_preludes", -1);

  tick(true, 11);

  EXPECT_TRUE(core->get_break(workrave::BREAK_ID_REST_BREAK)->is_active());
}

TEST_F(IntegrationTest, test_advance_rest_break_keeps_lower_rest_break_max_preludes)
{
  init();

  config->set_value("timers/micro_pause/limit", 10);
  config->set_value("timers/rest_break/limit", 20);
  config->set_value("breaks/micro_pause/max_preludes", 6);
  config->set_value("breaks/rest_break/max_preludes", 1);
  max_preludes = 1;

  tick(true, 11);

  EXPECT_TRUE(core->get_break(workrave::BREAK_ID_REST_BREAK)->is_active());
}

TEST_F(IntegrationTest, test_inactive_break_actions_and_forced_daily_limit)
{
  init();

  auto b = core->get_break(workrave::BREAK_ID_DAILY_LIMIT);
  b->postpone_break();
  b->skip_break();

  forced_break = true;
  core->force_break(workrave::BREAK_ID_DAILY_LIMIT, workrave::BreakHint::UserInitiated);
  EXPECT_TRUE(b->is_taking());

  b->postpone_break();
  EXPECT_TRUE(!b->is_taking());

  b->skip_break();
}

TEST_F(IntegrationTest, test_fake_break_postpone)
{
  init();

  config->set_value("breaks/rest_break/enabled", false);
  auto b = core->get_break(workrave::BREAK_ID_REST_BREAK);

  fake_break = true;
  core->force_break(workrave::BREAK_ID_REST_BREAK, workrave::BreakHint::Normal);
  EXPECT_TRUE(b->is_taking());

  b->postpone_break();
  EXPECT_TRUE(!b->is_taking());
}

TEST_F(IntegrationTest, test_overdue_time)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  expect(315, "hide");
  expect(315, "break", "break_id=micro_pause break_hint=normal");
  expect(315, "show");
  expect(315, "break_event", "break_id=micro_pause event=ShowBreak");
  expect(335, "hide");
  expect(335, "break_event", "break_id=micro_pause event=BreakTaken");
  expect(335, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(335, "break_event", "break_id=micro_pause event=BreakStop");
  tick(true, 315);
  tick(false, 40);

  auto *b = core->get_break(workrave::BREAK_ID_MICRO_BREAK);
  EXPECT_EQ(b->get_total_overdue_time(), 14);

  expect(655, "prelude", "break_id=micro_pause");
  expect(655, "show");
  expect(655, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(655, "break_event", "break_id=micro_pause event=BreakStart");
  expect(670, "hide");
  expect(670, "break", "break_id=micro_pause break_hint=normal");
  expect(670, "show");
  expect(670, "break_event", "break_id=micro_pause event=ShowBreak");
  expect(690, "hide");
  expect(690, "break_event", "break_id=micro_pause event=BreakTaken");
  expect(690, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(690, "break_event", "break_id=micro_pause event=BreakStop");
  tick(true, 315);
  tick(false, 40);

  EXPECT_EQ(b->get_total_overdue_time(), 28);

  verify();
}

TEST_F(IntegrationTest, test_insist_policy_halt)
{
  init();

  config->set_value("breaks/micro_pause/enabled", false);

  expect(1500, "prelude", "break_id=rest_break");
  expect(1500, "show");
  expect(1500, "break_event", "break_id=rest_break event=ShowPrelude");
  expect(1500, "break_event", "break_id=rest_break event=BreakStart");
  tick(true, 1501);

  expect(1509, "hide");
  expect(1509, "break", "break_id=rest_break break_hint=normal");
  expect(1509, "show");
  expect(1509, "break_event", "break_id=rest_break event=ShowBreak");

  auto *rb = core->get_break(workrave::BREAK_ID_REST_BREAK);

  tick(false, 50);

  core->set_insist_policy(InsistPolicy::Halt);

  uint64_t elapsed = rb->get_elapsed_idle_time();
  tick(true, 100, [=](int) { EXPECT_EQ(rb->get_elapsed_idle_time(), elapsed + 1); });
  tick(false, 400);

  expect(1900, "hide");
  expect(1900, "break_event", "break_id=rest_break event=BreakTaken");
  expect(1900, "break_event", "break_id=rest_break event=BreakIdle");
  expect(1900, "break_event", "break_id=rest_break event=BreakStop");

  verify();
}

TEST_F(IntegrationTest, test_insist_policy_reset)
{
  init();

  config->set_value("breaks/micro_pause/enabled", false);

  expect(1500, "prelude", "break_id=rest_break");
  expect(1500, "show");
  expect(1500, "break_event", "break_id=rest_break event=ShowPrelude");
  expect(1500, "break_event", "break_id=rest_break event=BreakStart");
  tick(true, 1501);

  expect(1509, "hide");
  expect(1509, "break", "break_id=rest_break break_hint=normal");
  expect(1509, "show");
  expect(1509, "break_event", "break_id=rest_break event=ShowBreak");

  auto *rb = core->get_break(workrave::BREAK_ID_REST_BREAK);

  tick(false, 50);

  core->set_insist_policy(InsistPolicy::Reset);

  tick(true, 100, [=](int) { EXPECT_EQ(rb->get_elapsed_idle_time(), 0); });
  tick(false, 400);

  expect(1951, "hide");
  expect(1951, "break_event", "break_id=rest_break event=BreakTaken");
  expect(1951, "break_event", "break_id=rest_break event=BreakIdle");
  expect(1951, "break_event", "break_id=rest_break event=BreakStop");

  verify();
}

TEST_F(IntegrationTest, test_insist_policy_ignore)
{
  init();

  config->set_value("breaks/micro_pause/enabled", false);

  expect(1500, "prelude", "break_id=rest_break");
  expect(1500, "show");
  expect(1500, "break_event", "break_id=rest_break event=ShowPrelude");
  expect(1500, "break_event", "break_id=rest_break event=BreakStart");
  tick(true, 1501);

  expect(1509, "hide");
  expect(1509, "break", "break_id=rest_break break_hint=normal");
  expect(1509, "show");
  expect(1509, "break_event", "break_id=rest_break event=ShowBreak");

  tick(false, 50);

  core->set_insist_policy(InsistPolicy::Ignore);

  tick(true, 100);
  tick(false, 400);

  expect(1800, "hide");
  expect(1800, "break_event", "break_id=rest_break event=BreakTaken");
  expect(1800, "break_event", "break_id=rest_break event=BreakIdle");
  expect(1800, "break_event", "break_id=rest_break event=BreakStop");

  verify();
}

TEST_F(IntegrationTest, test_insist_policy_invalid)
{
  init();

  core->set_insist_policy(workrave::InsistPolicy::Invalid);

  verify();
}

TEST_F(IntegrationTest, test_insist_policy_ignore_defrost_while_suspended)
{
  init();

  forced_break = true;
  core->set_insist_policy(workrave::InsistPolicy::Ignore);
  core->force_break(workrave::BREAK_ID_REST_BREAK, workrave::BreakHint::UserInitiated);
  EXPECT_TRUE(core->is_taking());

  core->set_operation_mode(workrave::OperationMode::Suspended);
  EXPECT_TRUE(!core->is_taking());
}

TEST_F(IntegrationTest, test_user_postpones_rest_break)
{
  init();

  config->set_value("breaks/micro_pause/enabled", false);

  expect(1500, "prelude", "break_id=rest_break");
  expect(1500, "show");
  expect(1500, "break_event", "break_id=rest_break event=ShowPrelude");
  expect(1500, "break_event", "break_id=rest_break event=BreakStart");
  tick(true, 1501);

  expect(1509, "hide");
  expect(1509, "break", "break_id=rest_break break_hint=normal");
  expect(1509, "show");
  expect(1509, "break_event", "break_id=rest_break event=ShowBreak");

  expect(1551, "hide");
  expect(1551, "break_event", "break_id=rest_break event=BreakPostponed");
  expect(1551, "break_event", "break_id=rest_break event=BreakIdle");
  expect(1551, "break_event", "break_id=rest_break event=BreakStop");
  tick(false, 50);
  auto *b = core->get_break(workrave::BREAK_ID_REST_BREAK);
  b->postpone_break();
  tick(false, 1);

  expect(1732, "prelude", "break_id=rest_break");
  expect(1732, "show");
  expect(1732, "break_event", "break_id=rest_break event=ShowPrelude");
  expect(1732, "break_event", "break_id=rest_break event=BreakStart");
  tick(true, 200);

  verify();
}

TEST_F(IntegrationTest, test_user_skips_rest_break)
{
  init();

  config->set_value("breaks/micro_pause/enabled", false);

  expect(1500, "prelude", "break_id=rest_break");
  expect(1500, "show");
  expect(1500, "break_event", "break_id=rest_break event=ShowPrelude");
  expect(1500, "break_event", "break_id=rest_break event=BreakStart");
  tick(true, 1501);

  expect(1509, "hide");
  expect(1509, "break", "break_id=rest_break break_hint=normal");
  expect(1509, "show");
  expect(1509, "break_event", "break_id=rest_break event=ShowBreak");

  expect(1551, "hide");
  expect(1551, "break_event", "break_id=rest_break event=BreakSkipped");
  expect(1551, "break_event", "break_id=rest_break event=BreakIdle");
  expect(1551, "break_event", "break_id=rest_break event=BreakStop");
  tick(false, 50);
  auto *b = core->get_break(workrave::BREAK_ID_REST_BREAK);
  b->skip_break();
  tick(false, 1);

  expect(3052, "prelude", "break_id=rest_break");
  expect(3052, "show");
  expect(3052, "break_event", "break_id=rest_break event=ShowPrelude");
  expect(3052, "break_event", "break_id=rest_break event=BreakStart");
  tick(true, 1510);

  verify();
}

TEST_F(IntegrationTest, test_quiet_during_prelude)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  tick(true, 315);

  expect(315, "operationmode", "mode=2");
  expect(315, "break_event", "break_id=micro_pause event=BreakIgnored");
  expect(315, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(315, "break_event", "break_id=micro_pause event=BreakStop");
  core->set_operation_mode(workrave::OperationMode::Quiet);

  expect(315, "hide");
  tick(false, 40);

  verify();
}

TEST_F(IntegrationTest, test_suspended_during_prelude)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  tick(true, 315);

  expect(315, "operationmode", "mode=1");
  expect(315, "break_event", "break_id=micro_pause event=BreakIgnored"); // TODO: why ignored?
  expect(315, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(315, "break_event", "break_id=micro_pause event=BreakStop");
  core->set_operation_mode(workrave::OperationMode::Suspended);

  expect(315, "hide");
  tick(false, 40);

  verify();
}

TEST_F(IntegrationTest, test_suspended_during_break)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  tick(true, 310);

  expect(310, "hide");
  expect(310, "break", "break_id=micro_pause break_hint=normal");
  expect(310, "show");
  expect(310, "break_event", "break_id=micro_pause event=ShowBreak");

  expect(320, "operationmode", "mode=1");
  expect(320, "hide");
  expect(320, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(320, "break_event", "break_id=micro_pause event=BreakStop");
  tick(false, 10);
  core->set_operation_mode(workrave::OperationMode::Suspended);

  tick(false, 30);

  verify();
}

TEST_F(IntegrationTest, test_quiet_during_break)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  tick(true, 310);

  expect(310, "hide");
  expect(310, "break", "break_id=micro_pause break_hint=normal");
  expect(310, "show");
  expect(310, "break_event", "break_id=micro_pause event=ShowBreak");

  expect(320, "operationmode", "mode=2");
  expect(320, "hide");
  expect(320, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(320, "break_event", "break_id=micro_pause event=BreakStop");
  tick(false, 10);
  core->set_operation_mode(workrave::OperationMode::Quiet);

  tick(false, 30);

  verify();
}

TEST_F(IntegrationTest, test_rest_break_now)
{
  init();

  tick(true, 10);
  tick(false, 10);

  forced_break = true;

  core->force_break(workrave::BREAK_ID_REST_BREAK, BreakHint::UserInitiated);
  expect(20, "break", "break_id=rest_break break_hint=userinitiated");
  expect(20, "show");
  expect(20, "break_event", "break_id=rest_break event=ShowBreakForced");
  // expect(20, "break_event", "break_id=rest_break event=BreakStart");

  tick(false, 50);
  tick(false, 260);

  expect(310, "hide");
  expect(310, "break_event", "break_id=rest_break event=BreakTaken");
  expect(310, "break_event", "break_id=rest_break event=BreakIdle");
  expect(310, "break_event", "break_id=rest_break event=BreakStop");

  verify();
}

TEST_F(IntegrationTest, test_rest_break_now_active_during_break)
{
  init();

  tick(true, 10);
  tick(false, 10);

  forced_break = true;

  core->force_break(workrave::BREAK_ID_REST_BREAK, BreakHint::UserInitiated);
  expect(20, "break", "break_id=rest_break break_hint=userinitiated");
  expect(20, "show");
  expect(20, "break_event", "break_id=rest_break event=ShowBreakForced");
  // expect(20, "break_event", "break_id=rest_break event=BreakStart");

  tick(false, 50);
  tick(true, 20);
  tick(false, 260);

  expect(330, "hide");
  expect(330, "break_event", "break_id=rest_break event=BreakTaken");
  expect(330, "break_event", "break_id=rest_break event=BreakIdle");
  expect(330, "break_event", "break_id=rest_break event=BreakStop");

  verify();
}

TEST_F(IntegrationTest, test_rest_break_now_during_microbreak_prelude)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  tick(true, 305);

  forced_break = true;

  core->force_break(workrave::BREAK_ID_REST_BREAK, BreakHint::UserInitiated);
  expect(305, "hide");
  expect(305, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(305, "break_event", "break_id=micro_pause event=BreakIgnored"); // TODO: why
  expect(305, "break_event", "break_id=micro_pause event=BreakStop");

  expect(305, "break", "break_id=rest_break break_hint=userinitiated");
  expect(305, "show");
  expect(305, "break_event", "break_id=rest_break event=ShowBreakForced");

  tick(false, 50);
  tick(false, 260);

  expect(604, "hide");
  expect(604, "break_event", "break_id=rest_break event=BreakTaken");
  expect(604, "break_event", "break_id=rest_break event=BreakIdle");
  expect(604, "break_event", "break_id=rest_break event=BreakStop");

  verify();
}

TEST_F(IntegrationTest, test_rest_break_now_during_microbreak)
{
  init();

  expect(300, "prelude", "break_id=micro_pause");
  expect(300, "show");
  expect(300, "break_event", "break_id=micro_pause event=ShowPrelude");
  expect(300, "break_event", "break_id=micro_pause event=BreakStart");
  tick(true, 310);

  expect(310, "hide");
  expect(310, "break", "break_id=micro_pause break_hint=normal");
  expect(310, "show");
  expect(310, "break_event", "break_id=micro_pause event=ShowBreak");

  tick(false, 10);

  forced_break = true;

  core->force_break(workrave::BREAK_ID_REST_BREAK, BreakHint::UserInitiated);
  expect(320, "hide");
  expect(320, "break_event", "break_id=micro_pause event=BreakIdle");
  expect(320, "break_event", "break_id=micro_pause event=BreakStop");

  expect(320, "break", "break_id=rest_break break_hint=userinitiated");
  expect(320, "show");
  expect(320, "break_event", "break_id=rest_break event=ShowBreakForced");
  // expect(320, "break_event", "break_id=rest_break event=BreakStart");

  tick(false, 50);
  tick(false, 260);

  expect(610, "hide");
  expect(610, "break_event", "break_id=rest_break event=BreakTaken");
  expect(610, "break_event", "break_id=rest_break event=BreakIdle");
  expect(610, "break_event", "break_id=rest_break event=BreakStop");

  verify();
}

TEST_F(IntegrationTest, test_rest_break_now_when_timer_is_disabled)
{
  init();

  config->set_value("breaks/rest_break/enabled", false);

  tick(true, 10);
  tick(false, 10);

  forced_break = true;
  fake_break = true;
  fake_break_delta = 1;

  core->force_break(workrave::BREAK_ID_REST_BREAK, BreakHint::UserInitiated);
  expect(20, "break", "break_id=rest_break break_hint=userinitiated");
  expect(20, "show");
  expect(20, "break_event", "break_id=rest_break event=ShowBreakForced");
  // expect(20, "break_event", "break_id=rest_break event=BreakStart");

  tick(false, 50);
  tick(false, 260);

  expect(319, "hide");
  // expect(319, "break_event", "break_id=rest_break event=BreakTaken");
  expect(319, "break_event", "break_id=rest_break event=BreakIdle");
  expect(319, "break_event", "break_id=rest_break event=BreakStop");

  verify();
}

TEST_F(IntegrationTest, test_rest_break_now_when_break_is_idle)
{
  init();

  tick(false, 500);

  forced_break = true;
  fake_break = true;
  fake_break_delta = 1;

  core->force_break(workrave::BREAK_ID_REST_BREAK, BreakHint::UserInitiated);
  expect(500, "break", "break_id=rest_break break_hint=userinitiated");
  expect(500, "show");
  expect(500, "break_event", "break_id=rest_break event=ShowBreakForced");
  // expect(500, "break_event", "break_id=rest_break event=BreakStart");

  tick(false, 50);
  tick(false, 260);

  expect(799, "hide");
  // expect(789, "break_event", "break_id=rest_break event=BreakTaken");
  expect(799, "break_event", "break_id=rest_break event=BreakIdle");
  expect(799, "break_event", "break_id=rest_break event=BreakStop");

  verify();
}

TEST_F(IntegrationTest, test_advance_imminent_rest_break)
{
  init();

  config->set_value("timers/micro_pause/limit", 300);
  config->set_value("timers/micro_pause/auto_reset", 20);

  config->set_value("timers/rest_break/limit", 330);
  config->set_value("timers/rest_break/auto_reset", 300);

  expect(300, "prelude", "break_id=rest_break");
  expect(300, "show");
  expect(300, "break_event", "break_id=rest_break event=BreakStart");
  expect(300, "break_event", "break_id=rest_break event=ShowPrelude");
  expect(309, "hide");
  expect(309, "break", "break_id=rest_break break_hint=normal");
  expect(309, "show");
  expect(309, "break_event", "break_id=rest_break event=ShowBreak");

  tick(true, 305);
  tick(false, 10);

  verify();
}

TEST_F(IntegrationTest, test_advance_imminent_rest_break_max_prelude_count_taken_from_micro_break)
{
  init();

  config->set_value("timers/micro_pause/limit", 300);
  config->set_value("timers/micro_pause/auto_reset", 20);
  config->set_value("breaks/micro_pause/max_preludes", 1);

  config->set_value("timers/rest_break/limit", 330);
  config->set_value("timers/rest_break/auto_reset", 300);
  config->set_value("breaks/rest_break/max_preludes", 6);

  max_preludes = 1;
  expect(300, "prelude", "break_id=rest_break");
  expect(300, "show");
  expect(300, "break_event", "break_id=rest_break event=BreakStart");
  expect(300, "break_event", "break_id=rest_break event=ShowPrelude");
  expect(329, "hide");
  expect(329, "break", "break_id=rest_break break_hint=normal");
  expect(329, "show");
  expect(329, "break_event", "break_id=rest_break event=ShowBreak");

  tick(true, 350);

  verify();
}

TEST_F(IntegrationTest, test_two_breaks_at_the_same_time)
{
  init();

  config->set_value("timers/micro_pause/limit", 300);
  config->set_value("timers/rest_break/limit", 300);

  expect(300, "prelude", "break_id=rest_break");
  expect(300, "show");
  expect(300, "break_event", "break_id=rest_break event=BreakStart");
  expect(300, "break_event", "break_id=rest_break event=ShowPrelude");
  expect(309, "hide");
  expect(309, "break", "break_id=rest_break break_hint=normal");
  expect(309, "show");
  expect(309, "break_event", "break_id=rest_break event=ShowBreak");

  tick(true, 305);
  tick(false, 50);

  verify();
}

TEST_F(IntegrationTest, test_daily_limit_postpone)
{
  init();

  config->set_value("breaks/micro_pause/enabled", false);
  config->set_value("breaks/rest_break/enabled", false);
  config->set_value("timers/daily_limit/reset_pred", "");
  config->set_value("timers/daily_limit/limit", 7200);
  config->set_value("timers/daily_limit/snooze", 600);

  expect(7200, "prelude", "break_id=daily_limit");
  expect(7200, "show");
  expect(7200, "break_event", "break_id=daily_limit event=BreakStart");
  expect(7200, "break_event", "break_id=daily_limit event=ShowPrelude");
  tick(true, 7201);

  expect(7209, "hide");
  expect(7209, "break", "break_id=daily_limit break_hint=normal");
  expect(7209, "show");
  expect(7209, "break_event", "break_id=daily_limit event=ShowBreak");
  tick(false, 20);

  auto *b = core->get_break(workrave::BREAK_ID_DAILY_LIMIT);
  b->postpone_break();

  expect(7221, "hide");
  expect(7221, "break_event", "break_id=daily_limit event=BreakPostponed");
  expect(7221, "break_event", "break_id=daily_limit event=BreakIdle");
  expect(7221, "break_event", "break_id=daily_limit event=BreakStop");
  tick(false, 1);

  expect(7822, "prelude", "break_id=daily_limit");
  expect(7822, "show");
  expect(7822, "break_event", "break_id=daily_limit event=ShowPrelude");
  expect(7822, "break_event", "break_id=daily_limit event=BreakStart");
  tick(true, 620);

  verify();
}

TEST_F(IntegrationTest, test_daily_limit_disabled)
{
  init();

  config->set_value("breaks/micro_pause/enabled", false);
  config->set_value("breaks/rest_break/enabled", false);
  config->set_value("timers/daily_limit/reset_pred", "");
  config->set_value("timers/daily_limit/limit", 10);
  config->set_value("breaks/daily_limit/enabled", false);

  auto b = core->get_break(workrave::BREAK_ID_DAILY_LIMIT);
  EXPECT_TRUE(!b->is_enabled());

  tick(true, 20);

  EXPECT_TRUE(!b->is_active());
  EXPECT_TRUE(!core->is_taking());

  config->set_value("breaks/daily_limit/enabled", true);
  EXPECT_TRUE(b->is_enabled());

  verify();
}

TEST_F(IntegrationTest, test_daily_limit_skip)
{
  init();

  config->set_value("breaks/micro_pause/enabled", false);
  config->set_value("breaks/rest_break/enabled", false);
  config->set_value("timers/daily_limit/reset_pred", "");
  config->set_value("timers/daily_limit/limit", 7200);
  config->set_value("timers/daily_limit/snooze", 600);

  expect(7200, "prelude", "break_id=daily_limit");
  expect(7200, "show");
  expect(7200, "break_event", "break_id=daily_limit event=BreakStart");
  expect(7200, "break_event", "break_id=daily_limit event=ShowPrelude");
  tick(true, 7201);

  expect(7209, "hide");
  expect(7209, "break", "break_id=daily_limit break_hint=normal");
  expect(7209, "show");
  expect(7209, "break_event", "break_id=daily_limit event=ShowBreak");
  tick(false, 20);

  auto *b = core->get_break(workrave::BREAK_ID_DAILY_LIMIT);
  b->skip_break();

  expect(7221, "hide");
  expect(7221, "break_event", "break_id=daily_limit event=BreakSkipped");
  expect(7221, "break_event", "break_id=daily_limit event=BreakIdle");
  expect(7221, "break_event", "break_id=daily_limit event=BreakStop");
  tick(false, 1);
  tick(true, 3600);

  verify();
}

TEST_F(IntegrationTest, test_daily_limit_regard_micro_break_as_activity)
{
  init();

  config->set_value("breaks/rest_break/enabled", false);
  config->set_value("timers/daily_limit/use_microbreak_activity", true);

  config->set_value("timers/daily_limit/monitor", "micro_pause");

  config->set_value("timers/micro_pause/limit", 300);
  config->set_value("timers/micro_pause/auto_reset", 8000);
  config->set_value("timers/micro_pause/reset_pred", "");
  config->set_value("timers/micro_pause/snooze", 150);

  config->set_value("timers/daily_limit/limit", 7200);
  config->set_value("timers/daily_limit/auto_reset", 0);
  config->set_value("timers/daily_limit/reset_pred", "");
  config->set_value("timers/daily_limit/snooze", 1200);

  tick(true, 1);

  expect(7200, "prelude", "break_id=daily_limit");
  expect(7200, "show");
  expect(7200, "break_event", "break_id=daily_limit event=BreakStart");
  expect(7200, "break_event", "break_id=daily_limit event=ShowPrelude");
  tick(false, 7200);

  expect(7209, "hide");
  expect(7209, "break", "break_id=daily_limit break_hint=normal");
  expect(7209, "show");
  expect(7209, "break_event", "break_id=daily_limit event=ShowBreak");
  tick(false, 20);

  auto *b = core->get_break(workrave::BREAK_ID_DAILY_LIMIT);
  b->skip_break();

  expect(7221, "hide");
  expect(7221, "break_event", "break_id=daily_limit event=BreakSkipped");
  expect(7221, "break_event", "break_id=daily_limit event=BreakIdle");
  expect(7221, "break_event", "break_id=daily_limit event=BreakStop");
  tick(false, 1);
  tick(false, 3600);

  verify();
}

TEST_F(IntegrationTest, test_daily_limit_regard_micro_break_as_activity_off)
{
  init();

  config->set_value("breaks/rest_break/enabled", false);
  config->set_value("timers/daily_limit/use_microbreak_activity", false);

  config->set_value("timers/micro_pause/limit", 300);
  config->set_value("timers/micro_pause/auto_reset", 8000);
  config->set_value("timers/micro_pause/reset_pred", "");
  config->set_value("timers/micro_pause/snooze", 150);

  config->set_value("timers/daily_limit/limit", 7200);
  config->set_value("timers/daily_limit/auto_reset", 0);
  config->set_value("timers/daily_limit/reset_pred", "");
  config->set_value("timers/daily_limit/snooze", 1200);

  tick(true, 1);
  tick(false, 8000);

  verify();
}

TEST_F(IntegrationTest, test_daily_limit_reset)
{
  init();

  auto *b = core->get_break(workrave::BREAK_ID_DAILY_LIMIT);

  config->set_value("breaks/micro_pause/enabled", false);
  config->set_value("breaks/rest_break/enabled", false);

  config->set_value("timers/daily_limit/reset_pred", "day/23:00");
  config->set_value("timers/daily_limit/limit", 7200);
  config->set_value("timers/daily_limit/snooze", 600);

  tick(true, 100);

  EXPECT_EQ(b->get_elapsed_time(), 99);

  tick(false, 3500);

  EXPECT_EQ(b->get_elapsed_time(), 100);

  tick(false, 1);

  EXPECT_EQ(b->get_elapsed_time(), 0);

  verify();
}

// TODO: daily limit + change limit
// TODO: daily limit + statistics reset
// TODO: forced restbreak in reading mode (active state)

