/*
 * Deskflow -- mouse and keyboard sharing utility
 * SPDX-FileCopyrightText: (C) 2012 - 2025 Symless Ltd.
 * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
 */

#pragma once

#include "platform/MSWindowsProcess.h"
#include "platform/MSWindowsSession.h"

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <memory>
#include <mutex>
#include <optional>
#include <string>

typedef VOID(WINAPI *SendSas)(BOOL asUser);

class Thread;
class FileLogOutputter;

/**
 * @brief Monitors and controls a core process on Windows, elevating if necessary.
 */
class MSWindowsWatchdog
{
  enum class ProcessState
  {
    Idle,
    StartScheduled,
    StartPending,
    StopPending,
    Running
  };

public:
  explicit MSWindowsWatchdog(bool foreground);
  ~MSWindowsWatchdog() = default;

  /**
   * @brief Start threads for main loop and and output loop.
   */
  void startAsync();

  /**
   * @brief Set the command to run and whether to elevate the process.
   */
  void setProcessConfig(const std::string_view &command, bool elevate);

  /**
   * @brief Stop the main loop and output loop threads.
   */
  void stop();

  /**
   * @return True if the process is running.
   */
  bool isProcessRunning();

  /**
   * @brief Set the file log outputter.
   *
   * Outputter is not adopted by the watchdog, so the caller must manage the memory.
   *
   * Standard out/error from the launched core process is written to the file log outputter.
   */
  void setFileLogOutputter(FileLogOutputter *outputter);

private:
  /**
   * @brief Monitor the process state and start/stop the process as necessary.
   */
  void mainLoop(void *);

  /**
   * @brief Monitor the process standard out/error and write to the file log outputter.
   */
  void outputLoop(void *);

  /**
   * @brief Stops any core processes which were not started by the watchdog.
   */
  void shutdownExistingProcesses();

  /**
   * @brief Duplicates the process token for the given process.
   *
   * Required for starting a process in the user session; when we start an elevated process
   * to ensure that it has access to secure processes, such as the login screen, we duplicate
   * the token of an existing process that has the necessary access such as `winlogon.exe`.
   *
   * @param process The process to duplicate the token from (typically `winlogon.exe`).
   */
  HANDLE duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security);

  /**
   * @brief Get a security token for the user session.
   *
   * Checks to see if logonui.exe is running or if the `elevatedToken` arg is true,
   * which indicates either we're in a secure user session or we need an elevated token.
   * If either case is true, it duplicates the token from `winlogon.exe`.
   */
  HANDLE getUserToken(LPSECURITY_ATTRIBUTES security, bool elevatedToken);

  /**
   * @brief Start the core process, elevating if necessary.
   */
  void startProcess();

  /**
   * @brief Controls whether the process should restart immediately or delay start.
   */
  void handleStartError(const std::string_view &message = "");

  /**
   * @brief Init the output read pipe for standard out/error.
   */
  void initOutputReadPipe();

  /**
   * @brief Init the SendSAS function, used for Ctrl+Alt+Del emulation.
   */
  void initSasFunc();

  /**
   * @brief Send a SAS (Secure Attention Sequence) for Ctrl+Alt+Del emulation.
   */
  void sendSas() const;

  /**
   * @brief Re-run the process to get the active desktop name.
   *
   * It is necessary to run a utility process because the daemon runs in session 0, which does not
   * have access to the active desktop, and so cannot query it's name.
   *
   * @return std::string The name of the active desktop.
   */
  std::string runActiveDesktopUtility();

  /**
   * @brief Convert the process state enum to a string (useful for logging).
   */
  static std::string processStateToString(ProcessState state);

private:
  Thread *m_thread = nullptr;
  bool m_running = true;
  HANDLE m_outputWritePipe = nullptr;
  HANDLE m_outputReadPipe = nullptr;
  Thread *m_outputThread = nullptr;
  bool m_elevateProcess = false;
  MSWindowsSession m_session;
  int m_startFailures = 0;
  FileLogOutputter *m_fileLogOutputter = nullptr;
  bool m_foreground = false;
  std::string m_activeDesktop = "";
  std::unique_ptr<deskflow::platform::MSWindowsProcess> m_process;
  std::optional<double> m_nextStartTime = std::nullopt;
  ProcessState m_processState = ProcessState::Idle;
  std::string m_command = "";
  SendSas m_sendSasFunc = nullptr;
  std::mutex m_processStateMutex;
};
