#!/usr/bin/env node
// 打包并可选上传云函数到指定上传接口的工具脚本（通用模版）
// 依赖: archiver, node-fetch, form-data
const fs = require('fs');
const path = require('path');

async function ensureDeps() {
  try {
    require.resolve('archiver');
    require.resolve('node-fetch');
    require.resolve('form-data');
  } catch (e) {
    console.error('\n依赖缺失：请先在仓库根或 server 目录安装依赖：');
    console.error('  cd server');
    console.error('  npm install archiver node-fetch form-data');
    process.exit(1);
  }
}

const archiver = require('archiver');
const fetch = require('node-fetch');
const FormData = require('form-data');
const { spawnSync } = require('child_process');

function packDirToZip(srcDir, outPath) {
  return new Promise((resolve, reject) => {
    const output = fs.createWriteStream(outPath);
    const archive = archiver('zip', { zlib: { level: 9 } });
    output.on('close', () => resolve({ bytes: archive.pointer(), path: outPath }));
    archive.on('error', err => reject(err));
    archive.pipe(output);
    archive.directory(srcDir, false);
    archive.finalize();
  });
}

async function uploadZip(url, token, zipPath, extraFields = {}) {
  const form = new FormData();
  form.append('file', fs.createReadStream(zipPath));
  for (const k of Object.keys(extraFields)) form.append(k, extraFields[k]);

  const headers = token ? { Authorization: `Bearer ${token}` } : {};
  const resp = await fetch(url, { method: 'POST', headers: Object.assign(headers, form.getHeaders()), body: form });
  const text = await resp.text();
  let json;
  try { json = JSON.parse(text); } catch (e) { json = { statusText: text }; }
  return { ok: resp.ok, status: resp.status, body: json };
}

function runUnicloudUpload(spaceId, funcDir, funcName) {
  try {
    // 尝试使用全局 unicloud
    let args = ['upload', '-p', spaceId, '-f', funcName, funcDir];
    console.log('尝试使用全局 `unicloud` CLI：', ['unicloud', ...args].join(' '));
    let r = spawnSync('unicloud', args, { stdio: 'inherit' });
    if (r && r.status === 0) return { ok: true };

    // 否则尝试使用 npx 调用包内命令（不要求全局安装）
    args = ['@dcloudio/unicloud-cli', 'upload', '-p', spaceId, '-f', funcName, funcDir];
    console.log('尝试使用 npx 调用 unicloud-cli：', ['npx', ...args].join(' '));
    r = spawnSync('npx', args, { stdio: 'inherit' });
    if (r && r.status === 0) return { ok: true };

    return { ok: false, error: 'unicloud CLI 上传失败，返回码: ' + (r && r.status) };
  } catch (e) {
    return { ok: false, error: String(e) };
  }
}

async function callDeployApi(deployApi, deployToken, uploadResult) {
  try {
    const headers = { 'Content-Type': 'application/json' };
    if (deployToken) headers.Authorization = `Bearer ${deployToken}`;
    const body = Object.assign({ uploadResult }, {});
    const resp = await fetch(deployApi, { method: 'POST', headers, body: JSON.stringify(body) });
    const txt = await resp.text();
    let json;
    try { json = JSON.parse(txt); } catch (e) { json = { statusText: txt }; }
    return { ok: resp.ok, status: resp.status, body: json };
  } catch (e) {
    return { ok: false, error: String(e) };
  }
}

