"use strict";

/**
 * 调用本地 InfiniteTalk FastAPI（python-runtime/main.py）
 * 配置：INFINITETALK_API_URL，默认 http://127.0.0.1:8765
 */

const fs = require("fs");
const path = require("path");
const os = require("os");
const http = require("http");
const https = require("https");
const desktopConfig = require("./desktop_config.js");

const POLL_MS = 3000;
const MAX_WAIT_MS = 60 * 60 * 1000;

function apiBase() {
  const raw =
    desktopConfig.get("INFINITETALK_API_URL") ||
    desktopConfig.get("DIGITAL_HUMAN_API_URL") ||
    "http://127.0.0.1:8765";
  return String(raw).trim().replace(/\/+$/, "");
}

function requestJson(method, urlPath, body, headers = {}) {
  return new Promise((resolve, reject) => {
    const base = apiBase();
    const url = new URL(urlPath.startsWith("http") ? urlPath : `${base}${urlPath}`);
    const lib = url.protocol === "https:" ? https : http;
    const opts = {
      method,
      hostname: url.hostname,
      port: url.port || (url.protocol === "https:" ? 443 : 80),
      path: url.pathname + url.search,
      headers,
    };
    const req = lib.request(opts, (res) => {
      const chunks = [];
      res.on("data", (c) => chunks.push(c));
      res.on("end", () => {
        const text = Buffer.concat(chunks).toString("utf8");
        let data;
        try {
          data = text ? JSON.parse(text) : {};
        } catch {
          data = { raw: text };
        }
        if (res.statusCode >= 400) {
          reject(
            new Error(
              data.detail?.message ||
                data.message ||
                data.detail ||
                `HTTP ${res.statusCode}: ${text.slice(0, 300)}`,
            ),
          );
          return;
        }
        resolve(data);
      });
    });
    req.on("error", reject);
    if (body) req.write(body);
    req.end();
  });
}

function postMultipart(urlPath, fields, files) {
  return new Promise((resolve, reject) => {
    const boundary = `----Aiclient${Date.now()}`;
    const parts = [];

    for (const [name, value] of Object.entries(fields)) {
      parts.push(
        Buffer.from(
          `--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`,
        ),
      );
    }
    for (const { name, filePath, filename, contentType } of files) {
      const data = fs.readFileSync(filePath);
      parts.push(
        Buffer.from(
          `--${boundary}\r\nContent-Disposition: form-data; name="${name}"; filename="${filename}"\r\nContent-Type: ${contentType}\r\n\r\n`,
        ),
      );
      parts.push(data);
      parts.push(Buffer.from("\r\n"));
    }
    parts.push(Buffer.from(`--${boundary}--\r\n`));
    const body = Buffer.concat(parts);

    const base = apiBase();
    const url = new URL(urlPath.startsWith("http") ? urlPath : `${base}${urlPath}`);
    const lib = url.protocol === "https:" ? https : http;
    const opts = {
      method: "POST",
      hostname: url.hostname,
      port: url.port || (url.protocol === "https:" ? 443 : 80),
      path: url.pathname + url.search,
      headers: {
        "Content-Type": `multipart/form-data; boundary=${boundary}`,
        "Content-Length": body.length,
      },
    };
    const req = lib.request(opts, (res) => {
      const chunks = [];
      res.on("data", (c) => chunks.push(c));
      res.on("end", () => {
        const text = Buffer.concat(chunks).toString("utf8");
        let data;
        try {
          data = text ? JSON.parse(text) : {};
        } catch {
          data = { raw: text };
        }
        if (res.statusCode >= 400) {
          reject(
            new Error(
              data.detail?.message ||
                data.message ||
                `HTTP ${res.statusCode}: ${text.slice(0, 300)}`,
            ),
          );
          return;
        }
        resolve(data);
      });
    });
    req.on("error", reject);
    req.write(body);
    req.end();
  });
}

function downloadFile(urlPath, destPath) {
  return new Promise((resolve, reject) => {
    const base = apiBase();
    const url = new URL(urlPath.startsWith("http") ? urlPath : `${base}${urlPath}`);
    const lib = url.protocol === "https:" ? https : http;
    const file = fs.createWriteStream(destPath);
    lib
      .get(url.href, (res) => {
        if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
          file.close();
          return downloadFile(res.headers.location, destPath).then(resolve).catch(reject);
        }
        if (res.statusCode !== 200) {
          reject(new Error(`下载失败 HTTP ${res.statusCode}`));
          return;
        }
        res.pipe(file);
        file.on("finish", () => file.close(() => resolve(destPath)));
      })
      .on("error", reject);
  });
}

function sleep(ms) {
  return new Promise((r) => setTimeout(r, ms));
}

function guessMime(filePath) {
  const ext = path.extname(filePath).toLowerCase();
  if (ext === ".mp3") return "audio/mpeg";
  if (ext === ".wav") return "audio/wav";
  if (ext === ".mov") return "video/quicktime";
  return "video/mp4";
}

globalThis.__nodejsMain = async function (params) {
  const audioPath = String(params?.audioPath || "").trim();
  const avatarVideoPath = String(params?.avatarVideoPath || "").trim();
  if (!audioPath || !fs.existsSync(audioPath)) {
    return { success: false, error: "缺少或找不到音频文件" };
  }
  if (!avatarVideoPath || !fs.existsSync(avatarVideoPath)) {
    return { success: false, error: "缺少或找不到形象视频" };
  }

  try {
    const health = await requestJson("GET", "/health");
    if (!health.ok) {
      return {
        success: false,
        error: `InfiniteTalk 服务未就绪: ${(health.issues || []).join("; ")}`,
      };
    }
  } catch (e) {
    return {
      success: false,
      error: `无法连接 InfiniteTalk API (${apiBase()}): ${e.message}`,
    };
  }

  const create = await postMultipart(
    "/api/v1/talking-video",
    { prompt: String(params?.prompt || "") },
    [
      {
        name: "video",
        filePath: avatarVideoPath,
        filename: path.basename(avatarVideoPath),
        contentType: guessMime(avatarVideoPath),
      },
      {
        name: "audio",
        filePath: audioPath,
        filename: path.basename(audioPath),
        contentType: guessMime(audioPath),
      },
    ],
  );

  const jobId = create.job_id;
  if (!jobId) {
    return { success: false, error: "未返回 job_id" };
  }

  const started = Date.now();
  while (Date.now() - started < MAX_WAIT_MS) {
    const st = await requestJson("GET", `/api/v1/talking-video/${jobId}`);
    if (st.status === "succeeded") {
      const outDir = path.join(os.tmpdir(), "aiclient-infinitetalk");
      fs.mkdirSync(outDir, { recursive: true });
      const dest = path.join(outDir, `talking_${jobId}.mp4`);
      await downloadFile(`/api/v1/talking-video/${jobId}/download`, dest);
      return { success: true, videoPath: dest };
    }
    if (st.status === "failed") {
      return {
        success: false,
        error: String(st.message || "InfiniteTalk 任务失败"),
      };
    }
    await sleep(POLL_MS);
  }

  return { success: false, error: "InfiniteTalk 生成超时" };
};
