import fs from "node:fs/promises";
import http from "node:http";
import { randomUUID } from "node:crypto";
import os from "node:os";
import path from "node:path";
import {
  LOCAL_DATA_DIRECTORY,
  RUNNER_HOST,
  RUNNER_PORT,
} from "./config.mjs";
import { calculateAllocations } from "./calculate.mjs";
import { searchStreamingEvents } from "./streaming-events.mjs";

const tasks = new Map();
let activeTaskId = null;
let latestTaskId = null;
const allowedOrigins = new Set([
  "http://124.156.160.173",
  "http://zhuanzhuan-nt.cn",
  "http://www.zhuanzhuan-nt.cn",
  "https://zhuanzhuan-nt.cn",
  "https://www.zhuanzhuan-nt.cn",
]);
const latestDataFile = path.join(
  LOCAL_DATA_DIRECTORY,
  "latest-data.json",
);

await fs.mkdir(LOCAL_DATA_DIRECTORY, { recursive: true });

function setCorsHeaders(request, response) {
  const origin = request.headers.origin ?? "";
  const isAllowed =
    /^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(origin) ||
    allowedOrigins.has(origin) ||
    !origin;
  if (isAllowed && origin) {
    response.setHeader("Access-Control-Allow-Origin", origin);
    response.setHeader("Vary", "Origin");
  }
  response.setHeader("Access-Control-Allow-Headers", "Content-Type");
  response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
  response.setHeader("Access-Control-Allow-Private-Network", "true");
}

function sendJson(request, response, status, payload) {
  setCorsHeaders(request, response);
  response.writeHead(status, {
    "Content-Type": "application/json; charset=utf-8",
    "Cache-Control": "no-store",
  });
  response.end(JSON.stringify(payload));
}

async function readJsonBody(request) {
  const chunks = [];
  let totalBytes = 0;
  for await (const chunk of request) {
    totalBytes += chunk.length;
    if (totalBytes > 64 * 1024) {
      throw new Error("请求内容过大。");
    }
    chunks.push(chunk);
  }
  return JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
}

function publicTask(task) {
  return {
    id: task.id,
    status: task.status,
    stage: task.stage,
    step: task.step,
    percent: task.percent,
    message: task.message,
    error: task.error,
    createdAt: task.createdAt,
    updatedAt: task.updatedAt,
    result: task.result,
  };
}

function updateTask(task, patch) {
  Object.assign(task, patch, { updatedAt: new Date().toISOString() });
}

function createTask(input, message = "任务已创建。") {
  const now = new Date().toISOString();
  const task = {
    id: randomUUID(),
    input,
    status: "queued",
    stage: "queued",
    step: 1,
    percent: 0,
    message,
    error: null,
    result: null,
    createdAt: now,
    updatedAt: now,
  };
  tasks.set(task.id, task);
  latestTaskId = task.id;
  return task;
}

function validateInputs(body) {
  const budgets = {
    billiards: Number(body?.budgets?.billiards),
    cycling: Number(body?.budgets?.cycling),
  };
  const unitPrices = {
    billiards: Number(body?.unitPrices?.billiards),
    cycling: Number(body?.unitPrices?.cycling),
  };

  for (const [key, value] of Object.entries(budgets)) {
    if (!Number.isFinite(value) || value < 0) {
      throw new Error(`${key} 预算必须是大于或等于 0 的数字。`);
    }
  }
  for (const [key, value] of Object.entries(unitPrices)) {
    if (!Number.isFinite(value) || value <= 0) {
      throw new Error(`${key} 单条视频成本必须大于 0。`);
    }
  }
  return { budgets, unitPrices };
}

async function readLatestDataSnapshot() {
  try {
    const stored = JSON.parse(
      await fs.readFile(latestDataFile, "utf8"),
    );
    const imported = await validateImportedFiles(stored);
    const fetchedAt = String(stored.fetchedAt ?? "");
    if (!Number.isFinite(Date.parse(fetchedAt))) {
      throw new Error("上次抓取时间不正确。");
    }
    return {
      fetchedAt,
      files: imported.files,
      period: imported.period,
    };
  } catch {
    return null;
  }
}

