"use strict";

const crypto = require("node:crypto");
const fs = require("node:fs");
const path = require("node:path");

const POLL_FIELDS = [
  "schema", "node_id", "issued_at_unix", "nonce",
];
const RESULT_FIELDS = [
  "schema", "node_id", "job_id", "layout_payload_sha256",
  "signature_hex", "issued_at_unix", "nonce",
];

class GhdrControllerBroker {
  constructor({
    controllers,
    stateDir = "/var/lib/guanghu/lake-lamp-authz/ghdr-jobs",
    authorizer,
    now = () => Math.floor(Date.now() / 1000),
    waitMs = 45000,
  }) {
    if (!Array.isArray(controllers) || controllers.length !== 2) {
      throw new Error("ghdr_controller_registry_requires_exactly_two");
    }
    this.controllers = new Map(controllers.map(item => [item.node_id, validateController(item)]));
    if (this.controllers.size !== 2
      || new Set(controllers.map(item => item.failure_domain)).size !== 2) {
      throw new Error("ghdr_controller_registry_not_independent");
    }
    this.stateDir = stateDir;
    this.authorizer = authorizer;
    this.now = now;
    this.waitMs = waitMs;
    this.replay = new Map();
    fs.mkdirSync(this.stateDir, { recursive: true, mode: 0o700 });
  }

  async queueAndWait({ plan, binding, workorderId }) {
    const jobId = crypto.randomUUID();
    const job = {
      schema: "guanghu.ghdr-controller-job/v1",
      job_id: jobId,
      created_at_unix: this.now(),
      expires_at_unix: this.now() + 120,
      layout_payload_sha256: binding.payload_sha256,
      resource: binding.resource,
      workorder_id: workorderId,
      plan,
      authorizations: {},
      results: {},
    };
    for (const controller of this.controllers.values()) {
      job.authorizations[controller.node_id] = this.authorizer.issue({
        controllerNodeId: controller.node_id,
        targetNodeId: "GH-CVM-MAIN-PROD-01",
        layoutPayloadSha256: binding.payload_sha256,
        resource: binding.resource,
        workorderId,
      });
    }
    this.writeJob(job);
    const deadline = Date.now() + this.waitMs;
    while (Date.now() < deadline) {
      const current = this.readJob(jobId);
      if (Object.keys(current.results).length === 2) {
        return {
          ok: true,
          signatures: [...this.controllers.keys()].map(nodeId => current.results[nodeId]),
        };
      }
      await new Promise(resolve => setTimeout(resolve, 250));
    }
    return { ok: false, error: "ghdr_controller_poll_timeout" };
  }

  poll(body) {
    const authenticated = this.authenticate(body && body.request, body && body.request_signature_hex, POLL_FIELDS, "guanghu.ghdr-controller-poll/v1");
    if (!authenticated.ok) return authenticated;
    const nodeId = authenticated.node_id;
    const names = fs.readdirSync(this.stateDir).filter(name => /^[0-9a-f-]{36}\.json$/.test(name)).sort();
    for (const name of names) {
      const job = this.readJob(name.slice(0, -5));
      if (job.expires_at_unix <= this.now() || job.results[nodeId]) continue;
      return {
        ok: true,
        job: {
          job_id: job.job_id,
          expires_at_unix: job.expires_at_unix,
          layout_payload_sha256: job.layout_payload_sha256,
          authorization: job.authorizations[nodeId],
          plan: job.plan,
        },
      };
    }
    return { ok: true, job: null };
  }

