import express from "express";
import fetch, { Headers } from "node-fetch";
import cors from "cors";
import dotenv from "dotenv";
import crypto from "node:crypto";
import path from "node:path";
import { fileURLToPath } from "node:url";

dotenv.config();

const apiApp = express();
const port = Number.parseInt(process.env.PORT ?? "5174", 10);
const submitUrl = process.env.DOU_BAO_SUBMIT_URL ?? "https://openspeech.bytedance.com/api/v3/auc/bigmodel/submit";
const resultUrl = process.env.DOU_BAO_RESULT_URL ?? "https://openspeech.bytedance.com/api/v3/auc/bigmodel/query";
const shouldServeStatic = true;

apiApp.use(cors());
apiApp.use(express.json({ limit: "100mb" }));
// 基础访问日志，便于定位请求是否到达后端
apiApp.use((req, _res, next) => { console.log(`[incoming] ${req.method} ${req.path}`); next(); });

// 同端口托管静态页面
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const staticRoot = path.resolve(__dirname, "..");
apiApp.use(express.static(staticRoot));
apiApp.get(["/", "/index.html"], (req, res) => {
  res.sendFile(path.resolve(staticRoot, "index.html"));
});

function assertApiKeyConfigured() {
  if (!process.env.DOU_BAO_API_KEY) {
    throw new Error("服务器未配置 DOU_BAO_API_KEY，无法向豆包接口鉴权");
  }
}

function createUpstreamHeaders(overrideRequestId) {
  assertApiKeyConfigured();
  const headers = new Headers();
  const requestId = overrideRequestId || crypto.randomUUID();
  headers.set("Content-Type", "application/json");
  headers.set("x-api-key", process.env.DOU_BAO_API_KEY);
  headers.set("X-Api-Resource-Id", process.env.DOU_BAO_RESOURCE_ID ?? "volc.bigasr.auc");
  headers.set("X-Api-Request-Id", requestId);
  headers.set("X-Api-Sequence", "-1");
  return { headers, requestId };
}

function summarizeSubmitPayload(payload) {
  if (!payload) {
    return {};
  }
  const audioDataLength = typeof payload?.audio?.data === "string" ? payload.audio.data.length : 0;
  return {
    hasAudioData: audioDataLength > 0,
    audioDataLength,
    audioFormat: payload?.audio?.format,
    audioCodec: payload?.audio?.codec,
    sampleRate: payload?.audio?.rate,
    bits: payload?.audio?.bits,
    channel: payload?.audio?.channel,
    hasAudioUrl: typeof payload?.audio?.url === "string",
    modelName: payload?.request?.model_name,
    uid: payload?.user?.uid
  };
}

function pickTranscript(obj) {
  const segments = obj?.data?.result?.segments || obj?.result?.segments;
  if (Array.isArray(segments) && segments.length > 0) {
    const texts = segments
      .map(s => (typeof s?.text === 'string' && s.text.trim()) || (typeof s?.transcript === 'string' && s.transcript.trim()) || '')
      .filter(Boolean);
    if (texts.length) return texts.join('\n');
  }
  const candidates = [
    obj?.data?.result?.text,
    obj?.result?.text,
    obj?.result?.output?.choices?.[0]?.text,
    obj?.result?.output?.transcript,
    obj?.result?.transcript,
    obj?.data?.output?.choices?.[0]?.text,
    obj?.data?.transcript,
    obj?.text,
    obj?.transcript
  ];
  return candidates.find(v => typeof v === 'string' && v.trim().length > 0) || null;
}

async function forwardJson({ url, payload, logLabel, overrideRequestId }) {
  const { headers, requestId } = createUpstreamHeaders(overrideRequestId);
  const payloadSummary = summarizeSubmitPayload(payload);
  console.log(`[${logLabel}] 转发至 ${url}`, payloadSummary, { request: payload?.request });

  const response = await fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(payload)
  });

  const rawText = await response.text();
  let parsed;
  try {
    parsed = JSON.parse(rawText);
  } catch (error) {
    parsed = { message: "解析豆包响应失败", raw: rawText };
  }

  // 尝试兼容更多返回字段，并附带原始响应便于前端调试
  const normalized = {
    ...parsed,
    __upstream: {
      status: response.status,
      ok: response.ok,
      headers: Object.fromEntries(response.headers.entries()),
      rawText,
      requestId
    },
    result: parsed.result ?? parsed.data ?? parsed ?? {},
    data: parsed.data ?? parsed.result ?? parsed ?? {}
  };

  // 若上游未返回任务ID，使用本次 requestId 作为回退，确保前端能继续轮询
  if (!normalized.result.job_key && !normalized.data.job_key) {
    normalized.result.job_key = requestId;
    normalized.data.job_key = requestId;
  }

  // 不在服务端强制注入 transcript，避免前端“抢先以纯文本结束轮询”
  // const transcript = pickTranscript(normalized) || null;
  // if (transcript) normalized.transcript = transcript;

  console.log(`[${logLabel}] 响应 ${response.status}`, normalized);
  return { status: response.status, body: normalized };
}