async function saveLatestDataSnapshot(snapshot) {
  const temporaryFile = `${latestDataFile}.tmp`;
  await fs.writeFile(
    temporaryFile,
    `${JSON.stringify(snapshot, null, 2)}\n`,
    "utf8",
  );
  await fs.rename(temporaryFile, latestDataFile);
}

function publicLatestData(snapshot) {
  if (!snapshot) {
    return { available: false };
  }
  return {
    available: true,
    fetchedAt: snapshot.fetchedAt,
    period: snapshot.period,
  };
}

async function validateImportedFiles(body) {
  const requiredKeys = [
    "billiards_sop",
    "billiards_sku",
    "cycling_sop",
    "cycling_sku",
  ];
  const downloadsDirectory = path.join(os.homedir(), "Downloads");
  const allowedDirectories = [LOCAL_DATA_DIRECTORY, downloadsDirectory];
  const files = {};

  for (const key of requiredKeys) {
    const filePath = path.resolve(String(body?.files?.[key] ?? ""));
    const isAllowed = allowedDirectories.some(
      (directory) =>
        filePath === directory || filePath.startsWith(`${directory}${path.sep}`),
    );
    if (!isAllowed || path.extname(filePath).toLowerCase() !== ".xlsx") {
      throw new Error(`${key} 文件位置或格式不正确。`);
    }
    const stat = await fs.stat(filePath);
    if (!stat.isFile()) {
      throw new Error(`${key} 不是可读取的文件。`);
    }
    files[key] = filePath;
  }

  const start = String(body?.period?.start ?? "");
  const end = String(body?.period?.end ?? "");
  if (!/^\d{4}-\d{2}-\d{2}$/.test(start) || !/^\d{4}-\d{2}-\d{2}$/.test(end)) {
    throw new Error("统计日期格式不正确。");
  }
  return { files, period: { start, end } };
}

async function runTask(task) {
  activeTaskId = task.id;
  updateTask(task, {
    status: "running",
    stage: "opening_browser",
    step: 2,
    percent: 5,
    message: "任务已开始，正在准备本机浏览器…",
  });

  try {
    const { exportDashboardFiles } = await import("./browser-export.mjs");
    const exported = await exportDashboardFiles({
      taskId: task.id,
      onProgress: (progress) => updateTask(task, progress),
    });
    updateTask(task, {
      stage: "calculating",
      step: 3,
      percent: 88,
      message: "四份数据已下载，正在清洗并计算 SOP、SKU 分配…",
    });
    const result = await calculateAllocations({
      files: exported.files,
      budgets: task.input.budgets,
      unitPrices: task.input.unitPrices,
      period: exported.period,
    });
    updateTask(task, {
      status: "completed",
      stage: "completed",
      step: 4,
      percent: 100,
      message: "计算完成，分配总条数已严格对齐预算条数。",
      result,
    });
  } catch (error) {
    updateTask(task, {
      status: "failed",
      stage: "failed",
      percent: task.percent,
      message: "任务未完成。",
      error: error instanceof Error ? error.message : String(error),
    });
  } finally {
    activeTaskId = null;
  }
}

async function runImportedTask(
  task,
  files,
  period,
  { saveAsLatest = false, fetchedAt = null } = {},
) {
  activeTaskId = task.id;
  updateTask(task, {
    status: "running",
    stage: "calculating",
    step: 3,
    percent: 88,
    message: "四份真实数据已导入，正在清洗并计算 SOP、SKU 分配…",
  });

  try {
    const result = await calculateAllocations({
      files,
      budgets: task.input.budgets,
      unitPrices: task.input.unitPrices,
      period,
    });
    if (saveAsLatest) {
      await saveLatestDataSnapshot({
        fetchedAt: fetchedAt ?? new Date().toISOString(),
        period,
        files,
      });
    }
    updateTask(task, {
      status: "completed",
      stage: "completed",
      step: 4,
      percent: 100,
      message: "计算完成，分配总条数已严格对齐预算条数。",
      result,
    });
  } catch (error) {
    updateTask(task, {
      status: "failed",
      stage: "failed",
      percent: task.percent,
      message: "任务未完成。",
      error: error instanceof Error ? error.message : String(error),
    });
  } finally {
    activeTaskId = null;
  }
}

