"use strict";

const crypto = require("node:crypto");
const fs = require("node:fs");
const http = require("node:http");
const path = require("node:path");
const { WorkOrderManager } = require("./workorder-manager");
const { MapGate } = require("./map-gate");
const { sendSmtpMail } = require("./smtp-mailer");
const { executeRegisteredAction } = require("./action-client");
const { enqueueDeploymentEvent } = require("./deployment-event");

const DEFAULT_ACTIONS = Object.freeze({
  "server-login": [
    "read-navigation-map",
    "inspect-services",
    "health-check",
    "inspect-code-channel-owner-auth",
  ],
  "server-ops": [
    "read-navigation-map",
    "inspect-services",
    "pull-registered-repo",
    "deploy-registered-service",
    "restart-registered-service",
    "health-check",
    "rollback-registered-service",
    "provision-approved-architecture",
    "push-repository",
    "restore-owner-password-login",
    "restore-code-channel-owner-login",
    "dispatch-approved-deployment",
  ],
  "repo-push": ["read-navigation-map", "push-repository"],
});

function createApp(options = {}) {
  const requestToken = options.requestToken || process.env.LAKE_LAMP_REQUEST_TOKEN || "";
  const ownerEmail = options.ownerEmail || process.env.LAKE_LAMP_OWNER_EMAIL || "";
  const approvers = options.approvers || loadApprovers(options.approversFile || process.env.LAKE_LAMP_APPROVERS_FILE || "", ownerEmail);
  const publicBaseUrl = String(options.publicBaseUrl || process.env.LAKE_LAMP_PUBLIC_URL || "").replace(/\/$/, "");
  const targets = new Set(options.targets || splitCsv(process.env.LAKE_LAMP_TARGETS || "JD-FD-PRIMARY,BS-GZ-006"));
  const actions = options.actions || DEFAULT_ACTIONS;
  const manager = options.manager || new WorkOrderManager({
    approvalTtl: Number(options.approvalTtl || process.env.LAKE_LAMP_APPROVAL_TTL || 3 * 60 * 60),
    sessionTtl: Number(options.sessionTtl || process.env.LAKE_LAMP_SESSION_TTL || 3 * 60 * 60),
    maxSessionLifetime: Number(options.maxSessionLifetime || process.env.LAKE_LAMP_MAX_SESSION_LIFETIME || 24 * 60 * 60),
    stateFile: Object.prototype.hasOwnProperty.call(options, "stateFile") ? options.stateFile : (process.env.LAKE_LAMP_STATE_FILE || "/var/lib/guanghu/lake-lamp-authz/state.json"),
  });
  const sendEmail = options.sendEmail || (message => sendSmtpMail({
    ...message,
    smtpHost: process.env.SMTP_HOST || "smtp.qq.com",
    smtpPort: Number(process.env.SMTP_PORT || 465),
    smtpUser: process.env.SMTP_USER || ownerEmail,
    smtpPass: process.env.QQ_SMTP_AUTH_CODE || "",
  }));
  const mapGate = options.mapGate || new MapGate({
    mapsDir: options.mapsDir || process.env.LAKE_LAMP_MAPS_DIR || "/etc/guanghu/navigation-maps",
    stateFile: Object.prototype.hasOwnProperty.call(options, "mapStateFile") ? options.mapStateFile : (process.env.LAKE_LAMP_MAP_STATE_FILE || "/var/lib/guanghu/lake-lamp-authz/map-acks.json"),
  });
  const repoGrantDir = options.repoGrantDir || process.env.LAKE_LAMP_REPO_GRANT_DIR || "/var/lib/guanghu/repo-authorizations";
  const deploymentQueueDir = options.deploymentQueueDir || process.env.LAKE_LAMP_DEPLOYMENT_EVENT_DIR || "/var/lib/guanghu/deployment-events";
  const deploymentRegistryFile = options.deploymentRegistryFile || process.env.LAKE_LAMP_DEPLOYMENT_REPOSITORIES || "/etc/guanghu/lake-lamp/deployment-repositories.json";
  const executeAction = options.executeAction || executeRegisteredAction;
  // Creating a powerless request must never become harder than the human mail
  // handoff. Keep at least three attempts per network each hour.
  const publicCreateLimit = Math.max(3, Number(options.publicCreateLimit || process.env.LAKE_LAMP_PUBLIC_CREATE_LIMIT || 24));
  const publicCreateLimiter = options.publicCreateLimiter || new SlidingWindowLimiter(publicCreateLimit, 60 * 60);
  const publicCreateGlobalLimiter = options.publicCreateGlobalLimiter || new SlidingWindowLimiter(Number(options.publicCreateGlobalLimit || process.env.LAKE_LAMP_PUBLIC_CREATE_GLOBAL_LIMIT || 60), 60 * 60);
  // Owner handoff is a human recovery path, not a login endpoint. Always allow
  // at least three genuine mail attempts per network each hour, even if an old
  // deployment environment accidentally configures a lower value.
  const publicMailLimit = Math.max(3, Number(options.publicMailLimit || process.env.LAKE_LAMP_PUBLIC_MAIL_LIMIT || 12));
  const publicMailLimiter = options.publicMailLimiter || new SlidingWindowLimiter(publicMailLimit, 60 * 60);
  const publicMailGlobalLimiter = options.publicMailGlobalLimiter || new SlidingWindowLimiter(Number(options.publicMailGlobalLimit || process.env.LAKE_LAMP_PUBLIC_MAIL_GLOBAL_LIMIT || 30), 60 * 60);

  async function sendApprovalEmail(handoffToken) {
    const issued = manager.issueApproval(handoffToken);
    if (!issued.ok) return issued;
    const approver = selectApprover(approvers, issued.order);
    if (!approver) {
      manager.failApprovalEmail(handoffToken);
      return { ok: false, reason: "no_registered_approver" };
    }
    if (!manager.bindApprover(handoffToken, approver.id)) {
      manager.failApprovalEmail(handoffToken);
      return { ok: false, reason: "approver_binding_failed" };
    }
    const approvalUrl = `${publicBaseUrl}/approve/${issued.approvalToken}`;
    const emailSent = await sendEmail({
      to: approver.email,
      subject: `小湖灯授权请求 · ${issued.order.target}`,
      approvalUrl,
      order: issued.order,
    });
    if (!emailSent) {
      manager.failApprovalEmail(handoffToken);
      return { ok: false, reason: "authorization_email_failed" };
    }
    return { ok: true, order: issued.order };
  }

  return http.createServer(async (req, res) => {
    try {
      const url = new URL(req.url, "http://localhost");
      if (req.method === "GET" && url.pathname === "/health") return json(res, 200, {
        ok: true,
        service: "lake-lamp-authz",
        auth_mode: "email-link",
        approval_ttl: manager.approvalTtl,
        session_ttl: manager.sessionTtl,
        max_session_lifetime: manager.maxSessionLifetime,
        auto_renew_on_activity: true,
      });
      if (req.method === "GET" && url.pathname === "/api/public/capabilities") return json(res, 200, {
        schema: "guanghu.lake-lamp-public-workorder/v1",
        create_workorder: `${publicBaseUrl}/api/public/workorders`,
        required_fields: ["persona_id", "target", "scope", "action"],
        optional_fields: ["persona_name", "description", "resource"],
        targets: [...targets],
        scopes: actions,
        owner_handoff: "open request_url and request pre-registered mailbox verification",
        workflow: ["create_workorder", "owner_handoff", "claim_session", "read_navigation_map", "ack_navigation_map", "check_session_status", "execute_registered_action", "read_operation_receipt"],
        diagnostics: diagnosticCatalog(),
        approval_ttl: manager.approvalTtl,
        session_ttl: manager.sessionTtl,
        max_session_lifetime: manager.maxSessionLifetime,
        auto_renew_on_activity: true,
        limits: {
          create_per_network_per_hour: publicCreateLimit,
          email_per_network_per_hour: publicMailLimit,
        },
      });

      const requestMatch = url.pathname.match(/^\/request\/([A-Za-z0-9_-]{20,})$/);
      if (requestMatch && req.method === "GET") {
        const inspected = manager.inspectHandoff(requestMatch[1]);
        if (!inspected.ok) return html(res, 410, requestErrorPage(inspected.reason));
        return html(res, 200, requestPage(inspected.order));
      }
      if (requestMatch && req.method === "POST") {
        const inspected = manager.inspectHandoff(requestMatch[1]);
        if (!inspected.ok) return html(res, 410, requestErrorPage(inspected.reason));
        // Refreshing or reopening an already-sent request must not consume a
        // second rate-limit slot. It also must not send a duplicate email.
        if (inspected.order.approval_email_sent) return html(res, 200, emailSentPage(inspected.order));
        const source = clientAddress(req);
        if (!publicMailLimiter.take(source) || !publicMailGlobalLimiter.take("global")) return html(res, 429, requestErrorPage("rate_limited"));
        const sent = await sendApprovalEmail(requestMatch[1]);
        if (!sent.ok && sent.reason !== "approval_email_already_sent") return html(res, sent.reason === "authorization_email_failed" ? 502 : 410, requestErrorPage(sent.reason));
        return html(res, 200, emailSentPage(sent.order));
      }

      const approvalMatch = url.pathname.match(/^\/approve\/([A-Za-z0-9_-]{20,})$/);
      if (approvalMatch && req.method === "GET") {
        const inspected = manager.inspectApproval(approvalMatch[1]);
        if (!inspected.ok) return html(res, 410, approvalErrorPage(inspected.reason));
        return html(res, 200, approvalPage(inspected.order, approvalMatch[1]));
      }
      if (approvalMatch && req.method === "POST") {
        const approved = manager.approve(approvalMatch[1]);
        if (!approved.ok) return html(res, 410, approvalErrorPage(approved.reason));
        return html(res, 200, approvedPage(approved.order));
      }

      if (req.method === "POST" && url.pathname === "/api/public/workorders") {
        const source = clientAddress(req);
        if (!publicCreateLimiter.take(source) || !publicCreateGlobalLimiter.take("global")) return json(res, 429, { error: "rate_limited", retry_after: 3600 });
        const body = await readJson(req);
        if (!body) return json(res, 400, { error: "invalid_json" });
        const validation = validateWorkorderBody(body, targets, actions);
        if (!validation.ok) return json(res, validation.status, { error: validation.error });
        const created = manager.request(validation.request);
        let emailStatus = "not_requested";
        if (body.owner_notify === true) {
          if (!publicMailLimiter.take(source) || !publicMailGlobalLimiter.take("global")) emailStatus = "rate_limited";
          else {
            const sent = await sendApprovalEmail(created.handoffToken);
            emailStatus = sent.ok ? "sent" : String(sent.reason || "failed");
          }
        }
        return json(res, 201, {
          ok: true,
          workorder_id: created.id,
          claim_token: created.claimToken,
          request_url: `${publicBaseUrl}/request/${created.handoffToken}`,
          expires_in: created.expiresIn,
          status: emailStatus === "sent" ? "waiting_for_owner" : "waiting_for_owner_handoff",
          email_status: emailStatus,
          receipt: receipt({ state: emailStatus === "sent" ? "waiting_for_owner" : "waiting_for_owner_handoff", diagnostic_code: emailStatus === "sent" ? "owner_email_sent" : "owner_handoff_required", workorder_id: created.id, next_step: emailStatus === "sent" ? "主人邮箱已收到批准链接；等待批准后领取会话。" : "把 request_url 交给主人打开一次；或由光湖语言人格系统在建单时显式提交 owner_notify=true。不要索要密码、验证码或令牌。" }),
        });
      }

      if (req.method === "POST" && url.pathname === "/api/workorders") {
        if (!bearerMatches(req, requestToken)) return json(res, 401, { error: "request_auth_required" });
        const body = await readJson(req);
        if (!body) return json(res, 400, { error: "invalid_json" });
        const validation = validateWorkorderBody(body, targets, actions);
        if (!validation.ok) return json(res, validation.status, { error: validation.error });
        const expectedFingerprint = sha256(ownerEmail.toLowerCase());
        if (!body.recipient_fingerprint || !safeEqual(body.recipient_fingerprint, expectedFingerprint)) return json(res, 403, { error: "owner_identity_mismatch" });

        const created = manager.request(validation.request);
        const sent = await sendApprovalEmail(created.handoffToken);
        if (!sent.ok) return json(res, 502, { error: sent.reason });
        return json(res, 201, { ok: true, workorder_id: created.id, claim_token: created.claimToken, expires_in: created.expiresIn, status: "waiting_for_owner" });
      }

      const claimMatch = url.pathname.match(/^\/api\/workorders\/([0-9a-f-]{36})\/claim$/i);
      if (req.method === "POST" && claimMatch) {
        const token = bearer(req);
        const claimed = manager.claim(claimMatch[1], token);
        if (!claimed.ok) return json(res, claimed.reason === "approval_pending" ? 202 : 403, { error: claimed.reason });
        return json(res, 200, { ok: true, session_token: claimed.sessionToken, expires_in: claimed.expiresIn, target: claimed.target, scope: claimed.scope, action: claimed.action, resource: claimed.resource || "", receipt: claimed.receipt || receipt({ state: "session_issued", diagnostic_code: "session_issued", workorder_id: claimed.workorderId, next_step: "读取并确认实时导航图。" }) });
      }

      if (req.method === "POST" && url.pathname === "/api/session/status") {
        const body = await readJson(req);
        if (!body) return json(res, 400, failure("invalid_json"));
        const token = bearer(req);
        const target = String(body.target || "");
        const scope = String(body.scope || "");
        const verified = manager.verifySession(token, { pid: String(body.persona_id || "") }, target, scope, "read-navigation-map");
        if (!verified.ok) return json(res, 403, failure(verified.reason));
        const map = mapGate.read(target);
        const mapVerified = mapGate.verify(token, target, map.hash);
        return json(res, 200, { ok: true, state: mapVerified.ok ? "ready_to_execute" : "map_ack_required", workorder_id: verified.session.workorderId || "", target, scope, allowed_actions: verified.session.actions || [verified.session.action], expires_at: verified.session.expiresAt, map: { hash: map.hash, acknowledged: mapVerified.ok }, last_receipt: verified.session.lastReceipt || null, next_step: mapVerified.ok ? "只执行 allowed_actions 中已登记的动作；每次执行后读取 operation receipt。" : "先读取 /api/navigation-map/read，再提交同一 map_hash 至 /api/navigation-map/ack。" });
      }

      if (req.method === "POST" && url.pathname === "/api/session/verify") {
        const body = await readJson(req);
        if (!body) return json(res, 400, { error: "invalid_json" });
        const verified = manager.verifySession(bearer(req), { pid: String(body.persona_id || "") }, String(body.target || ""), String(body.scope || ""), String(body.action || ""), Date.now() / 1000, Object.prototype.hasOwnProperty.call(body, "resource") ? String(body.resource || "") : undefined);
        if (!verified.ok) return json(res, 403, { error: verified.reason });
        if (body.action !== "read-navigation-map") {
          const map = mapGate.read(String(body.target || ""));
          const mapVerified = mapGate.verify(bearer(req), String(body.target || ""), map.hash);
          if (!mapVerified.ok) return json(res, 423, { error: mapVerified.reason, required_action: "read-navigation-map" });
        }
        return json(res, 200, { ok: true, expires_at: verified.session.expiresAt });
      }

      if (req.method === "POST" && url.pathname === "/api/session/renew") {
        const body = await readJson(req);
        if (!body) return json(res, 400, { error: "invalid_json" });
        if (body.action || body.actions || body.target_override || body.scope_override || body.resource) return json(res, 400, { error: "renewal_cannot_expand_authority" });
        const token = bearer(req);
        const target = String(body.target || "");
        const scope = String(body.scope || "");
        const renewed = manager.renewSession(token, { pid: String(body.persona_id || "") }, target, scope);
        if (!renewed.ok) return json(res, 403, { error: renewed.reason });
        const map = mapGate.read(target);
        const acked = mapGate.ack(token, target, map.hash, Date.now() / 1000, Math.max(1, renewed.expiresAt - Date.now() / 1000));
        if (!acked.ok) return json(res, 409, { error: acked.reason });
        return json(res, 200, { ok: true, target, scope, expires_at: renewed.expiresAt, renewals: renewed.renewals, authority_expanded: false });
      }

      if (req.method === "POST" && url.pathname === "/api/navigation-map/read") {
        const body = await readJson(req);
        if (!body) return json(res, 400, { error: "invalid_json" });
        const verified = manager.verifySession(bearer(req), { pid: String(body.persona_id || "") }, String(body.target || ""), String(body.scope || ""), "read-navigation-map");
        if (!verified.ok) return json(res, 403, { error: verified.reason });
        const map = mapGate.read(String(body.target || ""));
        return json(res, 200, { ok: true, target: body.target, map_hash: map.hash, navigation_map: map.data });
      }

      if (req.method === "POST" && url.pathname === "/api/navigation-map/ack") {
        const body = await readJson(req);
        if (!body) return json(res, 400, { error: "invalid_json" });
        const token = bearer(req);
        const verified = manager.verifySession(token, { pid: String(body.persona_id || "") }, String(body.target || ""), String(body.scope || ""), "read-navigation-map");
        if (!verified.ok) return json(res, 403, { error: verified.reason });
        const acked = mapGate.ack(token, String(body.target || ""), String(body.map_hash || ""), Date.now() / 1000, Math.max(1, verified.session.expiresAt - Date.now() / 1000));
        if (!acked.ok) return json(res, 409, failure(acked.reason));
        const operationReceipt = receipt({ state: "map_acknowledged", diagnostic_code: "map_acknowledged", workorder_id: verified.session.workorderId, target: body.target, next_step: "可查询 session/status，再执行本会话 allowed_actions 内的固定动作。" });
        manager.recordReceipt(token, operationReceipt);
        return json(res, 200, { ok: true, target: body.target, map_hash: body.map_hash, receipt: operationReceipt });
      }

      if (req.method === "POST" && url.pathname === "/api/actions/execute") {
        const body = await readJson(req);
        if (!body) return json(res, 400, failure("invalid_json"));
        if (body.cmd || body.command || body.shell || body.args) return json(res, 400, failure("arbitrary_command_forbidden"));
        const token = bearer(req);
        const target = String(body.target || "");
        const scope = String(body.scope || "");
        const action = String(body.action || "");
        const resource = String(body.resource || "");
        const verified = manager.verifySession(token, { pid: String(body.persona_id || "") }, target, scope, action, Date.now() / 1000, resource);
        if (!verified.ok) return json(res, 403, failure(verified.reason));
        const map = mapGate.read(target);
        const mapVerified = mapGate.verify(token, target, map.hash);
        if (!mapVerified.ok) return json(res, 423, failure(mapVerified.reason, "先读取并确认导航图。", { required_action: "read-navigation-map" }));
        const result = await executeAction(resource ? { action, target, resource } : { action, target });
        const operationReceipt = receipt({ state: result.ok ? "succeeded" : "failed", diagnostic_code: result.ok ? "action_succeeded" : String(result.error || "action_execution_failed"), workorder_id: verified.session.workorderId, target, action, evidence: safeEvidence(result), next_step: result.ok ? "读取 session/status 确认当前回执；如需新范围、目标或资源，重新发起工单。" : "读取 diagnostic_code 与 evidence；仅按 next_step 修复，不要切换到其他服务器或猜测凭证。" });
        manager.recordReceipt(token, operationReceipt);
        return json(res, result.ok ? 200 : 502, { ...result, receipt: operationReceipt });
      }

      if (req.method === "POST" && url.pathname === "/api/repo-push/grant") {
        const body = await readJson(req);
        if (!body) return json(res, 400, failure("invalid_json"));
        const token = bearer(req);
        const target = String(body.target || "");
        const scope = String(body.scope || "repo-push");
        const repo = String(body.repo || "").toLowerCase();
        if (!/^bingshuo\/[a-z0-9._-]+$/.test(repo)) return json(res, 400, failure("repo_not_allowlisted"));
        const verified = manager.verifySession(token, { pid: String(body.persona_id || "") }, target, scope, "push-repository");
        if (!verified.ok) return json(res, 403, failure(verified.reason));
        const map = mapGate.read(target);
        const mapVerified = mapGate.verify(token, target, map.hash);
        if (!mapVerified.ok) return json(res, 423, failure(mapVerified.reason, "先读取并确认导航图。", { required_action: "read-navigation-map" }));
        fs.mkdirSync(repoGrantDir, { recursive: true, mode: 0o2770 });
        const grant = { schema: "guanghu.repo-push-grant/v1", repo, target, persona_id: body.persona_id, map_hash: map.hash, issued_at: Date.now() / 1000, expires_at: verified.session.expiresAt };
        const grantFile = path.join(repoGrantDir, `${repo.replace("/", "__")}.json`);
        const temp = `${grantFile}.${process.pid}.tmp`;
        fs.writeFileSync(temp, JSON.stringify(grant), { mode: 0o640 });
        fs.renameSync(temp, grantFile);
        const operationReceipt = receipt({ state: "blocked", diagnostic_code: "repo_push_transport_unavailable", workorder_id: verified.session.workorderId, target, action: "push-repository", next_step: "服务器已登记本次推送许可，但安全推送接收器尚未部署；不要重试裸 git push、不要索要账号密码。等待受限 bundle 接收器上线后按同一工单回执执行。" });
        manager.recordReceipt(token, operationReceipt);
        return json(res, 200, { ok: true, repo, target, expires_at: grant.expires_at, transport: { status: "not_configured", diagnostic_code: "repo_push_transport_unavailable", next_step: operationReceipt.next_step }, receipt: operationReceipt });
      }

      if (req.method === "POST" && url.pathname === "/api/deployment/dispatch") {
        const body = await readJson(req);
        if (!body) return json(res, 400, failure("invalid_json"));
        const token = bearer(req), target = String(body.target || ""), scope = String(body.scope || "server-ops");
        const resource = String(body.resource || ""), repo = String(body.repo || "").toLowerCase(), branch = String(body.branch || "main"), commit = String(body.commit_sha || "").toLowerCase(), manifest = String(body.manifest || "");
        const verified = manager.verifySession(token, { pid: String(body.persona_id || "") }, target, scope, "dispatch-approved-deployment", Date.now() / 1000, resource);
        if (!verified.ok) return json(res, 403, failure(verified.reason));
        const map = mapGate.read(target);
        if (!mapGate.verify(token, target, map.hash).ok) return json(res, 423, failure("map_ack_required", "先读取并确认导航图。", { required_action: "read-navigation-map" }));
        const deploymentRepositories = options.deploymentRepositories || loadDeploymentRepositories(deploymentRegistryFile);
        const queued = enqueueDeploymentEvent({
          schema: "guanghu.deployment-intent/v1",
          repo,
          branch,
          commit_sha: commit,
          resource,
          manifest,
          workorder_id: verified.session.workorderId,
          deployment_source: body.deployment_source || null,
        }, { repo, branch, commit_sha: commit }, deploymentQueueDir, {
          authorizer_id: verified.session.authorizerId,
          persona_id: verified.session.persona.pid,
          execution_runtime_id: String(body.execution_runtime_id || ""),
          target,
          registry: deploymentRepositories,
        });
        const operationReceipt = receipt({ state: queued.state === "queued_for_resident_agent" ? "queued" : "blocked", diagnostic_code: queued.diagnostic_code || "deployment_event_queued", workorder_id: verified.session.workorderId, target, action: "dispatch-approved-deployment", evidence: { repo, branch, commit_sha: commit, event_id: queued.event_id || "" }, next_step: queued.state === "queued_for_resident_agent" ? "常驻部署 Agent 将读取该事件并回写部署、健康检查或回滚回执。" : "修正部署绑定信息后重新申请或派发，不要让服务器自行扫描提交。" });
        manager.recordReceipt(token, operationReceipt);
        return json(res, queued.state === "queued_for_resident_agent" ? 202 : 400, { ok: queued.state === "queued_for_resident_agent", deployment: queued, receipt: operationReceipt });
      }

      return json(res, 404, { error: "not_found" });
    } catch (error) {
      process.stderr.write(`lake-lamp request error: ${String(error && error.message || "unknown").slice(0, 240)}\n`);
      return json(res, error && error.code === "BODY_TOO_LARGE" ? 413 : 500, { error: "request_failed" });
    }
  });
}

