#include <gtest/gtest.h>
#include <rclcpp/rclcpp.hpp>

#include <chrono>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <utility>
#include <vector>

#include "mtran/http_translation_client.hpp"
#include "mtran/srv/translate_english_to_chinese.hpp"
#include "mtran/translator_node.hpp"

using namespace std::chrono_literals;

namespace mtran
{
namespace
{

class FakeTranslationClient final : public TranslationClient
{
public:
  bool health() override
  {
    std::lock_guard<std::mutex> lock(mutex_);
    ++health_calls_;
    return healthy_;
  }

  TranslationResult translate(const std::string & text) override
  {
    std::lock_guard<std::mutex> lock(mutex_);
    if (text == "Hello.") {
      ++warmup_calls_;
      return warmup_result_;
    }
    user_inputs_.push_back(text);
    return user_result_;
  }

  void set_healthy(bool healthy)
  {
    std::lock_guard<std::mutex> lock(mutex_);
    healthy_ = healthy;
  }

  void set_warmup_result(TranslationResult result)
  {
    std::lock_guard<std::mutex> lock(mutex_);
    warmup_result_ = std::move(result);
  }

  void set_user_result(TranslationResult result)
  {
    std::lock_guard<std::mutex> lock(mutex_);
    user_result_ = std::move(result);
  }

  void set_all_translation_results(TranslationResult result)
  {
    std::lock_guard<std::mutex> lock(mutex_);
    warmup_result_ = result;
    user_result_ = std::move(result);
  }

  std::size_t user_call_count() const
  {
    std::lock_guard<std::mutex> lock(mutex_);
    return user_inputs_.size();
  }

  int warmup_call_count() const
  {
    std::lock_guard<std::mutex> lock(mutex_);
    return warmup_calls_;
  }

private:
  mutable std::mutex mutex_;
  bool healthy_{true};
  int health_calls_{0};
  int warmup_calls_{0};
  TranslationResult warmup_result_{true, "你好。", ClientError::kNone};
  TranslationResult user_result_{true, "机器人已完成检查。", ClientError::kNone};
  std::vector<std::string> user_inputs_;
};

class TranslatorNodeTest : public ::testing::Test
{
protected:
  using Service = mtran::srv::TranslateEnglishToChinese;

  static void SetUpTestSuite()
  {
    if (!rclcpp::ok()) {
      int argc = 0;
      rclcpp::init(argc, nullptr);
    }
  }

  static void TearDownTestSuite()
  {
    rclcpp::shutdown();
  }

  void SetUp() override
  {
    fake_ = std::make_shared<FakeTranslationClient>();
    rclcpp::NodeOptions options;
    options.parameter_overrides(
        {
          rclcpp::Parameter("health_retry_ms", 1),
          rclcpp::Parameter("startup_timeout_ms", 1000),
          rclcpp::Parameter("keep_warm_interval_s", 1),
          rclcpp::Parameter("max_input_characters", 512),
        });
    server_ = std::make_shared<TranslatorNode>(options, fake_);
    client_node_ = std::make_shared<rclcpp::Node>("mtran_test_client");
    client_ = client_node_->create_client<Service>("/translate_en_to_zh");
    executor_.add_node(server_);
    executor_.add_node(client_node_);
  }

  void TearDown() override
  {
    executor_.remove_node(client_node_);
    executor_.remove_node(server_);
    client_.reset();
    client_node_.reset();
    server_.reset();
    fake_.reset();
  }

  bool spin_until_service(std::chrono::milliseconds timeout = 500ms)
  {
    const auto deadline = std::chrono::steady_clock::now() + timeout;
    while (std::chrono::steady_clock::now() < deadline) {
      executor_.spin_some();
      if (client_->service_is_ready()) {
        return true;
      }
      std::this_thread::sleep_for(1ms);
    }
    return false;
  }

  Service::Response::SharedPtr call(const std::string & text)
  {
    auto request = std::make_shared<Service::Request>();
    request->text = text;
    auto future = client_->async_send_request(request);
    EXPECT_EQ(executor_.spin_until_future_complete(future, 1s), rclcpp::FutureReturnCode::SUCCESS);
    return future.get();
  }

