// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <thread>
#include "flutter/shell/platform/windows/flutter_windows_engine.h"

#include "flutter/fml/logging.h"
#include "flutter/fml/macros.h"
#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
#include "flutter/shell/platform/windows/flutter_windows_view.h"
#include "flutter/shell/platform/windows/public/flutter_windows.h"
#include "flutter/shell/platform/windows/testing/egl/mock_manager.h"
#include "flutter/shell/platform/windows/testing/engine_modifier.h"
#include "flutter/shell/platform/windows/testing/flutter_windows_engine_builder.h"
#include "flutter/shell/platform/windows/testing/mock_platform_view_manager.h"
#include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
#include "flutter/shell/platform/windows/testing/mock_windows_proc_table.h"
#include "flutter/shell/platform/windows/testing/test_keyboard.h"
#include "flutter/shell/platform/windows/testing/windows_test.h"
#include "flutter/shell/platform/windows/testing/windows_test_config_builder.h"
#include "flutter/third_party/accessibility/ax/platform/ax_platform_node_win.h"
#include "fml/synchronization/waitable_event.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

// winbase.h defines GetCurrentTime as a macro.
#undef GetCurrentTime

namespace {
// Process the next win32 message if there is one. This can be used to
// pump the Windows platform thread task runner.
void PumpMessage() {
  ::MSG msg;
  if (::GetMessage(&msg, nullptr, 0, 0)) {
    ::TranslateMessage(&msg);
    ::DispatchMessage(&msg);
  }
}
}  // namespace

namespace flutter {
namespace testing {

using ::testing::NiceMock;
using ::testing::Return;

class FlutterWindowsEngineTest : public WindowsTest {};

// The engine can be run without any views.
TEST_F(FlutterWindowsEngineTest, RunHeadless) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();

  EngineModifier modifier(engine.get());
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };

  ASSERT_TRUE(engine->Run());
  ASSERT_EQ(engine->view(kImplicitViewId), nullptr);
  ASSERT_EQ(engine->view(123), nullptr);
}

TEST_F(FlutterWindowsEngineTest, RunDoesExpectedInitialization) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  builder.AddDartEntrypointArgument("arg1");
  builder.AddDartEntrypointArgument("arg2");

  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  EngineModifier modifier(engine.get());

  // The engine should be run with expected configuration values.
  bool run_called = false;
  modifier.embedder_api().Run = MOCK_ENGINE_PROC(
      Run, ([&run_called, engine_instance = engine.get()](
                size_t version, const FlutterRendererConfig* config,
                const FlutterProjectArgs* args, void* user_data,
                FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) {
        run_called = true;
        *engine_out = reinterpret_cast<FLUTTER_API_SYMBOL(FlutterEngine)>(1);

        EXPECT_EQ(version, FLUTTER_ENGINE_VERSION);
        EXPECT_NE(config, nullptr);
        // We have an EGL manager, so this should be using OpenGL.
        EXPECT_EQ(config->type, kOpenGL);
        EXPECT_EQ(user_data, engine_instance);
        // Spot-check arguments.
        EXPECT_NE(args->assets_path, nullptr);
        EXPECT_NE(args->icu_data_path, nullptr);
        EXPECT_EQ(args->dart_entrypoint_argc, 2U);
        EXPECT_EQ(strcmp(args->dart_entrypoint_argv[0], "arg1"), 0);
        EXPECT_EQ(strcmp(args->dart_entrypoint_argv[1], "arg2"), 0);
        EXPECT_NE(args->platform_message_callback, nullptr);
        EXPECT_NE(args->custom_task_runners, nullptr);
        EXPECT_NE(args->custom_task_runners->thread_priority_setter, nullptr);
        EXPECT_EQ(args->custom_dart_entrypoint, nullptr);
        EXPECT_NE(args->vsync_callback, nullptr);
        EXPECT_EQ(args->update_semantics_callback, nullptr);
        EXPECT_NE(args->update_semantics_callback2, nullptr);
        EXPECT_EQ(args->update_semantics_node_callback, nullptr);
        EXPECT_EQ(args->update_semantics_custom_action_callback, nullptr);
        EXPECT_NE(args->view_focus_change_request_callback, nullptr);

        args->custom_task_runners->thread_priority_setter(
            FlutterThreadPriority::kRaster);
        EXPECT_EQ(GetThreadPriority(GetCurrentThread()),
                  THREAD_PRIORITY_ABOVE_NORMAL);
        return kSuccess;
      }));
  // Accessibility updates must do nothing when the embedder engine is mocked
  modifier.embedder_api().UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
      UpdateAccessibilityFeatures,
      [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
         FlutterAccessibilityFeature flags) { return kSuccess; });

  // It should send locale info.
  bool update_locales_called = false;
  modifier.embedder_api().UpdateLocales = MOCK_ENGINE_PROC(
      UpdateLocales,
      ([&update_locales_called](auto engine, const FlutterLocale** locales,
                                size_t locales_count) {
        update_locales_called = true;

        EXPECT_GT(locales_count, 0);
        EXPECT_NE(locales, nullptr);

        return kSuccess;
      }));

  // And it should send initial settings info.
  bool settings_message_sent = false;
  modifier.embedder_api().SendPlatformMessage = MOCK_ENGINE_PROC(
      SendPlatformMessage,
      ([&settings_message_sent](auto engine, auto message) {
        if (std::string(message->channel) == std::string("flutter/settings")) {
          settings_message_sent = true;
        }

        return kSuccess;
      }));

  // And it should send display info.
  bool notify_display_update_called = false;
  modifier.SetFrameInterval(16600000);  // 60 fps.
  modifier.embedder_api().NotifyDisplayUpdate = MOCK_ENGINE_PROC(
      NotifyDisplayUpdate,
      ([&notify_display_update_called, engine_instance = engine.get()](
           FLUTTER_API_SYMBOL(FlutterEngine) raw_engine,
           const FlutterEngineDisplaysUpdateType update_type,
           const FlutterEngineDisplay* embedder_displays,
           size_t display_count) {
        EXPECT_EQ(update_type, kFlutterEngineDisplaysUpdateTypeStartup);
        EXPECT_EQ(display_count, 1);

        FlutterEngineDisplay display = embedder_displays[0];

        EXPECT_EQ(display.display_id, 0);
        EXPECT_EQ(display.single_display, true);
        EXPECT_EQ(std::floor(display.refresh_rate), 60.0);

        notify_display_update_called = true;
        return kSuccess;
      }));

  // Set the EGL manager to !nullptr to test ANGLE rendering.
  modifier.SetEGLManager(std::make_unique<egl::MockManager>());

  engine->Run();

  EXPECT_TRUE(run_called);
  EXPECT_TRUE(update_locales_called);
  EXPECT_TRUE(settings_message_sent);
  EXPECT_TRUE(notify_display_update_called);

  // Ensure that deallocation doesn't call the actual Shutdown with the bogus
  // engine pointer that the overridden Run returned.
  modifier.embedder_api().Shutdown = [](auto engine) { return kSuccess; };
  modifier.ReleaseEGLManager();
}