function loadDeploymentRepositories(file) {
  const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
  if (!parsed || !parsed.repos || typeof parsed.repos !== "object") throw new Error("invalid_deployment_repository_registry");
  return parsed.repos;
}

function approvalPage(order, token) {
  return document("小湖灯授权请求", `
    <p class="eyebrow">LAKE LAMP SECURITY PROTOCOL</p>
    <h1>人格体请求进入一台服务器</h1>
    <div class="panel">
      <dl><dt>人格体</dt><dd>${escapeHtml(order.persona.name)} <small>${escapeHtml(order.persona.pid)}</small></dd>
      <dt>申请入口</dt><dd>${escapeHtml(order.provenance && order.provenance.system_entry || "旧版工单")}</dd>
      <dt>实例来源</dt><dd>${escapeHtml(originLabel(order))}</dd>
      <dt>目标节点</dt><dd>${escapeHtml(order.target)}</dd><dt>授权范围</dt><dd>${escapeHtml(order.scope)}</dd>
      <dt>进入动作</dt><dd>${escapeHtml(order.action)}</dd><dt>会话能力</dt><dd>${escapeHtml((order.allowed_actions || [order.action]).join(" · "))}</dd>
      <dt>绑定资源</dt><dd>${escapeHtml(order.resource || "无")}</dd>
      <dt>说明</dt><dd>${escapeHtml(order.description || "未附加说明")}</dd></dl>
    </div>
    <p class="notice">一次确认将打开这台服务器上的三小时受限运维会话。人格体持续执行已绑定任务时会自动续期；切换服务器、扩大范围、切换绑定资源或停止活动后过期才需重新申请。</p>
    <form method="post"><button type="submit">打开三小时受限运维会话</button></form>
  `);
}