  rclcpp::executors::SingleThreadedExecutor executor_;
  std::shared_ptr<FakeTranslationClient> fake_;
  std::shared_ptr<TranslatorNode> server_;
  rclcpp::Node::SharedPtr client_node_;
  rclcpp::Client<Service>::SharedPtr client_;
};

TEST_F(TranslatorNodeTest, AdvertisesServiceOnlyAfterHealthAndWarmup)
{
  fake_->set_healthy(false);
  executor_.spin_some();
  std::this_thread::sleep_for(10ms);
  executor_.spin_some();
  EXPECT_FALSE(client_->service_is_ready());

  fake_->set_healthy(true);

  EXPECT_TRUE(spin_until_service());
  EXPECT_GE(fake_->warmup_call_count(), 1);
}

TEST_F(TranslatorNodeTest, ValidatesEmptyAndLengthBoundariesBeforeCallingClient)
{
  ASSERT_TRUE(spin_until_service());

  const auto empty = call("   ");
  EXPECT_FALSE(empty->success);
  EXPECT_TRUE(empty->translation.empty());
  EXPECT_EQ(empty->error, "INVALID_INPUT");

  const auto at_limit = call(std::string(512, 'a'));
  EXPECT_TRUE(at_limit->success);
  EXPECT_EQ(at_limit->translation, "机器人已完成检查。");
  EXPECT_TRUE(at_limit->error.empty());

  const auto over_limit = call(std::string(513, 'a'));
  EXPECT_FALSE(over_limit->success);
  EXPECT_TRUE(over_limit->translation.empty());
  EXPECT_EQ(over_limit->error, "INPUT_TOO_LONG");
  EXPECT_EQ(fake_->user_call_count(), 1U);
}

TEST_F(TranslatorNodeTest, MapsTranslationClientOutcomesToServiceErrors)
{
  ASSERT_TRUE(spin_until_service());

  fake_->set_user_result({false, "", ClientError::kTimeout});
  EXPECT_EQ(call("timeout")->error, "TIMEOUT");

  fake_->set_user_result({false, "", ClientError::kUpstream});
  EXPECT_EQ(call("upstream")->error, "UPSTREAM_ERROR");

  fake_->set_user_result({false, "", ClientError::kInvalidResponse});
  EXPECT_EQ(call("invalid")->error, "INVALID_RESPONSE");
}

TEST_F(TranslatorNodeTest, RecoversAfterUnavailableSidecarIsHealthyAndWarmAgain)
{
  ASSERT_TRUE(spin_until_service());
  fake_->set_user_result({false, "", ClientError::kUnavailable});

  const auto unavailable = call("first request");
  EXPECT_FALSE(unavailable->success);
  EXPECT_EQ(unavailable->error, "NOT_READY");

  fake_->set_healthy(false);
  fake_->set_user_result({true, "恢复完成", ClientError::kNone});
  const auto while_down = call("second request");
  EXPECT_FALSE(while_down->success);
  EXPECT_EQ(while_down->error, "NOT_READY");

  const auto warmups_before_recovery = fake_->warmup_call_count();
  fake_->set_healthy(true);
  const auto deadline = std::chrono::steady_clock::now() + 500ms;
  while (fake_->warmup_call_count() == warmups_before_recovery &&
    std::chrono::steady_clock::now() < deadline)
  {
    executor_.spin_some();
    std::this_thread::sleep_for(1ms);
  }

  const auto recovered = call("third request");
  EXPECT_TRUE(recovered->success);
  EXPECT_EQ(recovered->translation, "恢复完成");
  EXPECT_TRUE(recovered->error.empty());
}

TEST_F(TranslatorNodeTest, HealthMonitorDetectsRestartBeforeNextUserRequest)
{
  ASSERT_TRUE(spin_until_service());
  const auto user_calls_before_outage = fake_->user_call_count();
  fake_->set_healthy(false);

  const auto detection_deadline = std::chrono::steady_clock::now() + 100ms;
  while (std::chrono::steady_clock::now() < detection_deadline) {
    executor_.spin_some();
    std::this_thread::sleep_for(1ms);
  }

  const auto during_outage = call("request during restart");
  EXPECT_FALSE(during_outage->success);
  EXPECT_EQ(during_outage->error, "NOT_READY");
  EXPECT_EQ(fake_->user_call_count(), user_calls_before_outage);

  fake_->set_healthy(true);
  const auto warmups_before_recovery = fake_->warmup_call_count();
  const auto recovery_deadline = std::chrono::steady_clock::now() + 500ms;
  while (fake_->warmup_call_count() == warmups_before_recovery &&
    std::chrono::steady_clock::now() < recovery_deadline)
  {
    executor_.spin_some();
    std::this_thread::sleep_for(1ms);
  }

  const auto recovered = call("request after restart");
  EXPECT_TRUE(recovered->success);
  EXPECT_EQ(recovered->translation, "机器人已完成检查。");
}

TEST_F(TranslatorNodeTest, TransientKeepWarmFailureDoesNotMarkServiceNotReady)
{
  ASSERT_TRUE(spin_until_service());
  fake_->set_all_translation_results({false, "", ClientError::kTimeout});

  const auto warmups_before_timer = fake_->warmup_call_count();
  const auto deadline = std::chrono::steady_clock::now() + 1500ms;
  while (fake_->warmup_call_count() == warmups_before_timer &&
    std::chrono::steady_clock::now() < deadline)
  {
    executor_.spin_some();
    std::this_thread::sleep_for(2ms);
  }
  ASSERT_GT(fake_->warmup_call_count(), warmups_before_timer);

  fake_->set_user_result({true, "服务仍然可用", ClientError::kNone});
  const auto response = call("request after transient failure");
  EXPECT_TRUE(response->success);
  EXPECT_EQ(response->translation, "服务仍然可用");
  EXPECT_TRUE(response->error.empty());
}

}  // namespace
}  // namespace mtran