TEST_F(FlutterWindowsEngineTest, ConfiguresFrameVsync) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  EngineModifier modifier(engine.get());
  bool on_vsync_called = false;

  modifier.embedder_api().GetCurrentTime =
      MOCK_ENGINE_PROC(GetCurrentTime, ([]() -> uint64_t { return 1; }));
  modifier.embedder_api().OnVsync = MOCK_ENGINE_PROC(
      OnVsync,
      ([&on_vsync_called, engine_instance = engine.get()](
           FLUTTER_API_SYMBOL(FlutterEngine) engine, intptr_t baton,
           uint64_t frame_start_time_nanos, uint64_t frame_target_time_nanos) {
        EXPECT_EQ(baton, 1);
        EXPECT_EQ(frame_start_time_nanos, 16600000);
        EXPECT_EQ(frame_target_time_nanos, 33200000);
        on_vsync_called = true;
        return kSuccess;
      }));
  modifier.SetStartTime(0);
  modifier.SetFrameInterval(16600000);

  engine->OnVsync(1);

  EXPECT_TRUE(on_vsync_called);
}

TEST_F(FlutterWindowsEngineTest, RunWithoutANGLEUsesSoftware) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  EngineModifier modifier(engine.get());

  modifier.embedder_api().NotifyDisplayUpdate =
      MOCK_ENGINE_PROC(NotifyDisplayUpdate,
                       ([engine_instance = engine.get()](
                            FLUTTER_API_SYMBOL(FlutterEngine) raw_engine,
                            const FlutterEngineDisplaysUpdateType update_type,
                            const FlutterEngineDisplay* embedder_displays,
                            size_t display_count) { return kSuccess; }));

  // The engine should be run with expected configuration values.
  bool run_called = false;
  modifier.embedder_api().Run = MOCK_ENGINE_PROC(
      Run, ([&run_called, engine_instance = engine.get()](
                size_t version, const FlutterRendererConfig* config,
                const FlutterProjectArgs* args, void* user_data,
                FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) {
        run_called = true;
        *engine_out = reinterpret_cast<FLUTTER_API_SYMBOL(FlutterEngine)>(1);
        // We don't have an EGL Manager, so we should be using software.
        EXPECT_EQ(config->type, kSoftware);
        return kSuccess;
      }));
  // Accessibility updates must do nothing when the embedder engine is mocked
  modifier.embedder_api().UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
      UpdateAccessibilityFeatures,
      [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
         FlutterAccessibilityFeature flags) { return kSuccess; });

  // Stub out UpdateLocales and SendPlatformMessage as we don't have a fully
  // initialized engine instance.
  modifier.embedder_api().UpdateLocales = MOCK_ENGINE_PROC(
      UpdateLocales, ([](auto engine, const FlutterLocale** locales,
                         size_t locales_count) { return kSuccess; }));
  modifier.embedder_api().SendPlatformMessage =
      MOCK_ENGINE_PROC(SendPlatformMessage,
                       ([](auto engine, auto message) { return kSuccess; }));

  // Set the EGL manager to nullptr to test software fallback path.
  modifier.SetEGLManager(nullptr);

  engine->Run();

  EXPECT_TRUE(run_called);

  // Ensure that deallocation doesn't call the actual Shutdown with the bogus
  // engine pointer that the overridden Run returned.
  modifier.embedder_api().Shutdown = [](auto engine) { return kSuccess; };
}

TEST_F(FlutterWindowsEngineTest, RunWithoutANGLEOnImpellerFailsToStart) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  builder.SetSwitches({"--enable-impeller=true"});
  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  EngineModifier modifier(engine.get());

  modifier.embedder_api().NotifyDisplayUpdate =
      MOCK_ENGINE_PROC(NotifyDisplayUpdate,
                       ([engine_instance = engine.get()](
                            FLUTTER_API_SYMBOL(FlutterEngine) raw_engine,
                            const FlutterEngineDisplaysUpdateType update_type,
                            const FlutterEngineDisplay* embedder_displays,
                            size_t display_count) { return kSuccess; }));

  // Accessibility updates must do nothing when the embedder engine is mocked
  modifier.embedder_api().UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
      UpdateAccessibilityFeatures,
      [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
         FlutterAccessibilityFeature flags) { return kSuccess; });

  // Stub out UpdateLocales and SendPlatformMessage as we don't have a fully
  // initialized engine instance.
  modifier.embedder_api().UpdateLocales = MOCK_ENGINE_PROC(
      UpdateLocales, ([](auto engine, const FlutterLocale** locales,
                         size_t locales_count) { return kSuccess; }));
  modifier.embedder_api().SendPlatformMessage =
      MOCK_ENGINE_PROC(SendPlatformMessage,
                       ([](auto engine, auto message) { return kSuccess; }));

  // Set the EGL manager to nullptr to test software fallback path.
  modifier.SetEGLManager(nullptr);

  EXPECT_FALSE(engine->Run());
}

TEST_F(FlutterWindowsEngineTest, SendPlatformMessageWithoutResponse) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  EngineModifier modifier(engine.get());

  const char* channel = "test";
  const std::vector<uint8_t> test_message = {1, 2, 3, 4};

  // Without a response, SendPlatformMessage should be a simple pass-through.
  bool called = false;
  modifier.embedder_api().SendPlatformMessage = MOCK_ENGINE_PROC(
      SendPlatformMessage, ([&called, test_message](auto engine, auto message) {
        called = true;
        EXPECT_STREQ(message->channel, "test");
        EXPECT_EQ(message->message_size, test_message.size());
        EXPECT_EQ(memcmp(message->message, test_message.data(),
                         message->message_size),
                  0);
        EXPECT_EQ(message->response_handle, nullptr);
        return kSuccess;
      }));

  engine->SendPlatformMessage(channel, test_message.data(), test_message.size(),
                              nullptr, nullptr);
  EXPECT_TRUE(called);
}