apiApp.post("/api/asr/submit", async (req, res) => {
  try {
    if (!req.body || Object.keys(req.body).length === 0) {
      return res.status(400).json({ message: "请求体为空，无法提交任务" });
    }
    const result = await forwardJson({ url: submitUrl, payload: req.body, logLabel: "submit" });
    return res.status(result.status).json(result.body);
  } catch (error) {
    console.error("[submit]", error);
    const message = error instanceof Error ? error.message : String(error);
    return res.status(500).json({ message: "提交任务失败", error: message });
  }
});

apiApp.post("/api/asr/result", async (req, res) => {
  try {
    if (!req.body || !req.body.job_key) {
      return res.status(400).json({ message: "缺少 job_key，无法查询任务" });
    }
    const jobKey = String(req.body.job_key || req.body.taskId || req.body.task_id || "");
    if (!jobKey) {
      return res.status(400).json({ message: "缺少 job_key，无法查询任务" });
    }
    // 按官方示例，query 使用 X-Api-Request-Id 作为查询键，body 可为空
    const result = await forwardJson({ url: resultUrl, payload: {}, logLabel: "result", overrideRequestId: jobKey });
    return res.status(result.status).json(result.body);
  } catch (error) {
    console.error("[result]", error);
    const message = error instanceof Error ? error.message : String(error);
    return res.status(500).json({ message: "查询任务失败", error: message });
  }
});

apiApp.get("/health", (req, res) => {
  res.json({
    status: "ok",
    submitUrl,
    resultUrl,
    hasApiKey: Boolean(process.env.DOU_BAO_API_KEY)
  });
});

const apiServer = apiApp.listen(port, "0.0.0.0", () => {
  console.log(`豆包 ASR 代理服务已启动，端口 ${port}`);
});

// Keep-Alive：定期自我心跳，支持工作时段窗口控制
const KEEP_ALIVE_MS = Number.parseInt(process.env.KEEP_ALIVE_MS ?? "300000", 10); // 默认5分钟
const KEEP_ALIVE_WINDOW = process.env.KEEP_ALIVE_WINDOW || "08:00-22:00"; // 形如 "08:00-22:00"
const KEEP_ALIVE_TZ = process.env.KEEP_ALIVE_TZ || process.env.TZ || "Asia/Shanghai";

function withinKeepAliveWindow(now = new Date()){
  try{
    // 获取目标时区的小时与分钟
    const parts = new Intl.DateTimeFormat('en-GB', { timeZone: KEEP_ALIVE_TZ, hour12: false, hour: '2-digit', minute: '2-digit' })
      .formatToParts(now);
    const hh = Number(parts.find(p=>p.type==='hour')?.value || '0');
    const mm = Number(parts.find(p=>p.type==='minute')?.value || '0');
    const cur = hh * 60 + mm;
    const [startStr, endStr] = KEEP_ALIVE_WINDOW.split('-');
    const [sh, sm] = (startStr||'08:00').split(':').map(n=>Number(n)||0);
    const [eh, em] = (endStr||'22:00').split(':').map(n=>Number(n)||0);
    const start = sh * 60 + sm;
    const end = eh * 60 + em;
    if (start === end) return true; // 全时段
    if (start < end) return cur >= start && cur < end; // 正常区间
    // 跨午夜区间
    return cur >= start || cur < end;
  }catch(_){ return true; }
}

if (KEEP_ALIVE_MS > 0) {
  setInterval(async () => {
    if (!withinKeepAliveWindow()) return;
    try {
      const resp = await fetch(`http://127.0.0.1:${port}/health`);
      process.stdout.write(".");
      await resp.text().catch(()=>{});
    } catch (e) {
      console.log("[keep-alive] ping 失败", e?.message || e);
    }
  }, KEEP_ALIVE_MS);
}

// 已在同端口托管静态页面，无需单独 staticApp

process.on("SIGINT", () => {
  apiServer.close(() => {
    console.log("代理服务已关闭");
    process.exit(0);
  });
});