const server = http.createServer(async (request, response) => {
  if (request.method === "OPTIONS") {
    setCorsHeaders(request, response);
    response.writeHead(204);
    response.end();
    return;
  }

  const url = new URL(
    request.url ?? "/",
    `http://${RUNNER_HOST}:${RUNNER_PORT}`,
  );

  if (request.method === "GET" && url.pathname === "/health") {
    sendJson(request, response, 200, {
      ok: true,
      activeTaskId,
      service: "NT 本机执行器",
    });
    return;
  }

  if (request.method === "GET" && url.pathname === "/api/tasks/latest") {
    const task = latestTaskId ? tasks.get(latestTaskId) : null;
    if (!task) {
      sendJson(request, response, 404, { error: "还没有计算记录。" });
      return;
    }
    sendJson(request, response, 200, publicTask(task));
    return;
  }

  if (request.method === "GET" && url.pathname === "/api/data/latest") {
    const snapshot = await readLatestDataSnapshot();
    sendJson(request, response, 200, publicLatestData(snapshot));
    return;
  }

  if (
    request.method === "GET" &&
    url.pathname === "/api/streaming/events"
  ) {
    try {
      const month = url.searchParams.get("month") ?? "";
      const result = await searchStreamingEvents(month);
      sendJson(request, response, 200, result);
    } catch (error) {
      sendJson(request, response, 400, {
        error: error instanceof Error ? error.message : String(error),
      });
    }
    return;
  }

  if (request.method === "POST" && url.pathname === "/api/tasks") {
    if (activeTaskId) {
      sendJson(request, response, 409, {
        error: "已有一个抓数任务正在运行，请等待它完成。",
        taskId: activeTaskId,
      });
      return;
    }

    try {
      const input = validateInputs(await readJsonBody(request));
      const task = createTask(input);
      sendJson(request, response, 202, publicTask(task));
      void runTask(task);
    } catch (error) {
      sendJson(request, response, 400, {
        error: error instanceof Error ? error.message : String(error),
      });
    }
    return;
  }

  if (request.method === "POST" && url.pathname === "/api/tasks/import") {
    if (activeTaskId) {
      sendJson(request, response, 409, {
        error: "已有一个抓数任务正在运行，请等待它完成。",
        taskId: activeTaskId,
      });
      return;
    }

    try {
      const body = await readJsonBody(request);
      const input = validateInputs(body);
      const imported = await validateImportedFiles(body);
      const task = createTask(input, "真实导出数据已接收。");
      sendJson(request, response, 202, publicTask(task));
      void runImportedTask(task, imported.files, imported.period, {
        saveAsLatest: true,
        fetchedAt: new Date().toISOString(),
      });
    } catch (error) {
      sendJson(request, response, 400, {
        error: error instanceof Error ? error.message : String(error),
      });
    }
    return;
  }

  if (
    request.method === "POST" &&
    url.pathname === "/api/tasks/recalculate"
  ) {
    if (activeTaskId) {
      sendJson(request, response, 409, {
        error: "已有一个计算任务正在运行，请等待它完成。",
        taskId: activeTaskId,
      });
      return;
    }

    try {
      const input = validateInputs(await readJsonBody(request));
      const snapshot = await readLatestDataSnapshot();
      if (!snapshot) {
        sendJson(request, response, 409, {
          error: "没有可复用的历史数据，请重新抓取一次。",
        });
        return;
      }
      const task = createTask(input, "正在复用上次抓取的数据。");
      sendJson(request, response, 202, publicTask(task));
      void runImportedTask(task, snapshot.files, snapshot.period);
    } catch (error) {
      sendJson(request, response, 400, {
        error: error instanceof Error ? error.message : String(error),
      });
    }
    return;
  }

  const match = url.pathname.match(/^\/api\/tasks\/([^/]+)$/);
  if (request.method === "GET" && match) {
    const task = tasks.get(match[1]);
    if (!task) {
      sendJson(request, response, 404, { error: "没有找到这个任务。" });
      return;
    }
    sendJson(request, response, 200, publicTask(task));
    return;
  }

  sendJson(request, response, 404, { error: "接口不存在。" });
});

server.listen(RUNNER_PORT, RUNNER_HOST, () => {
  console.log(`NT 本机执行器已启动：http://${RUNNER_HOST}:${RUNNER_PORT}`);
});

function shutdown() {
  server.close(() => process.exit(0));
}

process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
