// Standalone native Shard worker executable (DGR-033).
//
// Serves the complete ShardRuntime lifecycle/stream contract over real
// gRPC/HTTP2 using the model-free FakeShardEngine. It links neither llama.cpp
// nor any graph-execution entry point: the only surface it exposes is the
// ShardRuntime service defined in shard_runtime.proto.
//
// Usage:
//   shard_worker [listen_addr]     serve until SIGTERM/SIGINT (graceful drain)
//   shard_worker --selftest        bind an ephemeral port, self-drive the
//                                  lifecycle over a real loopback channel, exit
//
// Environment:
//   MESHNET_SHARD_LISTEN_ADDR   host:port to bind (default localhost:50051)
//   MESHNET_MAX_CHUNK_BYTES     per-chunk byte ceiling the worker enforces
//
// On a normal run it prints one readiness line — "ShardRuntime worker listening
// on <addr>" — once the socket is bound, so a supervisor/harness has a real
// readiness signal instead of a sleep.

#include <atomic>
#include <cerrno>
#include <csignal>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <unistd.h>

#include <grpcpp/grpcpp.h>

#include "shard_service.h"
#include "shard_runtime.grpc.pb.h"

namespace {

namespace sp = ::meshnet::shard::v1;

// Self-pipe: the signal handler must stay async-signal-safe, so it only writes
// one byte; a helper thread reads it and performs the (non-signal-safe) server
// Shutdown(). Set once in main() before installing the handler.
volatile std::sig_atomic_t g_signal_pipe_write_fd = -1;

extern "C" void HandleTermination(int /*signum*/) {
  if (g_signal_pipe_write_fd >= 0) {
    const char byte = 1;
    ssize_t rc = ::write(g_signal_pipe_write_fd, &byte, 1);
    (void)rc;  // best-effort; nothing safe to do on failure inside a handler
  }
}

meshnet::worker::FlowLimits LimitsFromEnv() {
  meshnet::worker::FlowLimits limits;
  if (const char* raw = std::getenv("MESHNET_MAX_CHUNK_BYTES")) {
    char* end = nullptr;
    const unsigned long long value = std::strtoull(raw, &end, 10);
    if (end != raw && value > 0) {
      limits.max_chunk_bytes = static_cast<uint64_t>(value);
    }
  }
  return limits;
}

int RunSelfTest() {
  meshnet::worker::ShardRuntimeServiceImpl service(LimitsFromEnv());
  int selected_port = 0;
  grpc::ServerBuilder builder;
  builder.AddListeningPort("127.0.0.1:0", grpc::InsecureServerCredentials(), &selected_port);
  builder.RegisterService(&service);
  std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
  if (!server || selected_port == 0) {
    std::cerr << "selftest: failed to bind ephemeral port\n";
    return 1;
  }
  const std::string target = "127.0.0.1:" + std::to_string(selected_port);
  auto channel = grpc::CreateChannel(target, grpc::InsecureChannelCredentials());
  auto stub = sp::ShardRuntime::NewStub(channel);

  int failures = 0;
  auto check = [&](bool cond, const char* what) {
    if (!cond) {
      std::cerr << "selftest FAIL: " << what << "\n";
      ++failures;
    }
  };

  // Capability + health.
  {
    grpc::ClientContext ctx;
    sp::CapabilityRequest req;
    req.set_schema_version(sp::SCHEMA_VERSION_1);
    sp::CapabilityReport rep;
    grpc::Status status = stub->GetCapability(&ctx, req, &rep);
    check(status.ok(), "GetCapability RPC");
    check(rep.validated(), "capability validated");
    check(rep.schema_version() == sp::SCHEMA_VERSION_1, "capability schema version");
  }
  {
    grpc::ClientContext ctx;
    sp::HealthRequest req;
    req.set_schema_version(sp::SCHEMA_VERSION_1);
    sp::HealthReport rep;
    grpc::Status status = stub->Health(&ctx, req, &rep);
    check(status.ok(), "Health RPC");
    check(rep.state() == sp::SERVING_STATE_SERVING, "health serving");
  }

  // A minimal session: open -> fragmented prefill -> decode -> release.
  {
    grpc::ClientContext ctx;
    auto stream = stub->Session(&ctx);

    sp::SessionRequest open;
    sp::SessionOpen* o = open.mutable_open();
    o->set_schema_version(sp::SCHEMA_VERSION_1);
    o->set_route_session_id("selftest");
    o->set_route_epoch(1);
    sp::FlowControl* fc = o->mutable_proposed_flow_control();
    fc->set_credits_granted(16);
    fc->set_max_inflight_chunks(16);
    fc->set_max_chunk_bytes(4u * 1024u * 1024u);
    check(stream->Write(open), "write open");

    sp::SessionResponse accepted;
    check(stream->Read(&accepted), "read accepted");
    check(accepted.kind_case() == sp::SessionResponse::kAccepted, "accepted kind");

    // Fragmented prefill: two fragments tiling a 6-byte payload.
    const std::string payload = "ABCDEF";
    sp::SessionRequest chunk;
    sp::ActivationChunk* ac = chunk.mutable_chunk();
    sp::Envelope* env = ac->mutable_envelope();
    env->set_schema_version(sp::SCHEMA_VERSION_1);
    env->set_work_id("w1");
    env->set_route_session_id("selftest");
    env->set_route_epoch(1);
    env->set_idempotency_step(1);
    env->set_phase(sp::PHASE_PREFILL);
    sp::TensorBundle* bundle = ac->mutable_bundle();
    bundle->set_bundle_version(1);
    sp::NamedTensor* tensor = bundle->add_tensors();
    tensor->set_name("hidden_states");
    tensor->set_dtype(sp::DTYPE_BFLOAT16);
    tensor->set_byte_order(sp::BYTE_ORDER_LITTLE_ENDIAN);
    tensor->set_total_bytes(payload.size());
    tensor->set_compression(sp::COMPRESSION_NONE);
    sp::Checksum* cksum = tensor->mutable_checksum();
    cksum->set_algorithm(sp::CHECKSUM_ALGORITHM_CRC32C);
    const uint32_t crc = meshnet::worker::Crc32(payload);
    std::string crc_be(4, '\0');
    crc_be[0] = static_cast<char>((crc >> 24) & 0xFF);
    crc_be[1] = static_cast<char>((crc >> 16) & 0xFF);
    crc_be[2] = static_cast<char>((crc >> 8) & 0xFF);
    crc_be[3] = static_cast<char>(crc & 0xFF);
    cksum->set_value(crc_be);
    sp::TensorFragment* f0 = tensor->add_fragments();
    f0->set_fragment_index(0);
    f0->set_fragment_count(2);
    f0->set_byte_offset(0);
    f0->set_payload(payload.substr(0, 3));
    sp::TensorFragment* f1 = tensor->add_fragments();
    f1->set_fragment_index(1);
    f1->set_fragment_count(2);
    f1->set_byte_offset(3);
    f1->set_payload(payload.substr(3));
    check(stream->Write(chunk), "write chunk");

    sp::SessionResponse echoed;
    check(stream->Read(&echoed), "read chunk echo");
    check(echoed.kind_case() == sp::SessionResponse::kChunk, "chunk echo kind");

    sp::SessionRequest decode;
    sp::DecodeStep* ds = decode.mutable_decode();
    ds->set_idempotency_step(2);
    ds->set_position(1);
    ds->set_work_id("w2");
    sp::TensorBundle* dbundle = ds->mutable_bundle();
    dbundle->set_bundle_version(1);
    sp::NamedTensor* dt = dbundle->add_tensors();
    dt->set_name("hidden_states");
    dt->set_dtype(sp::DTYPE_BFLOAT16);
    dt->set_byte_order(sp::BYTE_ORDER_LITTLE_ENDIAN);
    dt->set_total_bytes(payload.size());
    dt->set_compression(sp::COMPRESSION_NONE);
    sp::Checksum* dck = dt->mutable_checksum();
    dck->set_algorithm(sp::CHECKSUM_ALGORITHM_CRC32C);
    dck->set_value(crc_be);
    sp::TensorFragment* df = dt->add_fragments();
    df->set_fragment_index(0);
    df->set_fragment_count(1);
    df->set_byte_offset(0);
    df->set_payload(payload);
    check(stream->Write(decode), "write decode");

    sp::SessionResponse decode_echo;
    check(stream->Read(&decode_echo), "read decode echo");
    check(decode_echo.kind_case() == sp::SessionResponse::kChunk, "decode echo kind");

    sp::SessionRequest release;
    sp::ReleaseSignal* rs = release.mutable_release();
    rs->set_route_session_id("selftest");
    rs->set_work_id("w-final");
    check(stream->Write(release), "write release");
    stream->WritesDone();

    sp::SessionResponse terminal;
    check(stream->Read(&terminal), "read terminal");
    check(terminal.kind_case() == sp::SessionResponse::kStatus && terminal.status().terminal(),
          "terminal status");

    grpc::Status status = stream->Finish();
    check(status.ok(), "stream finish");
  }

  server->Shutdown();
  server->Wait();

  if (failures == 0) {
    std::cout << "selftest: all lifecycle checks passed\n";
    return 0;
  }
  std::cerr << "selftest: " << failures << " check(s) failed\n";
  return 1;
}

}  // namespace