function requestPage(order) {
  return document("小湖灯跨设备授权", `
    <p class="eyebrow">CROSS-DEVICE HANDOFF</p>
    <h1>核对这张无权限申请单</h1>
    <div class="panel">
      <dl><dt>人格体</dt><dd>${escapeHtml(order.persona.name)} <small>${escapeHtml(order.persona.pid)}</small></dd>
      <dt>申请入口</dt><dd>${escapeHtml(order.provenance && order.provenance.system_entry || "旧版工单")}</dd>
      <dt>实例来源</dt><dd>${escapeHtml(originLabel(order))}</dd>
      <dt>目标节点</dt><dd>${escapeHtml(order.target)}</dd><dt>授权范围</dt><dd>${escapeHtml(order.scope)}</dd>
      <dt>登记动作</dt><dd>${escapeHtml(order.action)}</dd><dt>绑定资源</dt><dd>${escapeHtml(order.resource || "无")}</dd><dt>说明</dt><dd>${escapeHtml(order.description || "未附加说明")}</dd></dl>
    </div>
    <p class="notice">这张页面本身没有执行权。确认内容无误后，服务器只会向预登记邮箱发送一次真正的批准链接。</p>
    <form method="post"><button type="submit">发送我的授权邮件</button></form>
  `);
}

