#include <thread>
#include <cstring>
#include <chrono>

#include "cf_step_abi.h"
#include "cf_step_utils.h"
#include "cf_plugin_table.h"
#include "cf_test_signature_hashes.h"

namespace {

CfValueHandle write_f64(CfCallContext* ctx, double value) {
  CfValueHandle out{};
  out.storage_type = CF_STORAGE_F64;
  out.size = sizeof(double);
  if (ctx && ctx->allocator && ctx->allocator->alloc) {
    void* mem = ctx->allocator->alloc(out.size, ctx->allocator->user_data);
    if (mem) {
      std::memcpy(mem, &value, sizeof(double));
      out.data = mem;
    }
  }
  return out;
}

CfValueHandle write_bytes(CfCallContext* ctx, CfStorageType storage_type, const char* bytes, size_t size) {
  CfValueHandle out{};
  out.storage_type = storage_type;
  out.size = size;
  if (ctx && ctx->allocator && ctx->allocator->alloc && bytes && size > 0) {
    void* mem = ctx->allocator->alloc(size, ctx->allocator->user_data);
    if (mem) {
      std::memcpy(mem, bytes, size);
      out.data = mem;
    }
  }
  return out;
}

static CfStatusCode call_json_source(
  CfCallContext* ctx,
  const CfValueHandle*,
  size_t,
  const CfValueHandle*,
  size_t,
  CfValueHandle* outputs,
  size_t n_outputs) {
  using S = cf_sig::JsonSourceStep;
  if (n_outputs < S::kOutputCount) return CF_STATUS_INVALID;
  static constexpr char kPayload[] = "{\"temperature\":42.5}";
  outputs[S::O_data] = write_bytes(ctx, CF_STORAGE_JSON, kPayload, sizeof(kPayload) - 1);
  return outputs[S::O_data].data ? CF_STATUS_OK : CF_STATUS_ERROR;
}

static CfStatusCode call_sleep(
  CfCallContext*,
  const CfValueHandle* params,
  size_t n_params,
  const CfValueHandle*,
  size_t,
  CfValueHandle*,
  size_t) {
  using S = cf_sig::SleepStep;
  if (n_params < S::kParamCount) return CF_STATUS_INVALID;
  if (!params[S::P_duration_ms].data || params[S::P_duration_ms].size < sizeof(int64_t)) {
    return CF_STATUS_INVALID;
  }
  int64_t duration_ms = *reinterpret_cast<const int64_t*>(params[S::P_duration_ms].data);
  if (duration_ms > 0) {
    std::this_thread::sleep_for(std::chrono::milliseconds(duration_ms));
  }
  return CF_STATUS_OK;
}

static CfStatusCode call_multi_param(
  CfCallContext*,
  const CfValueHandle* params,
  size_t n_params,
  const CfValueHandle*,
  size_t,
  CfValueHandle*,
  size_t) {
  using S = cf_sig::MultiParamStep;
  if (n_params < S::kParamCount) return CF_STATUS_INVALID;
  if (!params[S::P_alpha].data || params[S::P_alpha].size < sizeof(int64_t)) {
    return CF_STATUS_INVALID;
  }
  int64_t duration_ms = *reinterpret_cast<const int64_t*>(params[S::P_alpha].data);
  if (duration_ms > 0) {
    std::this_thread::sleep_for(std::chrono::milliseconds(duration_ms));
  }
  return CF_STATUS_OK;
}

static CfStatusCode call_budget_check(
  CfCallContext* ctx,
  const CfValueHandle* params,
  size_t n_params,
  const CfValueHandle*,
  size_t,
  CfValueHandle*,
  size_t) {
  using S = cf_sig::BudgetCheckStep;
  if (n_params < S::kParamCount) return CF_STATUS_INVALID;
  int64_t duration_ms = 0;
  int64_t expected_budget = 0;
  if (!cf_read_i64(&params[S::P_duration_ms], duration_ms)) return CF_STATUS_INVALID;
  if (!cf_read_i64(&params[S::P_expected_budget], expected_budget)) return CF_STATUS_INVALID;

  uint32_t budget = cf_runtime_thread_budget(ctx, 0);
  if (expected_budget < 0) return CF_STATUS_INVALID;
  if (budget != static_cast<uint32_t>(expected_budget)) return CF_STATUS_INVALID;

  if (duration_ms > 0) {
    std::this_thread::sleep_for(std::chrono::milliseconds(duration_ms));
  }
  return CF_STATUS_OK;
}

static CfStatusCode validate_sleep(
  CfCallContext*,
  const CfValueHandle* params,
  size_t n_params,
  const CfValueHandle*,
  size_t,
  CfValidationReport* report) {
  using S = cf_sig::SleepStep;
  if (n_params < S::kParamCount) return CF_STATUS_INVALID;
  if (!params[S::P_duration_ms].data || params[S::P_duration_ms].size < sizeof(int64_t)) {
    return CF_STATUS_INVALID;
  }
  int64_t duration_ms = *reinterpret_cast<const int64_t*>(params[S::P_duration_ms].data);
  if (duration_ms < 0) {
    cf_validation_append(report, CF_VALIDATION_ERROR, "duration_ms must be >= 0", "duration_ms", NULL);
  }
  return CF_STATUS_OK;
}

static CfStatusCode call_passthrough(
  CfCallContext* ctx,
  const CfValueHandle*,
  size_t,
  const CfValueHandle* inputs,
  size_t n_inputs,
  CfValueHandle* outputs,
  size_t n_outputs) {
  using S = cf_sig::PassThroughStep;
  if (n_inputs < S::kInputCount || n_outputs < S::kOutputCount) return CF_STATUS_INVALID;
  if (inputs[S::I_data].storage_type == CF_STORAGE_F64 && inputs[S::I_data].data) {
    double value = *reinterpret_cast<const double*>(inputs[S::I_data].data);
    outputs[S::O_data] = write_f64(ctx, value);
    return CF_STATUS_OK;
  }
  return CF_STATUS_INVALID;
}

static CfStatusCode call_i64_sink(
  CfCallContext*,
  const CfValueHandle*,
  size_t,
  const CfValueHandle* inputs,
  size_t n_inputs,
  CfValueHandle*,
  size_t) {
  using S = cf_sig::I64SinkStep;
  if (n_inputs < S::kInputCount) return CF_STATUS_INVALID;
  int64_t value = 0;
  if (!cf_read_i64(&inputs[S::I_observed_thread_budget], value)) return CF_STATUS_INVALID;
  (void)value;
  return CF_STATUS_OK;
}

#define CF_TEST_STEPS(X) \
  X(cf_generated::kJsonSourceStepIri, cf_generated::kJsonSourceStepSigHash, call_json_source, NULL) \
  X(cf_generated::kSleepStepIri, cf_generated::kSleepStepSigHash, call_sleep, validate_sleep) \
  X(cf_generated::kBudgetCheckStepIri, cf_generated::kBudgetCheckStepSigHash, call_budget_check, NULL) \
  X(cf_generated::kPassThroughStepIri, cf_generated::kPassThroughStepSigHash, call_passthrough, NULL) \
  X(cf_generated::kI64SinkStepIri, cf_generated::kI64SinkStepSigHash, call_i64_sink, NULL) \
  X(cf_generated::kMultiParamStepIri, cf_generated::kMultiParamStepSigHash, call_multi_param, NULL)

}  // namespace

CF_DEFINE_STEP_TABLE(CF_TEST_STEPS)