TEST_F(FlutterWindowsEngineTest, PlatformMessageRoundTrip) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  builder.SetDartEntrypoint("hiPlatformChannels");

  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  EngineModifier modifier(engine.get());
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };

  auto binary_messenger =
      std::make_unique<BinaryMessengerImpl>(engine->messenger());

  engine->Run();
  bool did_call_callback = false;
  bool did_call_reply = false;
  bool did_call_dart_reply = false;
  std::string channel = "hi";
  binary_messenger->SetMessageHandler(
      channel,
      [&did_call_callback, &did_call_dart_reply](
          const uint8_t* message, size_t message_size, BinaryReply reply) {
        if (message_size == 5) {
          EXPECT_EQ(message[0], static_cast<uint8_t>('h'));
          char response[] = {'b', 'y', 'e'};
          reply(reinterpret_cast<uint8_t*>(response), 3);
          did_call_callback = true;
        } else {
          EXPECT_EQ(message_size, 3);
          EXPECT_EQ(message[0], static_cast<uint8_t>('b'));
          did_call_dart_reply = true;
        }
      });
  char payload[] = {'h', 'e', 'l', 'l', 'o'};
  binary_messenger->Send(
      channel, reinterpret_cast<uint8_t*>(payload), 5,
      [&did_call_reply](const uint8_t* reply, size_t reply_size) {
        EXPECT_EQ(reply_size, 5);
        EXPECT_EQ(reply[0], static_cast<uint8_t>('h'));
        did_call_reply = true;
      });
  // Rely on timeout mechanism in CI.
  while (!did_call_callback || !did_call_reply || !did_call_dart_reply) {
    engine->task_runner()->ProcessTasks();
  }
}

TEST_F(FlutterWindowsEngineTest, PlatformMessageRespondOnDifferentThread) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  builder.SetDartEntrypoint("hiPlatformChannels");

  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();

  EngineModifier modifier(engine.get());
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };

  auto binary_messenger =
      std::make_unique<BinaryMessengerImpl>(engine->messenger());

  engine->Run();
  bool did_call_callback = false;
  bool did_call_reply = false;
  bool did_call_dart_reply = false;
  std::string channel = "hi";
  std::unique_ptr<std::thread> reply_thread;
  binary_messenger->SetMessageHandler(
      channel,
      [&did_call_callback, &did_call_dart_reply, &reply_thread](
          const uint8_t* message, size_t message_size, BinaryReply reply) {
        if (message_size == 5) {
          EXPECT_EQ(message[0], static_cast<uint8_t>('h'));
          reply_thread.reset(new std::thread([reply = std::move(reply)]() {
            char response[] = {'b', 'y', 'e'};
            reply(reinterpret_cast<uint8_t*>(response), 3);
          }));
          did_call_callback = true;
        } else {
          EXPECT_EQ(message_size, 3);
          EXPECT_EQ(message[0], static_cast<uint8_t>('b'));
          did_call_dart_reply = true;
        }
      });
  char payload[] = {'h', 'e', 'l', 'l', 'o'};
  binary_messenger->Send(
      channel, reinterpret_cast<uint8_t*>(payload), 5,
      [&did_call_reply](const uint8_t* reply, size_t reply_size) {
        EXPECT_EQ(reply_size, 5);
        EXPECT_EQ(reply[0], static_cast<uint8_t>('h'));
        did_call_reply = true;
      });
  // Rely on timeout mechanism in CI.
  while (!did_call_callback || !did_call_reply || !did_call_dart_reply) {
    engine->task_runner()->ProcessTasks();
  }
  ASSERT_TRUE(reply_thread);
  reply_thread->join();
}

TEST_F(FlutterWindowsEngineTest, SendPlatformMessageWithResponse) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  EngineModifier modifier(engine.get());

  const char* channel = "test";
  const std::vector<uint8_t> test_message = {1, 2, 3, 4};
  auto* dummy_response_handle =
      reinterpret_cast<FlutterPlatformMessageResponseHandle*>(5);
  const FlutterDesktopBinaryReply reply_handler = [](auto... args) {};
  void* reply_user_data = reinterpret_cast<void*>(6);

  // When a response is requested, a handle should be created, passed as part
  // of the message, and then released.
  bool create_response_handle_called = false;
  modifier.embedder_api().PlatformMessageCreateResponseHandle =
      MOCK_ENGINE_PROC(
          PlatformMessageCreateResponseHandle,
          ([&create_response_handle_called, &reply_handler, reply_user_data,
            dummy_response_handle](auto engine, auto reply, auto user_data,
                                   auto response_handle) {
            create_response_handle_called = true;
            EXPECT_EQ(reply, reply_handler);
            EXPECT_EQ(user_data, reply_user_data);
            EXPECT_NE(response_handle, nullptr);
            *response_handle = dummy_response_handle;
            return kSuccess;
          }));
  bool release_response_handle_called = false;
  modifier.embedder_api().PlatformMessageReleaseResponseHandle =
      MOCK_ENGINE_PROC(
          PlatformMessageReleaseResponseHandle,
          ([&release_response_handle_called, dummy_response_handle](
               auto engine, auto response_handle) {
            release_response_handle_called = true;
            EXPECT_EQ(response_handle, dummy_response_handle);
            return kSuccess;
          }));
  bool send_message_called = false;
  modifier.embedder_api().SendPlatformMessage = MOCK_ENGINE_PROC(
      SendPlatformMessage, ([&send_message_called, test_message,
                             dummy_response_handle](auto engine, auto message) {
        send_message_called = true;
        EXPECT_STREQ(message->channel, "test");
        EXPECT_EQ(message->message_size, test_message.size());
        EXPECT_EQ(memcmp(message->message, test_message.data(),
                         message->message_size),
                  0);
        EXPECT_EQ(message->response_handle, dummy_response_handle);
        return kSuccess;
      }));

  engine->SendPlatformMessage(channel, test_message.data(), test_message.size(),
                              reply_handler, reply_user_data);
  EXPECT_TRUE(create_response_handle_called);
  EXPECT_TRUE(release_response_handle_called);
  EXPECT_TRUE(send_message_called);
}

TEST_F(FlutterWindowsEngineTest, DispatchSemanticsAction) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  EngineModifier modifier(engine.get());

  bool called = false;
  std::string message = "Hello";
  modifier.embedder_api().SendSemanticsAction = MOCK_ENGINE_PROC(
      SendSemanticsAction, ([&called, &message](auto engine, auto info) {
        called = true;
        EXPECT_EQ(info->view_id, 456);
        EXPECT_EQ(info->node_id, 42);
        EXPECT_EQ(info->action, kFlutterSemanticsActionDismiss);
        EXPECT_EQ(memcmp(info->data, message.c_str(), message.size()), 0);
        EXPECT_EQ(info->data_length, message.size());
        return kSuccess;
      }));

  auto data = fml::MallocMapping::Copy(message.c_str(), message.size());
  engine->DispatchSemanticsAction(456, 42, kFlutterSemanticsActionDismiss,
                                  std::move(data));
  EXPECT_TRUE(called);
}