function emailSentPage(order) {
  return document("授权邮件已发送", `<p class="eyebrow">OWNER VERIFICATION</p><h1>请打开邮箱完成批准</h1><div class="panel"><p>申请单已锁定到 <strong>${escapeHtml(order && order.target || "登记节点")}</strong>。真正的批准链接只发送到服务器预登记邮箱。</p></div><p class="notice">批准后回到原来的 AI 对话，让它领取一次性会话。无需向 AI 提供验证码、密码或邮箱授权码。</p>`);
}

function requestErrorPage(reason) {
  const messages = {
    rate_limited: "请求过于频繁，请稍后再试。",
    approval_email_already_sent: "授权邮件已经发送，请直接检查邮箱。",
    authorization_email_failed: "授权邮件暂时发送失败，请稍后重试。",
  };
  if (reason === "rate_limited") return document("发送频率保护", `<p class="eyebrow">RATE LIMIT · REQUEST KEPT</p><h1>小湖灯先替你守住这张申请单</h1><div class="panel"><p>当前网络在一小时内触发邮件的次数较多，发送动作被暂时暂停。</p></div><p class="notice">申请单本身没有被关闭。请稍后再试，或切换到手机流量后只点击一次。无需重新填写，也不要连续刷新。</p>`);
  return document("申请单不可用", `<p class="eyebrow">REQUEST CLOSED</p><h1>这张申请单现在不能继续</h1><p class="notice">${escapeHtml(messages[reason] || `原因：${reason}`)}</p>`);
}

