const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
const fetch = require('node-fetch');
const FormData = require('form-data');

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 };
}

async function triggerDeployApi(deployApi, deployToken, payload = {}) {
  const headers = { 'Content-Type': 'application/json' };
  if (deployToken) headers.Authorization = `Bearer ${deployToken}`;
  const resp = await fetch(deployApi, { method: 'POST', headers, body: JSON.stringify(payload) });
  const json = await resp.json().catch(() => null);
  return { ok: resp.ok, status: resp.status, body: json };
}

async function invokeFunction(funcUrl, pushToken, testCid, title = 'CI Test', content = 'hello from backend') {
  const body = { token: pushToken, push_clientid: testCid, title, content, payload: {} };
  const resp = await fetch(funcUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
  const json = await resp.json().catch(() => null);
  return { ok: resp.ok, status: resp.status, body: json };
}

async function deployCloudFunction(options = {}) {
  const funcDir = options.funcDir || 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');

  const result = { packed: null, uploaded: null, deployed: null, invoked: null };

  // pack
  const packRes = await packDirToZip(funcDir, zipPath);
  result.packed = packRes;

  // upload
  if (!options.uploadUrl) return result;
  const extra = {};
  if (options.uniAppId) extra.appId = options.uniAppId;
  const up = await uploadZip(options.uploadUrl, options.uploadToken, zipPath, extra);
  result.uploaded = up;

  // trigger deploy API (optional)
  if (options.deployApi) {
    const payload = Object.assign({}, options.deployPayload || {}, { uploadUrl: options.uploadUrl, uploadResponse: up.body });
    const dp = await triggerDeployApi(options.deployApi, options.deployToken, payload);
    result.deployed = dp;
  }

  // invoke function for smoke test (optional)
  if (options.funcInvokeUrl && options.pushToken && options.testCid) {
    const iv = await invokeFunction(options.funcInvokeUrl, options.pushToken, options.testCid, options.testTitle, options.testContent);
    result.invoked = iv;
  }

  return result;
}

module.exports = { deployCloudFunction, packDirToZip, uploadZip, triggerDeployApi, invokeFunction };