TEST_F(FlutterWindowsEngineTest, SetsThreadPriority) {
  WindowsPlatformThreadPrioritySetter(FlutterThreadPriority::kBackground);
  EXPECT_EQ(GetThreadPriority(GetCurrentThread()),
            THREAD_PRIORITY_BELOW_NORMAL);

  WindowsPlatformThreadPrioritySetter(FlutterThreadPriority::kDisplay);
  EXPECT_EQ(GetThreadPriority(GetCurrentThread()),
            THREAD_PRIORITY_ABOVE_NORMAL);

  WindowsPlatformThreadPrioritySetter(FlutterThreadPriority::kRaster);
  EXPECT_EQ(GetThreadPriority(GetCurrentThread()),
            THREAD_PRIORITY_ABOVE_NORMAL);

  // FlutterThreadPriority::kNormal does not change thread priority, reset to 0
  // here.
  SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);

  WindowsPlatformThreadPrioritySetter(FlutterThreadPriority::kNormal);
  EXPECT_EQ(GetThreadPriority(GetCurrentThread()), THREAD_PRIORITY_NORMAL);
}

TEST_F(FlutterWindowsEngineTest, AddPluginRegistrarDestructionCallback) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  EngineModifier modifier(engine.get());

  MockEmbedderApiForKeyboard(modifier,
                             std::make_shared<MockKeyResponseController>());

  engine->Run();

  // Verify that destruction handlers don't overwrite each other.
  int result1 = 0;
  int result2 = 0;
  engine->AddPluginRegistrarDestructionCallback(
      [](FlutterDesktopPluginRegistrarRef ref) {
        auto result = reinterpret_cast<int*>(ref);
        *result = 1;
      },
      reinterpret_cast<FlutterDesktopPluginRegistrarRef>(&result1));
  engine->AddPluginRegistrarDestructionCallback(
      [](FlutterDesktopPluginRegistrarRef ref) {
        auto result = reinterpret_cast<int*>(ref);
        *result = 2;
      },
      reinterpret_cast<FlutterDesktopPluginRegistrarRef>(&result2));

  engine->Stop();
  EXPECT_EQ(result1, 1);
  EXPECT_EQ(result2, 2);
}

TEST_F(FlutterWindowsEngineTest, ScheduleFrame) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  EngineModifier modifier(engine.get());

  bool called = false;
  modifier.embedder_api().ScheduleFrame =
      MOCK_ENGINE_PROC(ScheduleFrame, ([&called](auto engine) {
                         called = true;
                         return kSuccess;
                       }));

  engine->ScheduleFrame();
  EXPECT_TRUE(called);
}

TEST_F(FlutterWindowsEngineTest, SetNextFrameCallback) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  EngineModifier modifier(engine.get());

  bool called = false;
  modifier.embedder_api().SetNextFrameCallback = MOCK_ENGINE_PROC(
      SetNextFrameCallback, ([&called](auto engine, auto callback, auto data) {
        called = true;
        return kSuccess;
      }));

  engine->SetNextFrameCallback([]() {});
  EXPECT_TRUE(called);
}

TEST_F(FlutterWindowsEngineTest, GetExecutableName) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  EXPECT_EQ(engine->GetExecutableName(), "flutter_windows_unittests.exe");
}

// Ensure that after setting or resetting the high contrast feature,
// the corresponding status flag can be retrieved from the engine.
TEST_F(FlutterWindowsEngineTest, UpdateHighContrastFeature) {
  auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
  EXPECT_CALL(*windows_proc_table, GetHighContrastEnabled)
      .WillOnce(Return(true))
      .WillOnce(Return(false));

  FlutterWindowsEngineBuilder builder{GetContext()};
  builder.SetWindowsProcTable(windows_proc_table);
  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  EngineModifier modifier(engine.get());

  std::optional<FlutterAccessibilityFeature> engine_flags;
  modifier.embedder_api().UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
      UpdateAccessibilityFeatures, ([&engine_flags](auto engine, auto flags) {
        engine_flags = flags;
        return kSuccess;
      }));
  modifier.embedder_api().SendPlatformMessage = MOCK_ENGINE_PROC(
      SendPlatformMessage,
      [](auto engine, const auto message) { return kSuccess; });

  // 1: High contrast is enabled.
  engine->UpdateHighContrastMode();

  EXPECT_TRUE(engine->high_contrast_enabled());
  EXPECT_TRUE(engine_flags.has_value());
  EXPECT_TRUE(
      engine_flags.value() &
      FlutterAccessibilityFeature::kFlutterAccessibilityFeatureHighContrast);

  // 2: High contrast is disabled.
  engine_flags.reset();
  engine->UpdateHighContrastMode();

  EXPECT_FALSE(engine->high_contrast_enabled());
  EXPECT_TRUE(engine_flags.has_value());
  EXPECT_FALSE(
      engine_flags.value() &
      FlutterAccessibilityFeature::kFlutterAccessibilityFeatureHighContrast);
}

TEST_F(FlutterWindowsEngineTest, PostRasterThreadTask) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  EngineModifier modifier(engine.get());

  modifier.embedder_api().PostRenderThreadTask = MOCK_ENGINE_PROC(
      PostRenderThreadTask, ([](auto engine, auto callback, auto context) {
        callback(context);
        return kSuccess;
      }));

  bool called = false;
  engine->PostRasterThreadTask([&called]() { called = true; });

  EXPECT_TRUE(called);
}

class MockFlutterWindowsView : public FlutterWindowsView {
 public:
  MockFlutterWindowsView(FlutterWindowsEngine* engine,
                         std::unique_ptr<WindowBindingHandler> wbh)
      : FlutterWindowsView(kImplicitViewId, engine, std::move(wbh)) {}
  ~MockFlutterWindowsView() {}

  MOCK_METHOD(void,
              NotifyWinEventWrapper,
              (ui::AXPlatformNodeWin*, ax::mojom::Event),
              (override));
  MOCK_METHOD(HWND, GetWindowHandle, (), (const, override));
  MOCK_METHOD(bool, Focus, (), (override));

 private:
  FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsView);
};