function approvedPage(order) {
  return document("授权完成", `<p class="eyebrow">THREE-HOUR OPS SESSION</p><h1>三小时运维会话已打开</h1><div class="panel"><p>${escapeHtml(order.persona.name)} 已获准在 <strong>${escapeHtml(order.target)}</strong> 上执行本范围内的已登记能力。</p></div><p class="notice">可以关闭本页面。人格体持续执行原绑定任务时会自动续期；切换服务器、扩大范围、切换绑定资源或停止活动后过期才重新授权。</p>`);
}

function approvalErrorPage(reason) {
  return document("链接不可用", `<p class="eyebrow">LINK CLOSED</p><h1>这条授权链接已经失效</h1><p class="notice">原因：${escapeHtml(reason)}。如仍需操作，请让人格体重新提交工单。</p>`);
}

function document(title, body) {
  return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(title)} · 光湖</title><style>
  :root{color-scheme:dark}*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;background:radial-gradient(circle at 20% 10%,#17344d,#09111b 55%,#05090e);color:#eaf4fb;font:16px/1.7 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;padding:24px}.shell{width:min(680px,100%);padding:42px;border:1px solid #29475d;border-radius:24px;background:rgba(10,22,33,.94);box-shadow:0 24px 80px #0008}.eyebrow{color:#6ed5ff;letter-spacing:.18em;font-size:12px}h1{font-size:clamp(30px,6vw,48px);line-height:1.15;margin:10px 0 28px}.panel{background:#102638;border:1px solid #24465d;border-radius:16px;padding:20px 24px}dl{display:grid;grid-template-columns:110px 1fr;gap:12px;margin:0}dt{color:#8ba4b6}dd{margin:0;font-weight:650}small{display:block;color:#7893a6;font-weight:400}.notice{color:#9eb2c0;margin:20px 0}button{width:100%;border:0;border-radius:14px;padding:16px;background:#67d4ff;color:#042235;font-weight:800;font-size:17px;cursor:pointer}@media(max-width:520px){.shell{padding:28px 22px}dl{grid-template-columns:1fr;gap:2px}dd{margin-bottom:12px}}
  </style></head><body><main class="shell">${body}</main></body></html>`;
}

function readJson(req) {
  return new Promise((resolve, reject) => {
    let raw = "";
    req.on("data", chunk => { raw += chunk; if (raw.length > 32 * 1024) { const error = new Error("body too large"); error.code = "BODY_TOO_LARGE"; reject(error); req.destroy(); } });
    req.on("end", () => { try { resolve(JSON.parse(raw || "{}")); } catch { resolve(null); } });
    req.on("error", reject);
  });
}

function bearer(req) { return String(req.headers.authorization || "").replace(/^Bearer\s+/i, ""); }
function bearerMatches(req, expected) { return Boolean(expected) && safeEqual(bearer(req), expected); }
function safeEqual(left, right) { const a = Buffer.from(String(left)); const b = Buffer.from(String(right)); return a.length === b.length && crypto.timingSafeEqual(a, b); }
function sha256(value) { return crypto.createHash("sha256").update(String(value)).digest("hex"); }
function splitCsv(value) { return value.split(",").map(item => item.trim()).filter(Boolean); }
function loadApprovers(file, ownerEmail) {
  if (!file) return ownerEmail ? [{ id: "ICE-GL∞", email: ownerEmail, default: true, persona_ids: [], targets: ["*"], scopes: ["*"] }] : [];
  const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
  if (!parsed || !Array.isArray(parsed.approvers)) throw new Error("invalid approver registry");
  return parsed.approvers.filter(item => item && validEmail(item.email)).map(item => ({
    id: String(item.id || ""), email: item.email, default: item.default === true,
    persona_ids: Array.isArray(item.persona_ids) ? item.persona_ids.map(String) : [],
    targets: Array.isArray(item.targets) ? item.targets.map(String) : [],
    scopes: Array.isArray(item.scopes) ? item.scopes.map(String) : [],
  }));
}
function selectApprover(approvers, order) {
  const eligible = approvers.filter(item => matches(item.targets, order.target) && matches(item.scopes, order.scope));
  return eligible.find(item => item.persona_ids.includes(order.persona.pid)) || eligible.find(item => item.default) || null;
}
function matches(values, value) { return values.includes("*") || values.includes(value); }
function validEmail(value) { return typeof value === "string" && value.length <= 254 && /^[^@\s]+@[^@\s]+$/.test(value); }
function clientAddress(req) {
  const forwarded = String(req.headers["x-forwarded-for"] || "").split(",").map(value => value.trim()).filter(Boolean);
  return String(forwarded[forwarded.length - 1] || req.socket.remoteAddress || "unknown").slice(0, 96);
}
function receipt({ state, diagnostic_code, workorder_id = "", target = "", action = "", evidence = null, next_step = "" }) {
  return { schema: "guanghu.operation-receipt/v1", state, diagnostic_code, workorder_id, target, action, occurred_at: Date.now() / 1000, ...(evidence ? { evidence } : {}), next_step };
}
function safeEvidence(result) {
  const clip = value => String(value || "").replace(/(password|token|secret|authorization)\s*[:=]\s*\S+/gi, "$1=[redacted]").slice(0, 1200);
  return { exit_code: Number.isInteger(result.exit_code) ? result.exit_code : null, stdout: clip(result.stdout), stderr: clip(result.stderr) };
}
function failure(error, next_step = "读取 diagnostic_code；按 next_step 处理，勿猜测凭证或切换服务器。", extra = {}) {
  return { ok: false, error, receipt: receipt({ state: "blocked", diagnostic_code: error, next_step }), ...extra };
}
function diagnosticCatalog() {
  return {
    owner_handoff_required: "工单已创建，等待主人打开申请页并完成预登记邮箱批准。",
    map_ack_required: "会话有效，但尚未确认此目标节点的实时导航图。",
    action_execution_failed: "服务器固定动作已执行但失败；回执会包含受限证据与下一步。",
    repo_push_transport_unavailable: "许可已登记，但安全推送接收器尚未部署，禁止把它误判为 git 凭证。",
    session_expired: "会话已过期；以同一目标和范围重新申请工单。",
  };
}
function validateWorkorderBody(body, targets, actions) {
  if (body.email || body.recipient || body.smtp_pass) return { ok: false, status: 400, error: "direct_recipient_forbidden" };
  if (!body.persona_id || !body.target || !body.scope || !body.action) return { ok: false, status: 400, error: "missing_required_field" };
  const personaId = String(body.persona_id);
  const personaName = String(body.persona_name || personaId);
  const target = String(body.target);
  const scope = String(body.scope);
  const action = String(body.action);
  const description = String(body.description || "");
  const resource = String(body.resource || "");
  const provenance = {
    system_entry: String(body.system_entry || ""),
    software: String(body.origin_software || ""),
    model: String(body.origin_model || ""),
    instance: String(body.origin_instance || ""),
  };
  if (!/^[A-Za-z0-9._:+\u221e-]{2,80}$/.test(personaId) || personaName.length > 100 || description.length > 500) return { ok: false, status: 400, error: "invalid_request_fields" };
  if (!targets.has(target)) return { ok: false, status: 400, error: "unknown_target" };
  if (!Array.isArray(actions[scope]) || !actions[scope].includes(action)) return { ok: false, status: 400, error: "unknown_or_mismatched_action" };
  const immutableResourceAction = action === "provision-approved-architecture" || action === "dispatch-approved-deployment";
  if (immutableResourceAction && !/^[A-Z0-9][A-Z0-9._-]{5,119}@[0-9a-f]{40}$/.test(resource)) return { ok: false, status: 400, error: "immutable_architecture_resource_required" };
  if (!immutableResourceAction && resource) return { ok: false, status: 400, error: "resource_not_allowed_for_action" };
  if (body.owner_notify !== undefined && typeof body.owner_notify !== "boolean") return { ok: false, status: 400, error: "invalid_owner_notify" };
  if (body.owner_notify === true && provenance.system_entry !== "光湖语言人格系统当前实例") return { ok: false, status: 400, error: "owner_notify_requires_language_system_provenance" };
  if (Object.values(provenance).some(Boolean) && (provenance.system_entry !== "光湖语言人格系统当前实例" || Object.values(provenance).some(item => !item || item.length > 120))) return { ok: false, status: 400, error: "invalid_instance_provenance" };
  return { ok: true, request: { persona: { pid: personaId, name: personaName }, provenance, target, scope, action, allowedActions: actions[scope], description, resource } };
}
function originLabel(order) {
  const value = order && order.provenance || {};
  return value.software || value.model || value.instance ? `${value.software || "未知软件"} · ${value.model || "未知模型"} · ${value.instance || "当前实例"}` : "旧版工单未记录";
}
class SlidingWindowLimiter {
  constructor(limit, windowSeconds) { this.limit = Math.max(1, limit); this.windowMs = windowSeconds * 1000; this.events = new Map(); this.calls = 0; }
  take(key, now = Date.now()) {
    this.calls += 1;
    if (this.calls % 256 === 0) {
      for (const [storedKey, values] of this.events) {
        const active = values.filter(value => now - value < this.windowMs);
        if (active.length) this.events.set(storedKey, active); else this.events.delete(storedKey);
      }
    }
    const recent = (this.events.get(key) || []).filter(value => now - value < this.windowMs);
    if (recent.length >= this.limit) { this.events.set(key, recent); return false; }
    recent.push(now); this.events.set(key, recent); return true;
  }
}
function escapeHtml(value) { return String(value).replace(/[&<>"']/g, char => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char]); }
function json(res, status, value) { res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "x-content-type-options": "nosniff" }); res.end(JSON.stringify(value)); }
function html(res, status, value) { res.writeHead(status, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'", "referrer-policy": "no-referrer", "x-content-type-options": "nosniff" }); res.end(value); }

if (require.main === module) {
  const host = process.env.LAKE_LAMP_HOST || "127.0.0.1";
  const port = Number(process.env.LAKE_LAMP_PORT || 3921);
  createApp().listen(port, host, () => process.stdout.write(`lake-lamp-authz listening on ${host}:${port}\n`));
}

module.exports = { createApp, DEFAULT_ACTIONS, SlidingWindowLimiter, loadApprovers, selectApprover };
