import { readFileSync } from "node:fs";
import https from "node:https";

const namespace = process.env.NAMESPACE || "";
const jobName = process.env.JOB_NAME || "";
const logContainer = process.env.LOG_CONTAINER || "";
const timeoutSeconds = requiredPositiveNumber("TIMEOUT_SECONDS");
const pollIntervalSeconds = requiredPositiveNumber("POLL_INTERVAL_SECONDS");
const host = process.env.KUBERNETES_SERVICE_HOST;
const port = Number(process.env.KUBERNETES_SERVICE_PORT || "443");
const token = readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/token", "utf8").trim();
const ca = readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt");
const manifest = JSON.parse(Buffer.from(readFileSync(0, "utf8").replace(/\s+/g, ""), "base64").toString("utf8"));

function request(method, path, body, contentType = "application/json") {
  return new Promise((resolve, reject) => {
    const headers = { authorization: `Bearer ${token}` };
    const payload = body === undefined ? null : typeof body === "string" ? body : JSON.stringify(body);
    if (payload !== null) {
      headers["content-type"] = contentType;
      headers["content-length"] = Buffer.byteLength(payload);
    }
    const req = https.request({ host, port, path, method, ca, headers }, (res) => {
      let text = "";
      res.setEncoding("utf8");
      res.on("data", (chunk) => { text += chunk; });
      res.on("end", () => resolve({ status: res.statusCode || 0, text }));
    });
    req.on("error", reject);
    if (payload !== null) req.write(payload);
    req.end();
  });
}

function parse(text) {
  try {
    return text ? JSON.parse(text) : null;
  } catch {
    return null;
  }
}

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function condition(job, type) {
  return (Array.isArray(job?.status?.conditions) ? job.status.conditions : []).find((item) => item?.type === type && item?.status === "True") || null;
}

async function getJob() {
  const result = await request("GET", `/apis/batch/v1/namespaces/${encodeURIComponent(namespace)}/jobs/${encodeURIComponent(jobName)}`);
  if (result.status === 404) return null;
  if (result.status < 200 || result.status >= 300) throw new Error(result.text || `kube api GET job status ${result.status}`);
  return parse(result.text);
}

async function podNames() {
  const selector = encodeURIComponent(`job-name=${jobName}`);
  const result = await request("GET", `/api/v1/namespaces/${encodeURIComponent(namespace)}/pods?labelSelector=${selector}`);
  if (result.status < 200 || result.status >= 300) return [];
  const list = parse(result.text);
  return (Array.isArray(list?.items) ? list.items : []).map((pod) => pod?.metadata?.name).filter(Boolean);
}

async function logsTail() {
  const names = await podNames();
  let combined = "";
  for (const pod of names.slice(-2)) {
    const container = logContainer ? `&container=${encodeURIComponent(logContainer)}` : "";
    const result = await request("GET", `/api/v1/namespaces/${encodeURIComponent(namespace)}/pods/${encodeURIComponent(pod)}/log?tailLines=120${container}`);
    if (result.status >= 200 && result.status < 300) combined += `${result.text}\n`;
  }
  return combined.length > 6000 ? combined.slice(-6000) : combined;
}

let created = false;
let reused = false;
let replacedFailed = false;
let existing = await getJob();
if (existing && condition(existing, "Failed")) {
  await deleteJob();
  replacedFailed = true;
  existing = null;
  const deleteDeadline = Date.now() + timeoutSeconds * 1000;
  let deleted = false;
  while (Date.now() <= deleteDeadline) {
    if (await getJob() === null) {
      deleted = true;
      break;
    }
    await delay(pollIntervalSeconds * 1000);
  }
  if (!deleted) throw new Error(`timed out deleting failed job ${jobName}`);
}
if (existing) {
  reused = true;
} else {
  const result = await request("POST", `/apis/batch/v1/namespaces/${encodeURIComponent(namespace)}/jobs`, manifest);
  if (result.status === 409) reused = true;
  else if (result.status >= 200 && result.status < 300) created = true;
  else {
    process.stderr.write(result.text || `kube api POST job status ${result.status}`);
    process.exit(1);
  }
}

const startedAt = Date.now();
const startedAtIso = new Date(startedAt).toISOString();
const deadline = startedAt + timeoutSeconds * 1000;
let polls = 0;
let latest = await getJob();
while (Date.now() <= deadline) {
  const complete = condition(latest, "Complete");
  const failed = condition(latest, "Failed");
  if (complete || failed) break;
  polls += 1;
  await delay(pollIntervalSeconds * 1000);
  latest = await getJob();
}

const complete = condition(latest, "Complete");
const failed = condition(latest, "Failed");
const logs = await logsTail();
const summary = parseLastJsonSummary(logs);
const timedOut = !complete && !failed;
const finishedAt = Date.now();
const output = {
  ok: Boolean(complete) && !timedOut,
  completed: Boolean(complete),
  failed: Boolean(failed),
  timedOut,
  created,
  reused,
  replacedFailed,
  jobName,
  namespace,
  polls,
  elapsedMs: finishedAt - startedAt,
  startedAt: startedAtIso,
  finishedAt: new Date(finishedAt).toISOString(),
  conditionReason: complete?.reason || failed?.reason || null,
  conditionMessage: complete?.message || failed?.message || null,
  logsTail: logs || null,
  summary,
  statusAuthority: "kubernetes-api-serviceaccount",
  parsedDownstreamCliOutput: false,
  valuesRedacted: true,
};
process.stdout.write(JSON.stringify(output));
if (!output.ok) process.exit(1);

async function deleteJob() {
  const result = await request("DELETE", `/apis/batch/v1/namespaces/${encodeURIComponent(namespace)}/jobs/${encodeURIComponent(jobName)}?propagationPolicy=Background`);
  if (result.status === 404) return;
  if (result.status < 200 || result.status >= 300) throw new Error(result.text || `kube api DELETE job status ${result.status}`);
}

function requiredPositiveNumber(name) {
  const value = Number(process.env[name]);
  if (!Number.isFinite(value) || value <= 0) throw new Error(`${name} must be a positive number`);
  return value;
}

function parseLastJsonSummary(text) {
  const lines = String(text || "").split(/\r?\n/u).map((item) => item.trim()).filter(Boolean);
  for (let index = lines.length - 1; index >= 0; index -= 1) {
    try {
      const parsed = JSON.parse(lines[index]);
      if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return compactValue(parsed, 0);
    } catch {
      continue;
    }
  }
  return null;
}

function compactValue(value, depth) {
  if (typeof value === "string") return value.length <= 240 ? value : `${value.slice(0, 160)} ... ${value.slice(-60)}`;
  if (typeof value !== "object" || value === null) return value;
  if (Array.isArray(value)) return value.slice(0, 8).map((item) => compactValue(item, depth + 1));
  if (depth >= 3) return "[bounded-object]";
  const output = {};
  for (const [key, child] of Object.entries(value).slice(0, 16)) output[key] = compactValue(child, depth + 1);
  return output;
}