async function invokeCloudFunction(funcUrl, pushToken, testCid) {
  try {
    const body = { token: pushToken || process.env.PUSH_TOKEN || null };
    if (testCid) body.push_clientid = testCid;
    body.title = 'smoke-test';
    body.content = 'smoke-test';
    const resp = await fetch(funcUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
    const txt = await resp.text();
    let json;
    try { json = JSON.parse(txt); } catch (e) { json = { statusText: txt }; }
    return { ok: resp.ok, status: resp.status, body: json };
  } catch (e) {
    return { ok: false, error: String(e) };
  }
}

async function main() {
  await ensureDeps();

  const argv = process.argv.slice(2);
  const args = {};
  // 明确哪些参数需要值；若缺失则报错并退出，避免像 --upload 没给 URL 的情况
  const expectsValue = new Set(['upload','deployApi','deployToken','invokeUrl','funcInvokeUrl','pushToken','testCid','dir','spaceId','name']);
  for (let i = 0; i < argv.length; i++) {
    const a = argv[i];
    if (!a.startsWith('--')) continue;
    const key = a.replace('--','');
    const next = argv[i+1] && !argv[i+1].startsWith('--') ? argv[i+1] : undefined;
    if (expectsValue.has(key)) {
      if (!next) {
        console.error(`参数错误：--${key} 需要一个值（例如 --${key} <value>）。`);
        process.exit(1);
      }
      args[key] = next;
      i++; // skip value
    } else {
      args[key] = true;
    }
  }

  const funcDir = args.dir || process.env.CLOUD_FUNCTION_DIR || path.join(__dirname, '..', '..', 'uniCloud-alipay', 'cloudfunctions', 'testUnipush2');
  const outDir = path.join(__dirname, '..', 'dist');
  if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
  const zipPath = path.join(outDir, path.basename(funcDir) + '.zip');

  console.log('打包目录:', funcDir);
  try {
    const r = await packDirToZip(funcDir, zipPath);
    console.log('打包完成 ->', zipPath, `(${r.bytes} bytes)`);
  } catch (e) {
    console.error('打包失败', e);
    process.exit(2);
  }

  let uploadUrl = args.upload || process.env.CLOUD_UPLOAD_URL;
  const uploadToken = process.env.CLOUD_UPLOAD_TOKEN || process.env.CLOUD_TOKEN;
  // 如果没有提供 upload URL，但提供了 UniCloud 的 spaceId，则默认使用官方上传 API
  const spaceId = args.spaceId || process.env.CLOUD_UNICLOUD_SPACEID || process.env.SPACE_ID || null;
  if (!uploadUrl && spaceId) {
    uploadUrl = process.env.CLOUD_UPLOAD_URL || 'https://unicloud.dcloud.net.cn/api/uni-cloud/function/upload';
    console.log('未指定 --upload，检测到 spaceId，使用 UniCloud 上传 API：', uploadUrl);
  }
  if (!uploadUrl) {
    console.log('\n未配置上传地址 (CLOUD_UPLOAD_URL)，只生成 zip 文件。');
    console.log('若要上传，请设置环境变量 CLOUD_UPLOAD_URL 与 CLOUD_UPLOAD_TOKEN，或使用 --upload <URL> 参数，或提供 --spaceId 使用 unicloud 上传 API。');
    process.exit(0);
  }

  console.log('上传到:', uploadUrl);
  try {
    const extra = {};
    if (process.env.UNI_PUSH_APPID) extra.appId = process.env.UNI_PUSH_APPID;
    // 若使用 UniCloud 官方上传接口，需要传入 spaceId 与 name
    if (spaceId) extra.spaceId = spaceId;
    const funcName = args.name || path.basename(funcDir);
    if (funcName) extra.name = funcName;
    // 本地文件校验
    if (!fs.existsSync(zipPath)) {
      console.error('打包文件不存在:', zipPath);
      process.exit(7);
    }

    let res = await uploadZip(uploadUrl, uploadToken, zipPath, extra);
    console.log('上传响应:', res.status, JSON.stringify(res.body));

    // 若 HTTP 上传被拒绝（例如 405）或不被接受，且提供了 spaceId，则尝试使用 unicloud CLI 上传回退
    if ((!res.ok || res.status === 405) && spaceId) {
      console.log('HTTP 上传失败或被拒绝，尝试使用 unicloud CLI 上传回退...');
      const funcName = args.name || path.basename(funcDir);
      const cli = runUnicloudUpload(spaceId, funcDir, funcName);
      if (cli.ok) {
        console.log('unicloud CLI 上传成功（回退）。');
        // 将 published 设为 true，后续进行 smoke-test
        res = { ok: true, status: 0, body: { source: 'unicloud-cli' } };
      } else {
        console.error('unicloud CLI 上传也失败：', cli.error);
        process.exit(8);
      }
    }
    if (!res.ok) process.exit(3);

    // 常见错误检测：如果把包 POST 到了函数的 invoke URL，云函数通常直接返回业务层错误
    // e.g. {"errCode":400,"errMsg":"push_clientid required"}
    try {
      const probe = JSON.stringify(res.body || {});
      if (/push_clientid|required/i.test(probe) && /errCode|errMsg|error/i.test(probe)) {
        console.error('\n检测到上传目标看起来像云函数的调用（invoke）URL，而不是上传/发布 API。');
        console.error('请使用提供商的上传/发布接口（不是函数的 /test 或类似 invoke 路径）。');
        console.error('建议：在 HBuilderX 或 控制台 的 Network 面板中捕获 “上传/发布” 请求，或联系平台文档获取上传 API。');
        console.error('当前响应（供参考）：', JSON.stringify(res.body));
        process.exit(6);
      }
    } catch (e) { /* ignore probe errors */ }

    // 简单判断上传响应是否已经标记为已发布/上线（根据不同厂商响应结构调整）
    let published = false;
    try {
      const b = res.body || {};
      if (b.errCode === 0 || b.errMsg === 'success' || /success|online|published/i.test(JSON.stringify(b))) published = true;
    } catch (e) { /* ignore */ }

    // 如果未明确发布且提供了 deploy API，则调用之以触发发布
    const deployApi = args.deployApi || process.env.CLOUD_DEPLOY_API || null;
    const deployToken = args.deployToken || process.env.CLOUD_DEPLOY_TOKEN || null;
    let deployResult = null;
    if (!published && deployApi) {
      console.log('调用部署 API:', deployApi);
      deployResult = await callDeployApi(deployApi, deployToken, res.body);
      console.log('部署 API 响应:', deployResult && (deployResult.body || deployResult.error));
      if (deployResult && deployResult.ok) published = true;
    }

    // 可选：若指定了 funcInvokeUrl 或 CLOUD_FUNC_URL，则在上传/发布后做一次 smoke-test 调用
    const funcInvokeUrl = args.invokeUrl || args.funcInvokeUrl || process.env.CLOUD_FUNC_URL || null;
    const pushToken = args.pushToken || process.env.PUSH_TOKEN || null;
    const testCid = args.testCid || process.env.TEST_DEVICE_CID || null;
    if (funcInvokeUrl) {
      console.log('调用云函数做 smoke-test:', funcInvokeUrl);
      const inv = await invokeCloudFunction(funcInvokeUrl, pushToken, testCid);
      console.log('smoke-test 响应:', inv && (inv.body || inv.error));
      if (!inv.ok) process.exit(5);
    }
  } catch (e) {
    console.error('上传失败', e);
    process.exit(4);
  }
}

if (require.main === module) main();