// Verify the view is notified of accessibility announcements.
TEST_F(FlutterWindowsEngineTest, AccessibilityAnnouncement) {
  auto& context = GetContext();
  WindowsConfigBuilder builder{context};
  builder.SetDartEntrypoint("sendAccessibilityAnnouncement");

  bool done = false;
  auto native_entry =
      CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; });
  context.AddNativeFunction("Signal", native_entry);

  EnginePtr engine{builder.RunHeadless()};
  ASSERT_NE(engine, nullptr);

  ui::AXPlatformNodeDelegateBase parent_delegate;
  AlertPlatformNodeDelegate delegate{parent_delegate};

  auto window_binding_handler =
      std::make_unique<NiceMock<MockWindowBindingHandler>>();
  EXPECT_CALL(*window_binding_handler, GetAlertDelegate)
      .WillOnce(Return(&delegate));

  auto windows_engine = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
  MockFlutterWindowsView view{windows_engine,
                              std::move(window_binding_handler)};
  EngineModifier modifier{windows_engine};
  modifier.SetImplicitView(&view);

  windows_engine->UpdateSemanticsEnabled(true);

  EXPECT_CALL(view, NotifyWinEventWrapper).Times(1);

  // Rely on timeout mechanism in CI.
  while (!done) {
    windows_engine->task_runner()->ProcessTasks();
  }
}

// Verify the app can send accessibility announcements while in headless mode.
TEST_F(FlutterWindowsEngineTest, AccessibilityAnnouncementHeadless) {
  auto& context = GetContext();
  WindowsConfigBuilder builder{context};
  builder.SetDartEntrypoint("sendAccessibilityAnnouncement");

  bool done = false;
  auto native_entry =
      CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; });
  context.AddNativeFunction("Signal", native_entry);

  EnginePtr engine{builder.RunHeadless()};
  ASSERT_NE(engine, nullptr);

  auto windows_engine = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
  windows_engine->UpdateSemanticsEnabled(true);

  // Rely on timeout mechanism in CI.
  while (!done) {
    windows_engine->task_runner()->ProcessTasks();
  }
}

// Verify the engine does not crash if it receives an accessibility event
// it does not support yet.
TEST_F(FlutterWindowsEngineTest, AccessibilityTooltip) {
  fml::testing::LogCapture log_capture;

  auto& context = GetContext();
  WindowsConfigBuilder builder{context};
  builder.SetDartEntrypoint("sendAccessibilityTooltipEvent");

  bool done = false;
  auto native_entry =
      CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; });
  context.AddNativeFunction("Signal", native_entry);

  ViewControllerPtr controller{builder.Run()};
  ASSERT_NE(controller, nullptr);

  auto engine = FlutterDesktopViewControllerGetEngine(controller.get());
  auto windows_engine = reinterpret_cast<FlutterWindowsEngine*>(engine);
  windows_engine->UpdateSemanticsEnabled(true);

  // Rely on timeout mechanism in CI.
  while (!done) {
    windows_engine->task_runner()->ProcessTasks();
  }

  // Verify no error was logged.
  // Regression test for:
  // https://github.com/flutter/flutter/issues/144274
  EXPECT_EQ(log_capture.str().find("tooltip"), std::string::npos);
}

class MockWindowsLifecycleManager : public WindowsLifecycleManager {
 public:
  MockWindowsLifecycleManager(FlutterWindowsEngine* engine)
      : WindowsLifecycleManager(engine) {}
  virtual ~MockWindowsLifecycleManager() {}

  MOCK_METHOD(
      void,
      Quit,
      (std::optional<HWND>, std::optional<WPARAM>, std::optional<LPARAM>, UINT),
      (override));
  MOCK_METHOD(void, DispatchMessage, (HWND, UINT, WPARAM, LPARAM), (override));
  MOCK_METHOD(bool, IsLastWindowOfProcess, (), (override));
  MOCK_METHOD(void, SetLifecycleState, (AppLifecycleState), (override));

  void BeginProcessingLifecycle() override {
    WindowsLifecycleManager::BeginProcessingLifecycle();
    if (begin_processing_callback) {
      begin_processing_callback();
    }
  }

  std::function<void()> begin_processing_callback = nullptr;
};

TEST_F(FlutterWindowsEngineTest, TestExit) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  builder.SetDartEntrypoint("exitTestExit");
  bool finished = false;

  auto engine = builder.Build();
  auto window_binding_handler =
      std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));

  EngineModifier modifier(engine.get());
  modifier.SetImplicitView(&view);
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
  EXPECT_CALL(*handler, Quit)
      .WillOnce([&finished](std::optional<HWND> hwnd,
                            std::optional<WPARAM> wparam,
                            std::optional<LPARAM> lparam,
                            UINT exit_code) { finished = exit_code == 0; });
  EXPECT_CALL(*handler, IsLastWindowOfProcess).WillRepeatedly(Return(true));
  modifier.SetLifecycleManager(std::move(handler));

  engine->lifecycle_manager()->BeginProcessingExit();

  engine->Run();

  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
                                                               0);

  // The test will only succeed when this while loop exits. Otherwise it will
  // timeout.
  while (!finished) {
    engine->task_runner()->ProcessTasks();
  }
}

TEST_F(FlutterWindowsEngineTest, TestExitCancel) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  builder.SetDartEntrypoint("exitTestCancel");
  bool did_call = false;

  auto engine = builder.Build();
  auto window_binding_handler =
      std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));

  EngineModifier modifier(engine.get());
  modifier.SetImplicitView(&view);
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
  EXPECT_CALL(*handler, IsLastWindowOfProcess).WillRepeatedly(Return(true));
  EXPECT_CALL(*handler, Quit).Times(0);
  modifier.SetLifecycleManager(std::move(handler));
  engine->lifecycle_manager()->BeginProcessingExit();

  auto binary_messenger =
      std::make_unique<BinaryMessengerImpl>(engine->messenger());
  binary_messenger->SetMessageHandler(
      "flutter/platform", [&did_call](const uint8_t* message,
                                      size_t message_size, BinaryReply reply) {
        std::string contents(message, message + message_size);
        EXPECT_NE(contents.find("\"method\":\"System.exitApplication\""),
                  std::string::npos);
        EXPECT_NE(contents.find("\"type\":\"required\""), std::string::npos);
        EXPECT_NE(contents.find("\"exitCode\":0"), std::string::npos);
        did_call = true;
        char response[] = "";
        reply(reinterpret_cast<uint8_t*>(response), 0);
      });

  engine->Run();

  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
                                                               0);

  while (!did_call) {
    engine->task_runner()->ProcessTasks();
  }
}