int main(int argc, char** argv) {
  GOOGLE_PROTOBUF_VERIFY_VERSION;

  for (int i = 1; i < argc; ++i) {
    if (std::strcmp(argv[i], "--selftest") == 0) {
      return RunSelfTest();
    }
  }

  std::string listen_addr = "localhost:50051";
  if (const char* env = std::getenv("MESHNET_SHARD_LISTEN_ADDR")) {
    listen_addr = env;
  }
  if (argc > 1 && argv[1][0] != '-') {
    listen_addr = argv[1];
  }

  meshnet::worker::FlowLimits limits = LimitsFromEnv();
  meshnet::worker::ShardRuntimeServiceImpl service(limits);

  grpc::ServerBuilder builder;
  int selected_port = 0;
  builder.AddListeningPort(listen_addr, grpc::InsecureServerCredentials(), &selected_port);
  // Bounded messages, two layers: a hard transport receive ceiling (never below
  // 4 MiB so the handshake and normal chunks always fit) plus the finer
  // app-level per-tensor RESOURCE_EXHAUSTED check the service enforces against
  // the negotiated max_chunk_bytes. Neither path lets an unbounded frame in.
  constexpr int kTransportFloor = 4 * 1024 * 1024;
  const int transport_max = limits.max_chunk_bytes > static_cast<uint64_t>(kTransportFloor)
                                ? static_cast<int>(limits.max_chunk_bytes)
                                : kTransportFloor;
  builder.SetMaxReceiveMessageSize(transport_max);
  builder.RegisterService(&service);
  std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
  if (!server || selected_port == 0) {
    std::cerr << "failed to bind " << listen_addr << "\n";
    return 1;
  }

  int pipe_fds[2];
  if (::pipe(pipe_fds) != 0) {
    std::cerr << "failed to create shutdown pipe\n";
    return 1;
  }
  g_signal_pipe_write_fd = pipe_fds[1];

  struct sigaction sa;
  std::memset(&sa, 0, sizeof(sa));
  sa.sa_handler = HandleTermination;
  ::sigaction(SIGTERM, &sa, nullptr);
  ::sigaction(SIGINT, &sa, nullptr);

  // Drain thread: wakes on the first termination signal and shuts the server
  // down gracefully so in-flight sessions finish rather than being severed.
  std::thread drain([&server, read_fd = pipe_fds[0]]() {
    char byte = 0;
    ssize_t rc = 0;
    do {
      rc = ::read(read_fd, &byte, 1);
    } while (rc < 0 && errno == EINTR);
    server->Shutdown();
  });

  std::cout << "ShardRuntime worker listening on " << listen_addr << std::endl;

  server->Wait();
  drain.join();
  ::close(pipe_fds[0]);
  ::close(pipe_fds[1]);
  std::cout << "ShardRuntime worker shut down cleanly" << std::endl;
  return 0;
}
