#include "runtime/builtin_steps.h"

#include <cstdio>
#include <cstring>
#include <string>

#include "cf_datahive_cpp/cf_datahive_cpp.h"
#include "cf_step_utils.h"

namespace cf {

namespace {

CfStatusCode fail_builtin_step(const PlanNode& node, CfStatusCode status, const std::string& message) {
  std::fprintf(stderr, "cf_pipeline_engine built-in step failure [%s]: %s\n", node.node_id.c_str(), message.c_str());
  std::fflush(stderr);
  return status;
}

void add_issue(CfValidationReport* report, CfValidationSeverity severity, const std::string& message) {
  if (!report || !report->allocator) return;
  if (report->issue_count >= report->issue_capacity) {
    size_t new_capacity = report->issue_capacity == 0 ? 4 : report->issue_capacity * 2;
    size_t bytes = sizeof(CfValidationIssue) * new_capacity;
    auto* mem = reinterpret_cast<CfValidationIssue*>(report->allocator->alloc(bytes, report->allocator->user_data));
    if (!mem) return;
    for (size_t i = 0; i < report->issue_count; ++i) {
      mem[i] = report->issues[i];
    }
    report->issues = mem;
    report->issue_capacity = new_capacity;
  }

  auto* issue = &report->issues[report->issue_count++];
  issue->severity = severity;
  char* msg = reinterpret_cast<char*>(report->allocator->alloc(message.size() + 1, report->allocator->user_data));
  if (msg) {
    std::memcpy(msg, message.data(), message.size());
    msg[message.size()] = '\0';
    issue->message = msg;
  } else {
    issue->message = nullptr;
  }
  issue->parameter_key = nullptr;
  issue->port_key = nullptr;
}

int find_parameter_index(const PlanNode& node, const std::string& name) {
  for (size_t i = 0; i < node.step_def.parameters.size(); ++i) {
    if (node.step_def.parameters[i].name == name) {
      return static_cast<int>(i);
    }
  }
  return -1;
}

std::string local_name(const std::string& iri) {
  size_t pos = iri.find_last_of("#/");
  if (pos == std::string::npos) {
    return iri;
  }
  return iri.substr(pos + 1);
}

int find_port_index(const std::vector<PortDef>& ports, const std::string& key) {
  for (size_t i = 0; i < ports.size(); ++i) {
    if (ports[i].key == key || local_name(ports[i].key) == key) {
      return static_cast<int>(i);
    }
  }
  return -1;
}

}  // namespace

PlanNodeExecutionKind classify_builtin_step(const std::string& step_iri) {
  if (step_iri == kDataHiveParquetSinkStepIri) {
    return PlanNodeExecutionKind::kBuiltinDataHiveSink;
  }
  return PlanNodeExecutionKind::kPlugin;
}

CfStatusCode validate_builtin_step(const PlanNode& node, CfCallContext*, CfValidationReport* report) {
  if (node.execution_kind != PlanNodeExecutionKind::kBuiltinDataHiveSink) {
    return CF_STATUS_INVALID;
  }

  const int pipeline_id_index = find_parameter_index(node, "pipelineId");
  if (pipeline_id_index < 0 || static_cast<size_t>(pipeline_id_index) >= node.params.size()) {
    add_issue(report, CF_VALIDATION_ERROR, "Missing pipelineId parameter for built-in data hive sink");
    return CF_STATUS_OK;
  }

  const std::string pipeline_id = cf_handle_to_string(&node.params[static_cast<size_t>(pipeline_id_index)]);
  if (pipeline_id.empty()) {
    add_issue(report, CF_VALIDATION_ERROR, "pipelineId must be a non-empty string for built-in data hive sink");
  }

  return CF_STATUS_OK;
}

CfStatusCode execute_builtin_step(
  const PlanNode& node,
  CfCallContext* ctx,
  const CfValueHandle* inputs,
  size_t n_inputs,
  CfValueHandle* outputs,
  size_t n_outputs) {
  if (node.execution_kind != PlanNodeExecutionKind::kBuiltinDataHiveSink) {
    return CF_STATUS_INVALID;
  }

  const int workspace_root_index = find_parameter_index(node, "workspaceRoot");
  const int pipeline_id_index = find_parameter_index(node, "pipelineId");
  const int pipeline_label_index = find_parameter_index(node, "pipelineLabel");
  const int data_input_index = find_port_index(node.step_def.inputs, "data");
  const int status_output_index = find_port_index(node.step_def.outputs, "status_port");

  if (pipeline_id_index < 0 || data_input_index < 0 || status_output_index < 0) {
    return fail_builtin_step(
      node,
      CF_STATUS_INVALID,
      "missing required built-in sink parameter or port mapping (pipelineId/data/status)");
  }

  if (static_cast<size_t>(pipeline_id_index) >= node.params.size() ||
      static_cast<size_t>(data_input_index) >= n_inputs ||
      static_cast<size_t>(status_output_index) >= n_outputs) {
    return fail_builtin_step(
      node,
      CF_STATUS_INVALID,
      "resolved built-in sink indices exceed available params/inputs/outputs");
  }

  const CfValueHandle* pipeline_id_handle = &node.params[static_cast<size_t>(pipeline_id_index)];
  const CfValueHandle* data_handle = &inputs[static_cast<size_t>(data_input_index)];
  if (cf_handle_is_empty(pipeline_id_handle) || cf_handle_is_empty(data_handle)) {
    return fail_builtin_step(
      node,
      CF_STATUS_INVALID,
      "pipelineId or data input is empty at execution time");
  }

  std::string workspace_root = workspace_root_index >= 0 && static_cast<size_t>(workspace_root_index) < node.params.size()
    ? cf_handle_to_string(&node.params[static_cast<size_t>(workspace_root_index)])
    : std::string();
  std::string pipeline_id = cf_handle_to_string(pipeline_id_handle);
  std::string pipeline_label = pipeline_label_index >= 0 && static_cast<size_t>(pipeline_label_index) < node.params.size()
    ? cf_handle_to_string(&node.params[static_cast<size_t>(pipeline_label_index)])
    : std::string();
  if (workspace_root.empty()) {
    workspace_root = "workspace";
  }

  std::string payload(reinterpret_cast<const char*>(data_handle->data), data_handle->size);

  cf_datahive_cpp::SinkConfig config{};
  config.workspace_root = workspace_root;
  config.pipeline_id = pipeline_id;
  config.pipeline_label = pipeline_label;
  config.cycle_count = 20;

  const cf_datahive_cpp::SinkResult write_result = cf_datahive_cpp::write_demo_measurements_run(config, payload);
  if (!write_result.ok) {
    return fail_builtin_step(
      node,
      CF_STATUS_ERROR,
      std::string("data hive sink write failed: ") + write_result.error);
  }

  return cf_out_string(ctx, &outputs[static_cast<size_t>(status_output_index)], write_result.status.c_str());
}

}  // namespace cf