// Flutter consumes the first WM_CLOSE message to allow the app to cancel the
// exit. If the app does not cancel the exit, Flutter synthesizes a second
// WM_CLOSE message.
TEST_F(FlutterWindowsEngineTest, TestExitSecondCloseMessage) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  builder.SetDartEntrypoint("exitTestExit");
  bool second_close = false;

  auto engine = builder.Build();
  auto window_binding_handler =
      std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));

  EngineModifier modifier(engine.get());
  modifier.SetImplicitView(&view);
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
  EXPECT_CALL(*handler, IsLastWindowOfProcess).WillOnce(Return(true));
  EXPECT_CALL(*handler, Quit)
      .WillOnce([handler_ptr = handler.get()](
                    std::optional<HWND> hwnd, std::optional<WPARAM> wparam,
                    std::optional<LPARAM> lparam, UINT exit_code) {
        handler_ptr->WindowsLifecycleManager::Quit(hwnd, wparam, lparam,
                                                   exit_code);
      });
  EXPECT_CALL(*handler, DispatchMessage)
      .WillRepeatedly(
          [&engine](HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
            engine->window_proc_delegate_manager()->OnTopLevelWindowProc(
                hwnd, msg, wparam, lparam);
          });
  modifier.SetLifecycleManager(std::move(handler));
  engine->lifecycle_manager()->BeginProcessingExit();

  engine->Run();

  // This delegate will be registered after the lifecycle manager, so it will be
  // called only when a message is not consumed by the lifecycle manager. This
  // should be called on the second, synthesized WM_CLOSE message that the
  // lifecycle manager posts.
  engine->window_proc_delegate_manager()->RegisterTopLevelWindowProcDelegate(
      [](HWND hwnd, UINT message, WPARAM wpar, LPARAM lpar, void* user_data,
         LRESULT* result) {
        switch (message) {
          case WM_CLOSE: {
            bool* called = reinterpret_cast<bool*>(user_data);
            *called = true;
            return true;
          }
        }
        return false;
      },
      reinterpret_cast<void*>(&second_close));

  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
                                                               0);

  while (!second_close) {
    engine->task_runner()->ProcessTasks();
  }
}

TEST_F(FlutterWindowsEngineTest, TestExitCloseMultiWindow) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  builder.SetDartEntrypoint("exitTestExit");
  bool finished = false;

  auto engine = builder.Build();
  auto window_binding_handler =
      std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));

  EngineModifier modifier(engine.get());
  modifier.SetImplicitView(&view);
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
  EXPECT_CALL(*handler, IsLastWindowOfProcess).WillOnce([&finished]() {
    finished = true;
    return false;
  });
  // Quit should not be called when there is more than one window.
  EXPECT_CALL(*handler, Quit).Times(0);
  modifier.SetLifecycleManager(std::move(handler));
  engine->lifecycle_manager()->BeginProcessingExit();

  engine->Run();

  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
                                                               0);

  while (!finished) {
    engine->task_runner()->ProcessTasks();
  }
}

TEST_F(FlutterWindowsEngineTest, LifecycleManagerDisabledByDefault) {
  FlutterWindowsEngineBuilder builder{GetContext()};

  auto engine = builder.Build();
  auto window_binding_handler =
      std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));

  EngineModifier modifier(engine.get());
  modifier.SetImplicitView(&view);
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
  EXPECT_CALL(*handler, IsLastWindowOfProcess).Times(0);
  modifier.SetLifecycleManager(std::move(handler));

  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
                                                               0);
}

TEST_F(FlutterWindowsEngineTest, EnableApplicationLifecycle) {
  FlutterWindowsEngineBuilder builder{GetContext()};

  auto engine = builder.Build();
  auto window_binding_handler =
      std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));

  EngineModifier modifier(engine.get());
  modifier.SetImplicitView(&view);
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
  EXPECT_CALL(*handler, IsLastWindowOfProcess).WillOnce(Return(false));
  modifier.SetLifecycleManager(std::move(handler));
  engine->lifecycle_manager()->BeginProcessingExit();

  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
                                                               0);
}

TEST_F(FlutterWindowsEngineTest, ApplicationLifecycleExternalWindow) {
  FlutterWindowsEngineBuilder builder{GetContext()};

  auto engine = builder.Build();
  auto window_binding_handler =
      std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));

  EngineModifier modifier(engine.get());
  modifier.SetImplicitView(&view);
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
  EXPECT_CALL(*handler, IsLastWindowOfProcess).WillOnce(Return(false));
  modifier.SetLifecycleManager(std::move(handler));
  engine->lifecycle_manager()->BeginProcessingExit();

  engine->lifecycle_manager()->ExternalWindowMessage(0, WM_CLOSE, 0, 0);
}

TEST_F(FlutterWindowsEngineTest, LifecycleStateTransition) {
  FlutterWindowsEngineBuilder builder{GetContext()};

  auto engine = builder.Build();
  auto window_binding_handler =
      std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));

  EngineModifier modifier(engine.get());
  modifier.SetImplicitView(&view);
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
  engine->Run();

  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(
      (HWND)1, WM_SIZE, SIZE_RESTORED, 0);

  while (engine->lifecycle_manager()->IsUpdateStateScheduled()) {
    PumpMessage();
  }

  EXPECT_EQ(engine->lifecycle_manager()->GetLifecycleState(),
            AppLifecycleState::kInactive);

  engine->lifecycle_manager()->OnWindowStateEvent((HWND)1,
                                                  WindowStateEvent::kFocus);

  while (engine->lifecycle_manager()->IsUpdateStateScheduled()) {
    PumpMessage();
  }

  EXPECT_EQ(engine->lifecycle_manager()->GetLifecycleState(),
            AppLifecycleState::kResumed);

  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(
      (HWND)1, WM_SIZE, SIZE_MINIMIZED, 0);

  while (engine->lifecycle_manager()->IsUpdateStateScheduled()) {
    PumpMessage();
  }

  EXPECT_EQ(engine->lifecycle_manager()->GetLifecycleState(),
            AppLifecycleState::kHidden);

  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(
      (HWND)1, WM_SIZE, SIZE_RESTORED, 0);

  while (engine->lifecycle_manager()->IsUpdateStateScheduled()) {
    PumpMessage();
  }

  EXPECT_EQ(engine->lifecycle_manager()->GetLifecycleState(),
            AppLifecycleState::kInactive);
}

