#include "runtime/gui_runtime.h"

#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/video_renderer.h"
#include "rd_log.h"

namespace crossdesk {

GuiRuntime::GuiRuntime()
    : clipboard_(*this), devices_(*this), transfers_(*this), settings_(*this),
      keyboard_(*this), peer_events_(*this) {}

GuiRuntime::~GuiRuntime() = default;

int GuiRuntime::CreateConnectionPeer() {
  params_.use_cfg_file = false;

  std::string signal_server_ip;
  int signal_server_port;
  int coturn_server_port;

  if (config_center_->IsSelfHosted()) {
    signal_server_ip = config_center_->GetSignalServerHost();
    signal_server_port = config_center_->GetSignalServerPort();
    coturn_server_port = config_center_->GetCoturnServerPort();

    std::string current_self_hosted_ip = config_center_->GetSignalServerHost();
    const bool use_cached_id = settings_.LoadCachedSelfHostedIdentity();
    if (!use_cached_id) {
      LOG_INFO(
          "secure_cache_v2.enc not found, will use empty id to get new id from "
          "server");
    }

    if (use_cached_id && strlen(self_hosted_id_) > 0) {
      memset(&self_hosted_user_id_, 0, sizeof(self_hosted_user_id_));
      strncpy(self_hosted_user_id_, self_hosted_id_,
              sizeof(self_hosted_user_id_) - 1);
      self_hosted_user_id_[sizeof(self_hosted_user_id_) - 1] = '\0';
      params_.user_id = self_hosted_user_id_;
    } else {
      memset(&self_hosted_user_id_, 0, sizeof(self_hosted_user_id_));
      params_.user_id = self_hosted_user_id_;
      LOG_INFO(
          "Using empty id for self-hosted server, server will assign new id");
    }
  } else {
    signal_server_ip = config_center_->GetDefaultServerHost();
    signal_server_port = config_center_->GetDefaultSignalServerPort();
    coturn_server_port = config_center_->GetDefaultCoturnServerPort();
    settings_.ActivateCachedPublicIdentity();
    params_.user_id = client_id_with_password_;
  }

  const bool self_hosted = config_center_->IsSelfHosted();
  const std::string pending_identity =
      settings_.PendingPasswordChangeIdentity(
          self_hosted, signal_server_ip, signal_server_port);
  bool try_pending_identity = false;
  {
    std::lock_guard<std::mutex> lock(password_change_mutex_);
    if (pending_identity.empty()) {
      credential_recovery_in_progress_ = false;
      credential_recovery_attempt_pending_ = false;
      credential_recovery_retry_active_ = false;
      credential_recovery_promote_pending_ = false;
      credential_recovery_clear_pending_ = false;
    } else if (!credential_recovery_in_progress_) {
      credential_recovery_in_progress_ = true;
      credential_recovery_attempt_pending_ = true;
      LOG_INFO("Recovering an interrupted password change for [{}]",
               client_id_);
    }
    try_pending_identity = credential_recovery_in_progress_ &&
                           credential_recovery_attempt_pending_ &&
                           !pending_identity.empty();
  }

  const char *active_identity =
      self_hosted ? self_hosted_user_id_ : client_id_with_password_;
  const std::string login_identity =
      try_pending_identity ? pending_identity : std::string(active_identity);
  std::memset(connection_login_identity_, 0,
              sizeof(connection_login_identity_));
  std::strncpy(connection_login_identity_, login_identity.c_str(),
               sizeof(connection_login_identity_) - 1);
  params_.user_id = connection_login_identity_;

  // self hosted server config
  strncpy(signal_server_ip_self_, config_center_->GetSignalServerHost().c_str(),
          sizeof(signal_server_ip_self_) - 1);
  signal_server_ip_self_[sizeof(signal_server_ip_self_) - 1] = '\0';
  int signal_port = config_center_->GetSignalServerPort();
  if (signal_port > 0) {
    strncpy(signal_server_port_self_, std::to_string(signal_port).c_str(),
            sizeof(signal_server_port_self_) - 1);
    signal_server_port_self_[sizeof(signal_server_port_self_) - 1] = '\0';
  } else {
    signal_server_port_self_[0] = '\0';
  }
  int coturn_port = config_center_->GetCoturnServerPort();
  if (coturn_port > 0) {
    strncpy(coturn_server_port_self_, std::to_string(coturn_port).c_str(),
            sizeof(coturn_server_port_self_) - 1);
    coturn_server_port_self_[sizeof(coturn_server_port_self_) - 1] = '\0';
  } else {
    coturn_server_port_self_[0] = '\0';
  }

  // peer config
  strncpy((char *)params_.signal_server_ip, signal_server_ip.c_str(),
          sizeof(params_.signal_server_ip) - 1);
  params_.signal_server_ip[sizeof(params_.signal_server_ip) - 1] = '\0';
  params_.signal_server_port = signal_server_port;
  strncpy((char *)params_.stun_server_ip, signal_server_ip.c_str(),
          sizeof(params_.stun_server_ip) - 1);
  params_.stun_server_ip[sizeof(params_.stun_server_ip) - 1] = '\0';
  params_.stun_server_port = coturn_server_port;
  strncpy((char *)params_.turn_server_ip, signal_server_ip.c_str(),
          sizeof(params_.turn_server_ip) - 1);
  params_.turn_server_ip[sizeof(params_.turn_server_ip) - 1] = '\0';
  params_.turn_server_port = coturn_server_port;
  // TURN credentials are issued by the signaling server after login. Keep the
  // initial values empty so a reusable static password is never embedded in
  // the client binary.
  params_.turn_server_username[0] = '\0';
  params_.turn_server_password[0] = '\0';

  strncpy(params_.log_path, dll_log_path_.c_str(),
          sizeof(params_.log_path) - 1);
  params_.log_path[sizeof(params_.log_path) - 1] = '\0';
  params_.hardware_acceleration = config_center_->IsHardwareVideoCodec();
  // The Slint desktop renderer currently consumes packed CPU frames. Native
  // renderers can opt into platform-native frame output independently.
  params_.native_video_output = false;
  params_.av1_encoding = config_center_->GetVideoEncodeFormat() ==
                                 ConfigCenter::VIDEO_ENCODE_FORMAT::AV1
                             ? true
                             : false;
  params_.turn_mode = static_cast<TurnMode>(config_center_->GetTurnMode());
  params_.enable_srtp = config_center_->IsEnableSrtp();
  params_.video_content_type = VideoContentType::ScreenContent;
  params_.video_quality =
      static_cast<VideoQuality>(config_center_->GetVideoQuality());
  params_.video_frame_rate =
      config_center_->GetVideoFrameRate() ==
              ConfigCenter::VIDEO_FRAME_RATE::FPS_30
          ? 30
          : 60;
  params_.video_degradation_preference =
      VideoDegradationPreference::MaintainFrameRate;
  params_.on_receive_video_buffer = nullptr;
  params_.on_receive_audio_buffer = PeerEventHandler::OnReceiveAudioBuffer;
  params_.on_receive_data_buffer = PeerEventHandler::OnReceiveDataBuffer;

  params_.on_receive_video_frame = PeerEventHandler::OnReceiveVideoBuffer;

  params_.on_signal_status = PeerEventHandler::OnSignalStatus;
  params_.on_signal_message = PeerEventHandler::OnSignalMessage;
  params_.on_connection_status = PeerEventHandler::OnConnectionStatus;
  params_.on_net_status_report = PeerEventHandler::OnNetStatusReport;

  params_.user_data = &peer_events_;

  // The previous peer may have left a terminal status behind. Reset it before
  // Init() starts emitting callbacks for the newly selected server.
  signal_connected_ = false;
  signal_status_ = SignalStatus::SignalConnecting;

  peer_ = CreatePeer(&params_);
  if (peer_) {
    LOG_INFO("Create peer instance [{}] successful", client_id_);
    Init(peer_);
    LOG_INFO("Peer [{}] init finish", client_id_);
  } else {
    LOG_INFO("Create peer [{}] instance failed", client_id_);
  }

  if (0 == devices_.InitializeScreenCapturer()) {
    const auto &displays = devices_.display_info_list();
    for (size_t index = 0; index < displays.size(); ++index) {
      const std::string stream_id = MakeDisplayStreamId(index);
      AddVideoStream(peer_, stream_id.c_str());
    }

    AddAudioStream(peer_, audio_label_.c_str());
    AddDataStream(peer_, data_label_.c_str(), false);
    AddDataStream(peer_, mouse_label_.c_str(), false);
    AddDataStream(peer_, keyboard_label_.c_str(), true);
    AddDataStream(peer_, control_data_label_.c_str(), true);
    AddDataStream(peer_, file_label_.c_str(), true);
    AddDataStream(peer_, file_feedback_label_.c_str(), true);
    AddDataStream(peer_, clipboard_label_.c_str(), true);
    return 0;
  } else {
    return -1;
  }
}

void GuiRuntime::UpdateLabels() {
  if (!label_inited_ ||
      localization_language_index_last_ != localization_language_index_) {
    connect_button_label_ =
        connect_button_pressed_
            ? localization::disconnect[localization_language_index_]
            : localization::connect[localization_language_index_];
    label_inited_ = true;
    localization_language_index_last_ = localization_language_index_;
  }
}


void GuiRuntime::HandleRecentConnections() {
  if (reload_recent_connections_ && thumbnail_) {
    uint32_t now_time = SDL_GetTicks();
    if (now_time - recent_connection_image_save_time_ >= 50) {
      int ret = thumbnail_->LoadThumbnail(recent_connections_,
                                          &recent_connection_image_width_,
                                          &recent_connection_image_height_);
      if (!ret) {
        LOG_INFO("Load recent connection thumbnails");
      }
      reload_recent_connections_ = false;

      recent_connection_ids_.clear();
      for (const auto &conn : recent_connections_) {
        recent_connection_ids_.push_back(conn.first);
      }
      need_to_send_recent_connections_ = true;
    }
  }
}


void GuiRuntime::SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len) {
  GuiRuntime *runtime = static_cast<GuiRuntime *>(userdata);
  if (!runtime) {
    return;
  }

  if (1) {
    std::shared_lock lock(runtime->remote_sessions_mutex_);
    for (const auto &it : runtime->remote_sessions_) {
      auto props = it.second;
      if (props->connection_status_.load() == ConnectionStatus::Connected) {
        if (props->peer_) {
          SendAudioFrame(props->peer_, (const char *)stream, len,
                         runtime->audio_label_.c_str());
        }
      }
    }

  } else {
    memcpy(runtime->audio_buffer_, stream, len);
    runtime->audio_len_ = len;
    SDL_Delay(10);
    runtime->audio_buffer_fresh_ = true;
  }
}

void GuiRuntime::SdlCaptureAudioOut([[maybe_unused]] void *userdata,
                                    [[maybe_unused]] Uint8 *stream,
                                    [[maybe_unused]] int len) {
  // GuiApplication *runtime = (GuiApplication *)userdata;
  // for (auto it : runtime->remote_sessions_) {
  //   auto props = it.second;
  //   if (props->connection_status_ == SignalStatus::SignalConnected) {
  //     SendAudioFrame(props->peer_, (const char *)stream, len);
  //   }
  // }

  // if (!runtime->audio_buffer_fresh_) {
  //   return;
  // }

  // SDL_memset(stream, 0, len);

  // if (runtime->audio_len_ == 0) {
  //   return;
  // } else {
  // }

  // len = (len > runtime->audio_len_ ? runtime->audio_len_ : len);
  // SDL_MixAudioFormat(stream, runtime->audio_buffer_, AUDIO_S16LSB, len,
  //                    SDL_MIX_MAXVOLUME);
  // runtime->audio_buffer_fresh_ = false;
}

} // namespace crossdesk
