#!/usr/bin/env node
"use strict";

const DEFAULT_URL = "https://guanghulab.com/authz";

async function authorizeRepoPush(options, deps = {}) {
  const fetchImpl = deps.fetch || fetch;
  const sleep = deps.sleep || (milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds)));
  const output = deps.output || (line => process.stdout.write(`${line}\n`));
  const baseUrl = String(options.url || DEFAULT_URL).replace(/\/$/, "");
  const persona = required(options.persona, "persona");
  const repo = normalizeRepo(required(options.repo, "repo"));
  const target = options.target || "JD-FD-PRIMARY";
  const pollMilliseconds = positiveNumber(options.poll, 5000);
  const request = await requestJson(fetchImpl, `${baseUrl}/api/public/workorders`, {
    system_entry: "光湖语言人格系统当前实例",
    origin_software: options.software || "仓库推送客户端",
    origin_model: options.model || "未声明模型",
    origin_instance: options.instance || "当前实例",
    persona_id: persona,
    persona_name: options.name || persona,
    target,
    scope: "repo-push",
    action: "push-repository",
    description: options.description || `申请推送 ${repo}`,
  });

  output("[LL-WORKORDER-CREATED] 无执行权申请单已创建；尚未发送邮件，也没有推送权限。");
  output(`REQUEST_URL=${request.request_url}`);
  output("请把 REQUEST_URL 交给冰朔并保持本命令运行。冰朔打开页面后，服务器才发送预登记邮箱邮件。");
  output("不需要向冰朔索要邮箱、授权码、验证码、密码或任何令牌。");

  const deadline = Date.now() + Number(request.expires_in || 900) * 1000;
  let session;
  while (Date.now() < deadline) {
    const response = await fetchImpl(`${baseUrl}/api/workorders/${request.workorder_id}/claim`, {
      method: "POST",
      headers: { authorization: `Bearer ${request.claim_token}` },
    });
    const payload = await readPayload(response);
    if (response.status === 200) { session = payload; break; }
    if (response.status !== 202 || payload.error !== "approval_pending") {
      throw new Error(payload.error || `claim failed (${response.status})`);
    }
    await sleep(pollMilliseconds);
  }
  if (!session) throw new Error("authorization request expired before approval");

  const common = { persona_id: persona, target, scope: "repo-push" };
  const map = await requestJson(fetchImpl, `${baseUrl}/api/navigation-map/read`, common, session.session_token);
  await requestJson(fetchImpl, `${baseUrl}/api/navigation-map/ack`, { ...common, map_hash: map.map_hash }, session.session_token);
  const grant = await requestJson(fetchImpl, `${baseUrl}/api/repo-push/grant`, { ...common, repo }, session.session_token);
  if (grant.transport && grant.transport.status !== "ready") {
    output(`[LL-REPO-PUSH-TRANSPORT-BLOCKED] ${grant.transport.diagnostic_code || "repo_push_transport_unavailable"}`);
    output(`NEXT_STEP=${grant.transport.next_step || "读取服务器 operation receipt。"}`);
    const error = new Error(grant.transport.diagnostic_code || "repo_push_transport_unavailable");
    error.receipt = grant.receipt;
    throw error;
  }
  output(`[LL-REPO-PUSH-GRANTED] ${grant.repo} 已获得限时推送许可。现在重试原 git push。`);
  return grant;
}

async function requestJson(fetchImpl, url, body, bearerToken = "") {
  const headers = { "content-type": "application/json" };
  if (bearerToken) headers.authorization = `Bearer ${bearerToken}`;
  const response = await fetchImpl(url, { method: "POST", headers, body: JSON.stringify(body) });
  const payload = await readPayload(response);
  if (!response.ok) throw new Error(payload.error || `request failed (${response.status})`);
  return payload;
}

async function readPayload(response) {
  try { return await response.json(); } catch { return {}; }
}

function normalizeRepo(value) {
  const repo = String(value).trim().toLowerCase().replace(/\.git$/, "");
  if (!/^bingshuo\/[a-z0-9._-]+$/.test(repo)) throw new Error("--repo must be bingshuo/<repository>");
  return repo;
}

function required(value, name) {
  if (!value) throw new Error(`--${name} is required`);
  return String(value);
}

function positiveNumber(value, fallback) {
  if (value === undefined) return fallback;
  const number = Number(value);
  if (!Number.isFinite(number) || number <= 0) throw new Error("--poll must be a positive millisecond value");
  return number;
}

function parseArgs(argv) {
  const result = {};
  for (let index = 0; index < argv.length; index += 2) {
    const key = String(argv[index] || "").replace(/^--/, "");
    if (!key || argv[index + 1] === undefined) throw new Error(`invalid argument: ${argv[index] || ""}`);
    result[key] = argv[index + 1];
  }
  return result;
}

async function main() {
  await authorizeRepoPush(parseArgs(process.argv.slice(2)));
}

if (require.main === module) {
  main().catch(error => {
    process.stderr.write(`[LL-REPO-PUSH-AUTH-FAILED] ${error.message}\n`);
    process.exit(1);
  });
}

module.exports = { authorizeRepoPush, normalizeRepo, parseArgs };