TEST_F(FlutterWindowsEngineTest, ExternalWindowMessage) {
  FlutterWindowsEngineBuilder builder{GetContext()};

  auto engine = builder.Build();
  auto window_binding_handler =
      std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));

  EngineModifier modifier(engine.get());
  modifier.SetImplicitView(&view);
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
  // Sets lifecycle state to resumed.
  engine->Run();

  // Ensure HWND(1) is in the set of visible windows before hiding it.
  engine->ProcessExternalWindowMessage(reinterpret_cast<HWND>(1), WM_SHOWWINDOW,
                                       TRUE, NULL);
  engine->ProcessExternalWindowMessage(reinterpret_cast<HWND>(1), WM_SHOWWINDOW,
                                       FALSE, NULL);

  while (engine->lifecycle_manager()->IsUpdateStateScheduled()) {
    PumpMessage();
  }

  EXPECT_EQ(engine->lifecycle_manager()->GetLifecycleState(),
            AppLifecycleState::kHidden);
}

TEST_F(FlutterWindowsEngineTest, InnerWindowHidden) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  HWND outer = reinterpret_cast<HWND>(1);
  HWND inner = reinterpret_cast<HWND>(2);

  auto engine = builder.Build();
  auto window_binding_handler =
      std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
  ON_CALL(view, GetWindowHandle).WillByDefault([=]() { return inner; });

  EngineModifier modifier(engine.get());
  modifier.SetImplicitView(&view);
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
  // Sets lifecycle state to resumed.
  engine->Run();

  // Show both top-level and Flutter window.
  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(
      outer, WM_SHOWWINDOW, TRUE, NULL);
  view.OnWindowStateEvent(inner, WindowStateEvent::kShow);
  view.OnWindowStateEvent(inner, WindowStateEvent::kFocus);

  while (engine->lifecycle_manager()->IsUpdateStateScheduled()) {
    PumpMessage();
  }

  EXPECT_EQ(engine->lifecycle_manager()->GetLifecycleState(),
            AppLifecycleState::kResumed);

  // Hide Flutter window, but not top level window.
  view.OnWindowStateEvent(inner, WindowStateEvent::kHide);

  while (engine->lifecycle_manager()->IsUpdateStateScheduled()) {
    PumpMessage();
  }

  // The top-level window is still visible, so we ought not enter hidden state.
  EXPECT_EQ(engine->lifecycle_manager()->GetLifecycleState(),
            AppLifecycleState::kInactive);
}

TEST_F(FlutterWindowsEngineTest, EnableLifecycleState) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  builder.SetDartEntrypoint("enableLifecycleTest");
  bool finished = false;

  auto engine = builder.Build();
  auto window_binding_handler =
      std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));

  EngineModifier modifier(engine.get());
  modifier.SetImplicitView(&view);
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
  EXPECT_CALL(*handler, SetLifecycleState)
      .WillRepeatedly([handler_ptr = handler.get()](AppLifecycleState state) {
        handler_ptr->WindowsLifecycleManager::SetLifecycleState(state);
      });
  modifier.SetLifecycleManager(std::move(handler));

  auto binary_messenger =
      std::make_unique<BinaryMessengerImpl>(engine->messenger());
  // Mark the test only as completed on receiving an inactive state message.
  binary_messenger->SetMessageHandler(
      "flutter/unittest", [&finished](const uint8_t* message,
                                      size_t message_size, BinaryReply reply) {
        std::string contents(message, message + message_size);
        EXPECT_NE(contents.find("AppLifecycleState.inactive"),
                  std::string::npos);
        finished = true;
        char response[] = "";
        reply(reinterpret_cast<uint8_t*>(response), 0);
      });

  engine->Run();

  // Test that setting the state before enabling lifecycle does nothing.
  HWND hwnd = reinterpret_cast<HWND>(1);
  view.OnWindowStateEvent(hwnd, WindowStateEvent::kShow);
  view.OnWindowStateEvent(hwnd, WindowStateEvent::kHide);
  EXPECT_FALSE(finished);

  // Test that we can set the state afterwards.

  engine->lifecycle_manager()->BeginProcessingLifecycle();
  view.OnWindowStateEvent(hwnd, WindowStateEvent::kShow);

  while (!finished) {
    engine->task_runner()->ProcessTasks();
  }
}

TEST_F(FlutterWindowsEngineTest, LifecycleStateToFrom) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  builder.SetDartEntrypoint("enableLifecycleToFrom");
  bool enabled_lifecycle = false;
  bool dart_responded = false;

  auto engine = builder.Build();
  auto window_binding_handler =
      std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));

  EngineModifier modifier(engine.get());
  modifier.SetImplicitView(&view);
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
  EXPECT_CALL(*handler, SetLifecycleState)
      .WillRepeatedly([handler_ptr = handler.get()](AppLifecycleState state) {
        handler_ptr->WindowsLifecycleManager::SetLifecycleState(state);
      });
  handler->begin_processing_callback = [&]() { enabled_lifecycle = true; };
  modifier.SetLifecycleManager(std::move(handler));

  auto binary_messenger =
      std::make_unique<BinaryMessengerImpl>(engine->messenger());
  binary_messenger->SetMessageHandler(
      "flutter/unittest",
      [&](const uint8_t* message, size_t message_size, BinaryReply reply) {
        std::string contents(message, message + message_size);
        EXPECT_NE(contents.find("AppLifecycleState."), std::string::npos);
        dart_responded = true;
        char response[] = "";
        reply(reinterpret_cast<uint8_t*>(response), 0);
      });

  engine->Run();

  while (!enabled_lifecycle) {
    engine->task_runner()->ProcessTasks();
  }

  HWND hwnd = reinterpret_cast<HWND>(1);
  view.OnWindowStateEvent(hwnd, WindowStateEvent::kShow);
  view.OnWindowStateEvent(hwnd, WindowStateEvent::kHide);

  while (!dart_responded) {
    engine->task_runner()->ProcessTasks();
  }
}

TEST_F(FlutterWindowsEngineTest, ChannelListenedTo) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  builder.SetDartEntrypoint("enableLifecycleToFrom");

  auto engine = builder.Build();
  auto window_binding_handler =
      std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));

  EngineModifier modifier(engine.get());
  modifier.SetImplicitView(&view);
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };

  bool lifecycle_began = false;
  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
  handler->begin_processing_callback = [&]() { lifecycle_began = true; };
  modifier.SetLifecycleManager(std::move(handler));

  engine->Run();

  while (!lifecycle_began) {
    engine->task_runner()->ProcessTasks();
  }
}

