#include <algorithm>
#include <chrono>
#include <cstring>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <thread>
#include <vector>

#include <display_stream_id.h>
#include "localization.h"
#include "platform.h"
#include "platform/video_renderer.h"
#include "rd_log.h"
#include "runtime/gui_runtime.h"

namespace crossdesk {

namespace {
constexpr auto kPresenceProbeTimeout = std::chrono::seconds(5);
}  // namespace

void GuiRuntime::HandleConnectionStatusChange() {
  if (signal_connected_ && peer_ && need_to_send_recent_connections_) {
    if (!recent_connection_ids_.empty()) {
      nlohmann::json j;
      j["type"] = "recent_connections_presence";
      j["user_id"] = client_id_;
      j["devices"] = nlohmann::json::array();
      for (const auto& id : recent_connection_ids_) {
        std::string pure_id = id;
        size_t pos_y = pure_id.find('Y');
        size_t pos_n = pure_id.find('N');
        size_t pos = std::string::npos;
        if (pos_y != std::string::npos &&
            (pos_n == std::string::npos || pos_y < pos_n)) {
          pos = pos_y;
        } else if (pos_n != std::string::npos) {
          pos = pos_n;
        }
        if (pos != std::string::npos) {
          pure_id = pure_id.substr(0, pos);
        }
        j["devices"].push_back(pure_id);
      }
      auto s = j.dump();
      SendSignalMessage(peer_, s.data(), s.size());
    }
  }
  need_to_send_recent_connections_ = false;
}

void GuiRuntime::HandlePendingPresenceProbe() {
  bool has_action = false;
  bool should_connect = false;
  bool remember_password = false;
  std::string remote_id;
  std::string password;

  {
    std::lock_guard<std::mutex> lock(pending_presence_probe_mutex_);
    if (!pending_presence_probe_ || !pending_presence_result_ready_) {
      return;
    }

    has_action = true;
    should_connect = pending_presence_online_;
    remote_id = pending_presence_remote_id_;
    password = pending_presence_password_;
    remember_password = pending_presence_remember_password_;

    pending_presence_probe_ = false;
    pending_presence_result_ready_ = false;
    pending_presence_online_ = false;
    pending_presence_remote_id_.clear();
    pending_presence_password_.clear();
    pending_presence_remember_password_ = false;
  }

  if (!has_action) {
    return;
  }

  if (should_connect) {
    ConnectTo(remote_id, password.c_str(), remember_password, true);
    return;
  }

  offline_warning_text_ =
      localization::device_offline[localization_language_index_];
  show_offline_warning_window_ = true;
}

void GuiRuntime::HandlePresenceProbeTimeout() {
  const auto now = std::chrono::steady_clock::now();

  bool presence_probe_timed_out = false;
  std::string presence_remote_id;
  {
    std::lock_guard<std::mutex> lock(pending_presence_probe_mutex_);
    if (pending_presence_probe_ && !pending_presence_result_ready_ &&
        now - pending_presence_probe_started_at_ >= kPresenceProbeTimeout) {
      presence_probe_timed_out = true;
      presence_remote_id = pending_presence_remote_id_;
      pending_presence_probe_ = false;
      pending_presence_result_ready_ = false;
      pending_presence_online_ = false;
      pending_presence_remote_id_.clear();
      pending_presence_password_.clear();
      pending_presence_remember_password_ = false;
    }
  }

  if (presence_probe_timed_out) {
    offline_warning_text_ =
        localization::device_offline[localization_language_index_];
    show_offline_warning_window_ = true;
    LOG_WARN("Presence probe timed out for [{}]", presence_remote_id);
  }
}

void GuiRuntime::HandleServerControllerDisconnected(
    const std::string& remote_id, const char* reason) {
  keyboard_.ReleaseRemotePressedKeys(remote_id, reason);
  {
    std::lock_guard lock(remote_pointer_input_mutex_);
    last_remote_pointer_input_time_.erase(remote_id);
  }

  bool has_connected_controller = false;
  bool has_web_controller = false;
  std::string remaining_controller_id;
  {
    std::unique_lock lock(connection_status_mutex_);
    connection_status_.erase(remote_id);
    connection_host_names_.erase(remote_id);
    for (const auto& [id, status] : connection_status_) {
      if (status != ConnectionStatus::Connected) {
        continue;
      }
      has_connected_controller = true;
      has_web_controller =
          has_web_controller || id.find("web") != std::string::npos;
      if (remaining_controller_id.empty()) {
        remaining_controller_id = id;
      }
    }
  }
  show_cursor_ = has_web_controller;
  if (has_connected_controller) {
    remote_client_id_ = remaining_controller_id;
    return;
  }

  need_to_create_server_window_.store(false, std::memory_order_release);
  need_to_destroy_server_window_.store(true, std::memory_order_release);
  is_server_mode_ = false;
#if defined(__linux__) && !defined(__APPLE__)
  if (IsWaylandSession()) {
    // Keep Wayland capture session warm to avoid black screen on subsequent
    // reconnects.
    start_screen_capturer_ = true;
    LOG_INFO(
        "Keeping Wayland screen capturer running after disconnect to "
        "preserve reconnect stability");
  } else {
    start_screen_capturer_ = false;
  }
#else
  start_screen_capturer_ = false;
#endif
  start_speaker_capturer_ = false;
  start_mouse_controller_ = false;
  start_keyboard_capturer_ = false;
  remote_client_id_.clear();
  if (audio_capture_) {
    devices_.StopSpeakerCapturer();
    audio_capture_ = false;
  }
  devices_.ResetToInitialDisplay();
}

int GuiRuntime::RequestSingleDevicePresence(const std::string& remote_id,
                                            const char* password,
                                            bool remember_password) {
  if (!signal_connected_ || !peer_) {
    return -1;
  }

  {
    std::lock_guard<std::mutex> lock(pending_presence_probe_mutex_);
    pending_presence_probe_ = true;
    pending_presence_result_ready_ = false;
    pending_presence_online_ = false;
    pending_presence_probe_started_at_ = std::chrono::steady_clock::now();
    pending_presence_remote_id_ = remote_id;
    pending_presence_password_ = password ? password : "";
    pending_presence_remember_password_ = remember_password;
  }

  nlohmann::json j;
  j["type"] = "recent_connections_presence";
  j["user_id"] = client_id_;
  j["devices"] = nlohmann::json::array({remote_id});
  auto s = j.dump();

  int ret = SendSignalMessage(peer_, s.data(), s.size());
  if (ret != 0) {
    std::lock_guard<std::mutex> lock(pending_presence_probe_mutex_);
    pending_presence_probe_ = false;
    pending_presence_result_ready_ = false;
    pending_presence_online_ = false;
    pending_presence_remote_id_.clear();
    pending_presence_password_.clear();
    pending_presence_remember_password_ = false;
  }

  return ret;
}

void GuiRuntime::CloseRemoteSession(std::shared_ptr<RemoteSession> props) {
  std::shared_ptr<std::vector<unsigned char>> frame_snapshot;
  int video_width = 0;
  int video_height = 0;
  {
    std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
    frame_snapshot = props->front_frame_;
    video_width = props->video_width_;
    video_height = props->video_height_;
    if ((!frame_snapshot || frame_snapshot->empty()) &&
        props->thumbnail_frame_ && !props->thumbnail_frame_->empty()) {
      frame_snapshot = props->thumbnail_frame_;
      video_width = props->thumbnail_width_;
      video_height = props->thumbnail_height_;
    }
  }
  auto* native_renderer = video_renderer_.get();
  if ((!frame_snapshot || frame_snapshot->empty()) && native_renderer) {
    auto native_snapshot = std::make_shared<std::vector<unsigned char>>();
    if (native_renderer->CopyLatestNv12(props->remote_id_,
                                        native_snapshot.get(), &video_width,
                                        &video_height)) {
      frame_snapshot = std::move(native_snapshot);
    }
  }

  if (frame_snapshot && !frame_snapshot->empty() && video_width > 0 &&
      video_height > 0) {
    std::vector<unsigned char> buffer_copy(*frame_snapshot);
    std::string remote_id = props->remote_id_;
    std::string remote_host_name = props->remote_host_name_;
    std::string password =
        props->remember_password_ ? props->remote_password_ : "";

    std::thread save_thread([buffer_copy, video_width, video_height, remote_id,
                             remote_host_name, password,
                             thumbnail = thumbnail_]() {
      thumbnail->SaveToThumbnail((char*)buffer_copy.data(), video_width,
                                 video_height, remote_id, remote_host_name,
                                 password);
    });

    {
      std::lock_guard<std::mutex> lock(thumbnail_save_threads_mutex_);
      thumbnail_save_threads_.emplace_back(std::move(save_thread));
    }
  }

  if (native_renderer) {
    native_renderer->DiscardStream(props->remote_id_);
    video_frame_dirty_.store(true, std::memory_order_release);
  }

  if (props->peer_) {
    LOG_INFO("[{}] Leave connection [{}]", props->local_id_, props->remote_id_);
    LeaveConnection(props->peer_, props->remote_id_.c_str());
    LOG_INFO("Destroy peer [{}]", props->local_id_);
    DestroyPeer(&props->peer_);
  }
}

void GuiRuntime::CloseAllRemoteSessions() {
  if (peer_) {
    LOG_INFO("[{}] Leave connection [{}]", client_id_, client_id_);
    LeaveConnection(peer_, client_id_);
    is_client_mode_ = false;
    devices_.StopMouseController();
    devices_.StopScreenCapturer();
    devices_.StopSpeakerCapturer();
    devices_.StopKeyboardCapturer();
    LOG_INFO("Destroy peer [{}]", client_id_);
    DestroyPeer(&peer_);
  }

  {
    // std::shared_lock lock(remote_sessions_mutex_);
    for (auto& it : remote_sessions_) {
      auto props = it.second;
      CloseRemoteSession(props);
    }
  }

  {
    // std::unique_lock lock(remote_sessions_mutex_);
    remote_sessions_.clear();
  }
}

void GuiRuntime::WaitForThumbnailSaveTasks() {
  std::vector<std::thread> threads_to_join;

  {
    std::lock_guard<std::mutex> lock(thumbnail_save_threads_mutex_);
    threads_to_join.swap(thumbnail_save_threads_);
  }

  if (threads_to_join.empty()) {
    return;
  }

  for (auto& thread : threads_to_join) {
    if (thread.joinable()) {
      thread.join();
    }
  }
}

void GuiRuntime::ResetRemoteSessionResources(
    std::shared_ptr<RemoteSession> props) {
  {
    std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
    props->front_frame_.reset();
    props->back_frame_.reset();
    props->thumbnail_frame_.reset();
    props->thumbnail_width_ = 0;
    props->thumbnail_height_ = 0;
    props->background_snapshot_time_ = {};
    props->video_width_ = 0;
    props->video_height_ = 0;
    props->video_size_ = 0;
    props->render_rect_dirty_ = true;
    props->stream_cleanup_pending_ = false;
  }
  {
    std::lock_guard<std::mutex> lock(props->remote_cursor_state_mutex_);
    props->remote_cursor_state_ = {};
    props->remote_cursor_state_received_ = false;
  }
}

std::shared_ptr<GuiRuntime::RemoteSession> GuiRuntime::FindRemoteSession(
    const std::string& remote_id) {
  if (remote_id.empty()) {
    return nullptr;
  }

  std::shared_lock lock(remote_sessions_mutex_);
  auto it = remote_sessions_.find(remote_id);
  if (it == remote_sessions_.end()) {
    return nullptr;
  }
  return it->second;
}

int GuiRuntime::ConnectTo(const std::string& remote_id, const char* password,
                          bool remember_password, bool bypass_presence_check) {
  if (!bypass_presence_check && !device_presence_cache_.IsOnline(remote_id)) {
    int ret =
        RequestSingleDevicePresence(remote_id, password, remember_password);
    if (ret != 0) {
      offline_warning_text_ =
          localization::device_offline[localization_language_index_];
      show_offline_warning_window_ = true;
      LOG_WARN("Presence probe failed for [{}], ret={}", remote_id, ret);
    } else {
      LOG_INFO("Presence probe requested for [{}] before connect", remote_id);
    }
    return -1;
  }

  LOG_INFO("Connect to [{}]", remote_id);
  focused_remote_id_ = remote_id;

  // std::shared_lock shared_lock(remote_sessions_mutex_);
  bool exists = (remote_sessions_.find(remote_id) != remote_sessions_.end());
  // shared_lock.unlock();

  if (!exists) {
    PeerPtr* peer_to_init = nullptr;
    std::string local_id;

    {
      // std::unique_lock unique_lock(remote_sessions_mutex_);
      if (remote_sessions_.find(remote_id) == remote_sessions_.end()) {
        remote_sessions_[remote_id] = std::make_shared<RemoteSession>();
        auto props = remote_sessions_[remote_id];
        props->local_id_ = "C-" + std::string(client_id_);
        props->remote_id_ = remote_id;
        memcpy(&props->params_, &params_, sizeof(Params));
        props->params_.user_id = props->local_id_.c_str();
        props->peer_ = CreatePeer(&props->params_);

        props->control_window_width_ = title_bar_height_ * 10.0f;
        props->control_window_height_ = title_bar_height_ * 1.3f;
        props->control_window_min_width_ = title_bar_height_ * 0.65f;
        props->control_window_min_height_ = title_bar_height_ * 1.3f;
        props->control_window_max_width_ = title_bar_height_ * 10.0f;
        props->control_window_max_height_ = title_bar_height_ * 7.0f;

        props->connection_status_.store(ConnectionStatus::Connecting);
        show_connection_status_window_ = true;

        if (!props->peer_) {
          LOG_INFO("Create peer [{}] instance failed", props->local_id_);
          return -1;
        }

        const auto& displays = devices_.display_info_list();
        for (size_t index = 0; index < displays.size(); ++index) {
          const std::string stream_id = MakeDisplayStreamId(index);
          AddVideoStream(props->peer_, stream_id.c_str());
        }
        AddAudioStream(props->peer_, props->audio_label_.c_str());
        AddDataStream(props->peer_, props->data_label_.c_str(), false);
        AddDataStream(props->peer_, props->mouse_label_.c_str(), false);
        AddDataStream(props->peer_, props->keyboard_label_.c_str(), true);
        AddDataStream(props->peer_, props->control_data_label_.c_str(), true);
        AddDataStream(props->peer_, props->file_label_.c_str(), true);
        AddDataStream(props->peer_, props->file_feedback_label_.c_str(), true);
        AddDataStream(props->peer_, props->clipboard_label_.c_str(), true);

        props->connection_status_.store(ConnectionStatus::Connecting);

        peer_to_init = props->peer_;
        local_id = props->local_id_;
      }
    }

    if (peer_to_init) {
      LOG_INFO("[{}] Create peer instance successful", local_id);
      Init(peer_to_init);
      LOG_INFO("[{}] Peer init finish", local_id);
    }
  }

  int ret = -1;
  // std::shared_lock read_lock(remote_sessions_mutex_);
  auto props = remote_sessions_[remote_id];
  if (!props->connection_established_) {
    props->connection_status_.store(ConnectionStatus::Connecting);
    show_connection_status_window_ = true;

    props->remember_password_ = remember_password;
    if (strcmp(password, "") != 0 &&
        strcmp(password, props->remote_password_) != 0) {
      strncpy(props->remote_password_, password,
              sizeof(props->remote_password_) - 1);
      props->remote_password_[sizeof(props->remote_password_) - 1] = '\0';
    }

    std::string remote_id_with_pwd = remote_id + "@" + password;
    if (props->peer_) {
      ret = JoinConnection(props->peer_, remote_id_with_pwd.c_str());
      if (0 == ret) {
        props->rejoin_ = false;
      } else {
        props->rejoin_ = true;
        need_to_rejoin_ = true;
      }
    }
  }
  // read_lock.unlock();

  return 0;
}
}  // namespace crossdesk
