#!/usr/bin/env node
"use strict";
const fs = require("node:fs");
const path = require("node:path");
const { transition } = require("./state");
const { sendSmtpMail } = require("../lake-lamp-authz/smtp-mailer");

async function main() {
  const config = JSON.parse(fs.readFileSync(process.env.SENTINEL_CONFIG || "/etc/guanghu/state-change-sentinel.json", "utf8"));
  const stateFile = process.env.SENTINEL_STATE || "/var/lib/guanghu/state-change-sentinel/state.json";
  const previous = fs.existsSync(stateFile) ? JSON.parse(fs.readFileSync(stateFile, "utf8")) : {};
  const next = {};
  const notices = [];
  for (const target of config.targets) {
    const current = await check(target);
    next[target.id] = current;
    const changed = transition(previous[target.id], current);
    if (changed.notify) notices.push({ id: target.id, kind: changed.kind, detail: current.detail });
  }
  fs.mkdirSync(path.dirname(stateFile), { recursive: true, mode: 0o700 });
  fs.writeFileSync(stateFile, JSON.stringify(next), { mode: 0o600 });
  if (!notices.length) return;
  const lines = notices.map(item => `${item.kind === "anomaly" ? "异常" : "恢复"}: ${item.id} · ${item.detail}`);
  await sendSmtpMail({
    to: process.env.AUTH_RECIPIENT,
    subject: `光湖节点状态变化 · ${notices.map(item => item.id).join(", ")}`,
    text: ["光湖节点状态发生变化。静止在线期间不会发送邮件。", "", ...lines, "", new Date().toISOString()].join("\n"),
    smtpHost: process.env.SMTP_HOST || "smtp.qq.com",
    smtpPort: Number(process.env.SMTP_PORT || 465),
    smtpUser: process.env.SMTP_USER,
    smtpPass: process.env.SMTP_PASS,
  });
}

async function check(target) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), target.timeout_ms || 8000);
  try {
    const response = await fetch(target.url, { method: "GET", redirect: "manual", signal: controller.signal, headers: { "user-agent": "Guanghu-State-Change-Sentinel/1.0" } });
    const ok = (target.accept || [200]).includes(response.status);
    return { ok, detail: `HTTP ${response.status}`, observed_at: new Date().toISOString() };
  } catch (error) {
    return { ok: false, detail: error.name === "AbortError" ? "timeout" : "connection-failed", observed_at: new Date().toISOString() };
  } finally { clearTimeout(timer); }
}

main().catch(error => { process.stderr.write(`state sentinel failed: ${error.message}\n`); process.exit(1); });