  submit(body) {
    const authenticated = this.authenticate(body && body.request, body && body.request_signature_hex, RESULT_FIELDS, "guanghu.ghdr-controller-result/v1");
    if (!authenticated.ok) return authenticated;
    const request = body.request;
    const job = this.readJob(request.job_id);
    if (job.expires_at_unix <= this.now()) return { ok: false, error: "ghdr_job_expired" };
    if (job.layout_payload_sha256 !== request.layout_payload_sha256) {
      return { ok: false, error: "ghdr_result_layout_mismatch" };
    }
    const controller = this.controllers.get(request.node_id);
    const signature = body.signature;
    if (!signature
      || signature.node_id !== controller.node_id
      || signature.failure_domain !== controller.failure_domain
      || signature.public_key_hex !== controller.layout_public_key_hex
      || signature.signature_hex !== request.signature_hex) {
      return { ok: false, error: "ghdr_result_binding_mismatch" };
    }
    const payload = Buffer.from(JSON.stringify(job.plan.payload));
    if (!verifyHex(controller.layout_public_key_hex, payload, request.signature_hex)) {
      return { ok: false, error: "ghdr_layout_signature_invalid" };
    }
    if (job.results[controller.node_id]) {
      return { ok: false, error: "ghdr_result_already_submitted" };
    }
    job.results[controller.node_id] = signature;
    this.writeJob(job);
    return { ok: true };
  }

  authenticate(request, signatureHex, fields, schema) {
    if (!request || typeof request !== "object" || JSON.stringify(Object.keys(request)) !== JSON.stringify(fields)) {
      return { ok: false, error: "ghdr_controller_auth_not_canonical" };
    }
    if (request.schema !== schema || !this.controllers.has(request.node_id)) {
      return { ok: false, error: "ghdr_controller_auth_unknown" };
    }
    if (!Number.isSafeInteger(request.issued_at_unix)
      || Math.abs(this.now() - request.issued_at_unix) > 30
      || !/^[A-Za-z0-9_-]{32}$/.test(String(request.nonce || ""))) {
      return { ok: false, error: "ghdr_controller_auth_expired" };
    }
    const controller = this.controllers.get(request.node_id);
    const canonical = Buffer.from(JSON.stringify(request));
    if (!verifyHex(controller.transport_public_key_hex, canonical, signatureHex)) {
      return { ok: false, error: "ghdr_controller_auth_invalid" };
    }
    const replay = crypto.createHash("sha256").update(canonical).digest("hex");
    for (const [digest, expires] of this.replay) {
      if (expires < this.now()) this.replay.delete(digest);
    }
    if (this.replay.has(replay)) return { ok: false, error: "ghdr_controller_auth_replayed" };
    this.replay.set(replay, this.now() + 35);
    return { ok: true, node_id: request.node_id };
  }

  readJob(jobId) {
    if (!/^[0-9a-f-]{36}$/.test(String(jobId))) throw new Error("ghdr_job_id_invalid");
    return JSON.parse(fs.readFileSync(path.join(this.stateDir, `${jobId}.json`), "utf8"));
  }

  writeJob(job) {
    const destination = path.join(this.stateDir, `${job.job_id}.json`);
    const temporary = `${destination}.${process.pid}.tmp`;
    fs.writeFileSync(temporary, JSON.stringify(job), { mode: 0o600 });
    fs.renameSync(temporary, destination);
  }
}

function validateController(controller) {
  if (!controller
    || !/^GH-CTRL-(GZ|SG)-01$/.test(String(controller.node_id))
    || !/^tencent\/ap-(guangzhou|singapore)\/[A-Z0-9-]+$/.test(String(controller.failure_domain))
    || !/^[0-9a-f]{64}$/.test(String(controller.layout_public_key_hex))
    || !/^[0-9a-f]{64}$/.test(String(controller.transport_public_key_hex))) {
    throw new Error("ghdr_controller_registry_invalid");
  }
  return Object.freeze({ ...controller });
}

function verifyHex(publicKeyHex, message, signatureHex) {
  if (!/^[0-9a-f]{128}$/.test(String(signatureHex || ""))) return false;
  const prefix = Buffer.from("302a300506032b6570032100", "hex");
  const publicKey = crypto.createPublicKey({
    key: Buffer.concat([prefix, Buffer.from(publicKeyHex, "hex")]),
    format: "der",
    type: "spki",
  });
  return crypto.verify(null, message, publicKey, Buffer.from(signatureHex, "hex"));
}

function loadControllers(file) {
  const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
  return parsed.controllers;
}

module.exports = { GhdrControllerBroker, loadControllers, verifyHex };