TEST_F(FlutterWindowsEngineTest, ReceivePlatformViewMessage) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  builder.SetDartEntrypoint("sendCreatePlatformViewMethod");
  auto engine = builder.Build();

  EngineModifier modifier{engine.get()};
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };

  bool received_call = false;

  auto manager = std::make_unique<MockPlatformViewManager>(engine.get());
  EXPECT_CALL(*manager, AddPlatformView)
      .WillOnce([&](PlatformViewId id, std::string_view type_name) {
        received_call = true;
        return true;
      });
  modifier.SetPlatformViewPlugin(std::move(manager));

  engine->Run();

  while (!received_call) {
    engine->task_runner()->ProcessTasks();
  }
}

TEST_F(FlutterWindowsEngineTest, AddViewFailureDoesNotHang) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  auto engine = builder.Build();

  EngineModifier modifier{engine.get()};

  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
  modifier.embedder_api().AddView = MOCK_ENGINE_PROC(
      AddView,
      [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
         const FlutterAddViewInfo* info) { return kInternalInconsistency; });

  ASSERT_TRUE(engine->Run());

  // Create the first view. This is the implicit view and isn't added to the
  // engine.
  auto implicit_window = std::make_unique<NiceMock<MockWindowBindingHandler>>();

  std::unique_ptr<FlutterWindowsView> implicit_view =
      engine->CreateView(std::move(implicit_window));

  EXPECT_TRUE(implicit_view);

  // Create a second view. The embedder attempts to add it to the engine.
  auto second_window = std::make_unique<NiceMock<MockWindowBindingHandler>>();

  EXPECT_DEBUG_DEATH(engine->CreateView(std::move(second_window)),
                     "FlutterEngineAddView returned an unexpected result");
}

TEST_F(FlutterWindowsEngineTest, RemoveViewFailureDoesNotHang) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  builder.SetDartEntrypoint("sendCreatePlatformViewMethod");
  auto engine = builder.Build();

  EngineModifier modifier{engine.get()};

  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
  modifier.embedder_api().RemoveView = MOCK_ENGINE_PROC(
      RemoveView,
      [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
         const FlutterRemoveViewInfo* info) { return kInternalInconsistency; });

  ASSERT_TRUE(engine->Run());
  EXPECT_DEBUG_DEATH(engine->RemoveView(123),
                     "FlutterEngineRemoveView returned an unexpected result");
}

TEST_F(FlutterWindowsEngineTest, MergedUIThread) {
  auto& context = GetContext();
  WindowsConfigBuilder builder{context};
  builder.SetDartEntrypoint("mergedUIThread");
  builder.SetUIThreadPolicy(FlutterDesktopUIThreadPolicy::RunOnPlatformThread);

  std::optional<std::thread::id> ui_thread_id;

  auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
    ui_thread_id = std::this_thread::get_id();
  });
  context.AddNativeFunction("Signal", native_entry);

  EnginePtr engine{builder.RunHeadless()};
  while (!ui_thread_id) {
    PumpMessage();
  }
  ASSERT_EQ(*ui_thread_id, std::this_thread::get_id());
}

TEST_F(FlutterWindowsEngineTest, OnViewFocusChangeRequest) {
  FlutterWindowsEngineBuilder builder{GetContext()};
  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
  auto window_binding_handler =
      std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));

  EngineModifier modifier(engine.get());
  modifier.SetImplicitView(&view);

  FlutterViewFocusChangeRequest request;
  request.view_id = kImplicitViewId;

  EXPECT_CALL(view, Focus()).WillOnce(Return(true));
  modifier.OnViewFocusChangeRequest(&request);
}

TEST_F(FlutterWindowsEngineTest, UpdateSemanticsMultiView) {
  auto& context = GetContext();
  WindowsConfigBuilder builder{context};
  builder.SetDartEntrypoint("sendSemanticsTreeInfo");

  // Setup: a signal for when we have send out all of our semantics updates
  bool done = false;
  auto native_entry =
      CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; });
  context.AddNativeFunction("Signal", native_entry);

  // Setup: Create the engine and two views + enable semantics
  EnginePtr engine{builder.RunHeadless()};
  ASSERT_NE(engine, nullptr);

  auto window_binding_handler1 =
      std::make_unique<NiceMock<MockWindowBindingHandler>>();
  auto window_binding_handler2 =
      std::make_unique<NiceMock<MockWindowBindingHandler>>();

  // The following mocks are required by
  // FlutterWindowsView::CreateWindowMetricsEvent so that we create a valid
  // view.
  EXPECT_CALL(*window_binding_handler1, GetPhysicalWindowBounds)
      .WillRepeatedly(testing::Return(PhysicalWindowBounds{100, 100}));
  EXPECT_CALL(*window_binding_handler1, GetDpiScale)
      .WillRepeatedly(testing::Return(96.0));
  EXPECT_CALL(*window_binding_handler2, GetPhysicalWindowBounds)
      .WillRepeatedly(testing::Return(PhysicalWindowBounds{200, 200}));
  EXPECT_CALL(*window_binding_handler2, GetDpiScale)
      .WillRepeatedly(testing::Return(96.0));

  auto windows_engine = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
  EngineModifier modifier{windows_engine};
  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };

  // We want to avoid adding an implicit view as the first view
  modifier.SetNextViewId(kImplicitViewId + 1);

  auto view1 = windows_engine->CreateView(std::move(window_binding_handler1));
  auto view2 = windows_engine->CreateView(std::move(window_binding_handler2));

  // Act: UpdateSemanticsEnabled will trigger the semantics updates
  // to get sent.
  windows_engine->UpdateSemanticsEnabled(true);

  while (!done) {
    windows_engine->task_runner()->ProcessTasks();
  }

  auto accessibility_bridge1 = view1->accessibility_bridge().lock();
  auto accessibility_bridge2 = view2->accessibility_bridge().lock();

  // Expect: that the semantics trees are updated with their
  // respective nodes.
  while (
      !accessibility_bridge1->GetPlatformNodeFromTree(view1->view_id() + 1)) {
    windows_engine->task_runner()->ProcessTasks();
  }

  while (
      !accessibility_bridge2->GetPlatformNodeFromTree(view2->view_id() + 1)) {
    windows_engine->task_runner()->ProcessTasks();
  }

  // Rely on timeout mechanism in CI.
  auto tree1 = accessibility_bridge1->GetTree();
  auto tree2 = accessibility_bridge2->GetTree();
  EXPECT_NE(tree1->GetFromId(view1->view_id() + 1), nullptr);
  EXPECT_NE(tree2->GetFromId(view2->view_id() + 1), nullptr);
}

}  // namespace testing
}  // namespace flutter
