const http = require("http");
const fs = require("fs");
const fsp = require("fs/promises");
const path = require("path");
const crypto = require("crypto");

const PORT = Number(process.env.PORT || 8787);
const HOST = process.env.HOST || "0.0.0.0";
const DATA_DIR = path.join(__dirname, "data");
const DB_PATH = path.join(DATA_DIR, "db.json");
const GUIDELINES_PATH = path.join(DATA_DIR, "guidelines.json");
const TOKEN_SECRET = process.env.TOKEN_SECRET || "change-this-secret-before-production";
const DEEPL_API_KEY = process.env.DEEPL_API_KEY || "";
const DEEPL_API_URL = process.env.DEEPL_API_URL || "https://api-free.deepl.com/v2/translate";
const OPENAI_API_KEY = process.env.OPENAI_API_KEY || "";
const OPENAI_BASE_URL = (process.env.OPENAI_BASE_URL || "https://api.openai.com/v1").replace(/\/$/, "");
const OPENAI_MODEL = process.env.OPENAI_MODEL || "gpt-4.1-mini";
const TRANSLATION_PROVIDER = (process.env.TRANSLATION_PROVIDER || "auto").toLowerCase();

const DEFAULT_DISEASE_OPTIONS = [
  "输尿管支架置入术后",
  "结石术后复查",
  "泌尿系肿瘤复查",
  "泌尿系感染复查",
  "导尿管/造瘘管随访",
  "其他"
];

let dbQueue = Promise.resolve();

function queueDb(task) {
  const run = dbQueue.then(task, task);
  dbQueue = run.catch(() => undefined);
  return run;
}

async function ensureDb() {
  await fsp.mkdir(DATA_DIR, { recursive: true });
  if (!fs.existsSync(DB_PATH)) {
    const initialDb = {
      hospitals: [],
      doctors: [],
      followUps: [],
      nextIds: {
        hospital: 1,
        doctor: 1,
        followUp: 1
      }
    };
    await fsp.writeFile(DB_PATH, JSON.stringify(initialDb, null, 2), "utf8");
  }
  if (!fs.existsSync(GUIDELINES_PATH)) {
    await fsp.writeFile(GUIDELINES_PATH, JSON.stringify(defaultGuidelines(), null, 2), "utf8");
  }
}

async function readDb() {
  await ensureDb();
  const raw = await fsp.readFile(DB_PATH, "utf8");
  return JSON.parse(raw);
}

async function writeDb(db) {
  await ensureDb();
  const tempPath = `${DB_PATH}.tmp`;
  await fsp.writeFile(tempPath, JSON.stringify(db, null, 2), "utf8");
  await fsp.rename(tempPath, DB_PATH);
}

function normalizeHospitalCode(value) {
  return String(value || "").trim().toLowerCase();
}

function defaultGuidelines() {
  return [
    {
      id: "eau-prostate-cancer",
      source: "EAU",
      topic: "前列腺癌",
      title: "EAU Guidelines on Prostate Cancer",
      year: 2026,
      url: "https://uroweb.org/guidelines/prostate-cancer",
      note: "EAU 前列腺癌指南主页，包含全文、随访章节与更新摘要。",
      summaryZh: "适合快速查看前列腺癌影像、分期、主动监测、生化复发评估和根治治疗后策略的中文要点。",
      keyPoints: [
        "2026 版更新重点包括 MRI 在活检指征与活检策略中的使用。",
        "分期评估和影像学推荐有更新，适合与当前本院检查流程对照。",
        "主动监测策略相关证据与推荐有修订。",
        "生化复发后的影像学评估与二线治疗建议有更新。"
      ],
      aiReadingAllowed: false
    },
    {
      id: "eau-nmibc",
      source: "EAU",
      topic: "非肌层浸润性膀胱癌",
      title: "EAU Guidelines on Non-muscle-invasive Bladder Cancer",
      year: 2026,
      url: "https://uroweb.org/guidelines/non-muscle-invasive-bladder-cancer",
      note: "EAU NMIBC 指南主页，含随访与风险分层相关章节。",
      summaryZh: "适合查阅膀胱镜分型、病理分级、风险分层以及随访策略的中文要点。",
      keyPoints: [
        "2026 版更新了 WHO 1973 与 WHO 2004/2022 分级相关图示与衔接。",
        "膀胱癌分类部分的证据等级与推荐有修订。",
        "风险分层与后续管理仍是核心，需要结合病理等级和复发风险判断。",
        "可重点对照随访章节，用于本院 NMIBC 术后复查安排。"
      ],
      aiReadingAllowed: false
    },
    {
      id: "eau-rcc",
      source: "EAU",
      topic: "肾癌",
      title: "EAU Guidelines on Renal Cell Carcinoma",
      year: 2026,
      url: "https://uroweb.org/guidelines/renal-cell-carcinoma",
      note: "EAU 肾细胞癌指南主页。",
      summaryZh: "适合查看 RCC 手术、非手术替代、寡转移处理和随访更新的中文要点。",
      keyPoints: [
        "2026 版新增了衰弱评估和老年综合评估相关内容。",
        "根治性肾切除和部分肾切除技术章节有更新。",
        "替代手术治疗的流程图和建议有新增内容。",
        "寡转移处理与 RCC 随访章节均有更新。"
      ],
      aiReadingAllowed: false
    },
    {
      id: "eau-urolithiasis",
      source: "EAU",
      topic: "尿路结石",
      title: "EAU Guidelines on Urolithiasis",
      year: 2026,
      url: "https://uroweb.org/guidelines/urolithiasis",
      note: "EAU 尿石症指南主页。",
      summaryZh: "适合查看肾绞痛、药物排石、ESWL 抗菌预防等结石管理要点。",
      keyPoints: [
        "2026 版修订了肾绞痛处理的证据总结与推荐。",
        "药物排石章节有更新，适合用于门急诊结石患者处理。",
        "ESWL 围术期抗菌预防相关信息有修订。",
        "适合作为结石术后和保守治疗随访的参考入口。"
      ],
      aiReadingAllowed: false
    },
    {
      id: "eau-male-luts",
      source: "EAU",
      topic: "男性LUTS/BPH",
      title: "EAU Guidelines on the Management of Non-neurogenic Male LUTS",
      year: 2026,
      url: "https://uroweb.org/guidelines/management-of-non-neurogenic-male-luts",
      note: "EAU 男性 LUTS 指南主页。",
      summaryZh: "适合查看男性 LUTS/BPH 的诊断、保守治疗、β3 激动剂与手术更新。",
      keyPoints: [
        "2026 版对诊断评估章节做了细节更新，并新增前列腺癌相关内容。",
        "保守治疗章节有更新，包含 β3 激动剂强度评级变化。",
        "手术治疗章节对不同剜除和消融技术进行了补充与调整。",
        "新增男性尿失禁管理、逼尿肌收缩乏力和青年男性排尿功能障碍相关内容。"
      ],
      aiReadingAllowed: false
    },
    {
      id: "eau-urological-infections",
      source: "EAU",
      topic: "泌尿系感染",
      title: "EAU Guidelines on Urological Infections",
      year: 2026,
      url: "https://uroweb.org/guidelines/urological-infections",
      note: "EAU 泌尿系感染指南主页。",
      summaryZh: "适合查看膀胱炎诊断、抗菌治疗、真菌性 UTI 与前列腺穿刺围手术期预防。",
      keyPoints: [
        "2026 版更新了膀胱炎诊断评估的推荐。",
        "膀胱炎抗菌治疗推荐有修订。",
        "新增真菌性尿路感染章节。",
        "前列腺穿刺围术期抗菌预防和部分药物剂量信息有更新。"
      ],
      aiReadingAllowed: false
    },
    {
      id: "aua-nmibc",
      source: "AUA",
      topic: "非肌层浸润性膀胱癌",
      title: "Diagnosis and Treatment of Non-Muscle Invasive Bladder Cancer: AUA/SUO Guideline",
      year: 2024,
      url: "https://www.auanet.org/guidelines-and-quality/guidelines/bladder-cancer-non-muscle-invasive-guideline",
      note: "AUA/SUO NMIBC 指南公开页面。",
      summaryZh: "适合快速查看 NMIBC 的初始切除、风险分层、BCG 相关再分层和随访要点。",
      keyPoints: [
        "建议在初始切除时完整记录肿瘤大小、部位、数量及黏膜异常，并尽可能完整切除。",
        "初始评估应包含上尿路影像学检查。",
        "每次新发或复发都应进行低危、中危、高危分层。",
        "BCG 后持续或复发病灶提示更高风险，需重新评估后续管理与随访。"
      ],
      aiReadingAllowed: false
    },
    {
      id: "aua-overactive-bladder",
      source: "AUA",
      topic: "膀胱过度活动症",
      title: "The AUA/SUFU Guideline on the Diagnosis and Treatment of Idiopathic Overactive Bladder",
      year: 2024,
      url: "https://www.auanet.org/guidelines-and-quality/guidelines/idiopathic-overactive-bladder",
      note: "AUA/SUFU OAB 指南公开页面。",
      summaryZh: "适合查看特发性 OAB 诊断、非泌尿因素评估、药物风险和非阶梯式治疗策略。",
      keyPoints: [
        "这是 2024 年的新系统评估版本，不再简单沿用旧版阶梯式治疗框架。",
        "强调需同时评估肥胖、便秘、盆底功能障碍等非泌尿因素。",
        "药物治疗部分更强调抗胆碱能药物潜在伤害。",
        "对有前列腺的患者和男性 OAB 的分析更加明确。"
      ],
      aiReadingAllowed: false
    },
    {
      id: "aua-urethral-stricture",
      source: "AUA",
      topic: "尿道狭窄",
      title: "Urethral Stricture Disease Guideline Amendment",
      year: 2023,
      url: "https://www.auanet.org/guidelines-and-quality/guidelines/urethral-stricture-guideline",
      note: "AUA 尿道狭窄指南公开页面。",
      summaryZh: "适合查看男性和女性尿道狭窄、重建方式及术后随访相关中文要点。",
      keyPoints: [
        "2023 修订版纳入了女性尿道狭窄研究。",
        "补充了不同吻合成形术对性功能结局影响的证据。",
        "新增机器人或开放重建相关处理内容。",
        "术后随访仍是重要组成部分，适合衔接本院复查计划。"
      ],
      aiReadingAllowed: false
    },
    {
      id: "aua-urotrauma",
      source: "AUA",
      topic: "泌尿创伤",
      title: "Urotrauma Guideline",
      year: 2020,
      url: "https://www.auanet.org/guidelines-and-quality/guidelines/urotrauma-guideline",
      note: "AUA 泌尿创伤指南公开页面。",
      summaryZh: "适合查看肾、输尿管、膀胱、尿道和生殖器创伤的急诊处理框架。",
      keyPoints: [
        "该指南覆盖肾、输尿管、膀胱、尿道和生殖器损伤。",
        "强调创伤团队协作、快速影像评估和尽早建立引流。",
        "重症场景下要结合损伤控制原则灵活处理。",
        "盆骨骨折相关尿道损伤应尽快建立尿路引流。"
      ],
      aiReadingAllowed: false
    }
  ];
}

async function readGuidelines() {
  await ensureDb();
  const raw = await fsp.readFile(GUIDELINES_PATH, "utf8");
  return JSON.parse(raw);
}

function normalizeUsername(value) {
  return String(value || "").trim().toLowerCase();
}

function nowIso() {
  return new Date().toISOString();
}

function hashPassword(password, salt = crypto.randomBytes(16).toString("hex")) {
  const hash = crypto.scryptSync(String(password), salt, 64).toString("hex");
  return `${salt}:${hash}`;
}

function verifyPassword(password, storedHash) {
  const [salt, hash] = String(storedHash || "").split(":");
  if (!salt || !hash) return false;
  const calculated = crypto.scryptSync(String(password), salt, 64).toString("hex");
  return crypto.timingSafeEqual(Buffer.from(hash, "hex"), Buffer.from(calculated, "hex"));
}

function base64UrlEncode(input) {
  return Buffer.from(input).toString("base64url");
}

function base64UrlDecode(input) {
  return Buffer.from(input, "base64url").toString("utf8");
}

function createToken(payload) {
  const encodedPayload = base64UrlEncode(JSON.stringify(payload));
  const signature = crypto
    .createHmac("sha256", TOKEN_SECRET)
    .update(encodedPayload)
    .digest("base64url");
  return `${encodedPayload}.${signature}`;
}

function verifyToken(token) {
  const [encodedPayload, signature] = String(token || "").split(".");
  if (!encodedPayload || !signature) {
    throw new Error("无效登录状态");
  }

  const expectedSignature = crypto
    .createHmac("sha256", TOKEN_SECRET)
    .update(encodedPayload)
    .digest("base64url");

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
    throw new Error("登录状态校验失败");
  }

  const payload = JSON.parse(base64UrlDecode(encodedPayload));
  if (!payload.exp || Date.now() > payload.exp) {
    throw new Error("登录已过期，请重新登录");
  }

  return payload;
}

function sendJson(res, statusCode, payload) {
  res.writeHead(statusCode, {
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Headers": "Content-Type, Authorization",
    "Access-Control-Allow-Methods": "GET, POST, PATCH, OPTIONS"
  });
  res.end(JSON.stringify(payload));
}

function sendError(res, statusCode, message) {
  sendJson(res, statusCode, { error: message });
}

function decodeXmlEntities(text) {
  return String(text || "")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/&amp;/g, "&")
    .replace(/&quot;/g, "\"")
    .replace(/&#39;/g, "'");
}

function stripTags(text) {
  return decodeXmlEntities(String(text || "").replace(/<[^>]+>/g, " "));
}

async function readJsonBody(req) {
  const chunks = [];
  for await (const chunk of req) {
    chunks.push(chunk);
  }

  const raw = Buffer.concat(chunks).toString("utf8").trim();
  if (!raw) return {};

  try {
    return JSON.parse(raw);
  } catch {
    throw new Error("请求体不是有效 JSON");
  }
}

function computeStatus(record, today = new Date()) {
  if (record.completedAt) return "completed";

  const reminderDate = new Date(record.reminderDate);
  const todayDate = new Date(today.toISOString().slice(0, 10));
  const overdueBoundary = new Date(reminderDate);
  overdueBoundary.setMonth(overdueBoundary.getMonth() + 1);

  if (reminderDate <= todayDate) {
    return overdueBoundary <= todayDate ? "overdue" : "due";
  }
  return "pending";
}

function toDoctorPayload(doctor, hospital) {
  return {
    id: doctor.id,
    username: doctor.username,
    display_name: doctor.displayName,
    hospital_id: hospital.id,
    hospital_name: hospital.name,
    hospital_code: hospital.code
  };
}

function toFollowUpPayload(record, db) {
  const createdDoctor = db.doctors.find((item) => item.id === record.createdByDoctorId);
  const completedDoctor = db.doctors.find((item) => item.id === record.completedByDoctorId);
  return {
    id: String(record.id),
    admission_number: record.admissionNumber,
    patient_name: record.patientName,
    contact_phone: record.contactPhone || "",
    disease_type: record.diseaseType,
    procedure_type: record.procedureType || "",
    reminder_date: record.reminderDate,
    notes: record.notes || "",
    status: computeStatus(record),
    created_by_doctor: createdDoctor?.displayName || "",
    created_at_millis: Date.parse(record.createdAt),
    completion_outcome: record.completionOutcome || null,
    completed_by_doctor: completedDoctor?.displayName || null,
    completed_at_millis: record.completedAt ? Date.parse(record.completedAt) : null
  };
}

function summarizeFollowUps(records) {
  const summary = {
    pending_count: 0,
    due_count: 0,
    overdue_count: 0,
    completed_count: 0
  };

  for (const record of records) {
    const status = computeStatus(record);
    if (status === "completed") {
      summary.completed_count += 1;
      continue;
    }
    summary.pending_count += 1;
    if (status === "due" || status === "overdue") summary.due_count += 1;
    if (status === "overdue") summary.overdue_count += 1;
  }

  return summary;
}

async function fetchJson(url, options = {}) {
  const response = await fetch(url, options);
  if (!response.ok) {
    throw new Error(`上游接口请求失败：${response.status}`);
  }
  return response.json();
}

async function fetchText(url, options = {}) {
  const response = await fetch(url, options);
  if (!response.ok) {
    throw new Error(`上游接口请求失败：${response.status}`);
  }
  return response.text();
}

function extractAbstractsFromPubMedXml(xmlText) {
  const sections = xmlText.split(/<PubmedArticle>/g).slice(1);
  const map = {};

  for (const section of sections) {
    const pmidMatch = section.match(/<PMID[^>]*>(.*?)<\/PMID>/);
    if (!pmidMatch) continue;
    const pmid = stripTags(pmidMatch[1]).trim();

    const abstractParts = [];
    const abstractRegex = /<AbstractText([^>]*)>([\s\S]*?)<\/AbstractText>/g;
    let match;
    while ((match = abstractRegex.exec(section)) !== null) {
      const attrs = match[1] || "";
      const labelMatch = attrs.match(/Label="([^"]+)"/i);
      const text = stripTags(match[2]).replace(/\s+/g, " ").trim();
      if (!text) continue;
      abstractParts.push(labelMatch ? `${decodeXmlEntities(labelMatch[1])}: ${text}` : text);
    }

    map[pmid] = abstractParts.join("\n");
  }

  return map;
}

async function searchPubMed(query, retmax = 10, sort = "date") {
  const term = String(query || "").trim();
  if (!term) {
    throw new Error("检索词不能为空");
  }

  const normalizedRetmax = Math.min(Math.max(Number(retmax) || 10, 1), 20);
  const normalizedSort = String(sort || "date").trim().toLowerCase() === "relevance"
    ? "relevance"
    : "pub+date";
  const esearchUrl =
    `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&sort=${normalizedSort}&retmax=${normalizedRetmax}&term=${encodeURIComponent(term)}`;
  const searchJson = await fetchJson(esearchUrl);
  const ids = searchJson?.esearchresult?.idlist || [];
  if (!Array.isArray(ids) || ids.length === 0) {
    return [];
  }

  const idParam = ids.join(",");
  const esummaryUrl =
    `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&id=${encodeURIComponent(idParam)}`;
  const efetchUrl =
    `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=xml&id=${encodeURIComponent(idParam)}`;

  const [summaryJson, fetchXml] = await Promise.all([
    fetchJson(esummaryUrl),
    fetchText(efetchUrl)
  ]);
  const abstractMap = extractAbstractsFromPubMedXml(fetchXml);

  return ids.map((id) => {
    const item = summaryJson?.result?.[id] || {};
    const authors = Array.isArray(item.authors)
      ? item.authors.map((author) => author.name).filter(Boolean).slice(0, 5)
      : [];

    return {
      pmid: id,
      title: item.title || "",
      journal: item.fulljournalname || item.source || "",
      pubdate: item.pubdate || "",
      authors,
      doi: item.elocationid || "",
      abstract: abstractMap[id] || "",
      pubmed_url: `https://pubmed.ncbi.nlm.nih.gov/${id}/`
    };
  });
}

function resolveTranslationProvider(mode, overrides = {}) {
  if (overrides.provider === "deepl" && overrides.deeplApiKey) return "deepl";
  if (overrides.provider === "openai" && overrides.openaiApiKey) return "openai";

  if (TRANSLATION_PROVIDER === "deepl" && DEEPL_API_KEY) return "deepl";
  if (TRANSLATION_PROVIDER === "openai" && OPENAI_API_KEY) return "openai";
  if (TRANSLATION_PROVIDER === "auto") {
    if (mode === "translate" && (overrides.deeplApiKey || DEEPL_API_KEY)) return "deepl";
    if (overrides.openaiApiKey || OPENAI_API_KEY) return "openai";
    if (overrides.deeplApiKey || DEEPL_API_KEY) return "deepl";
  }
  if (overrides.deeplApiKey || DEEPL_API_KEY) return "deepl";
  if (overrides.openaiApiKey || OPENAI_API_KEY) return "openai";
  return null;
}

async function translateWithDeepL(text, apiKey = DEEPL_API_KEY, apiUrl = DEEPL_API_URL) {
  const response = await fetch(apiUrl, {
    method: "POST",
    headers: {
      "Authorization": `DeepL-Auth-Key ${apiKey}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      text: [text],
      target_lang: "ZH"
    })
  });

  if (!response.ok) {
    throw new Error(`DeepL 调用失败：${response.status}`);
  }
  const json = await response.json();
  return json?.translations?.[0]?.text || "";
}

async function summarizeWithOpenAI(
  title,
  text,
  mode,
  apiKey = OPENAI_API_KEY,
  baseUrl = OPENAI_BASE_URL,
  model = OPENAI_MODEL
) {
  const prompt = [
    "你是泌尿外科文献助手。",
    mode === "translate"
      ? "请将用户提供的英文医学文本准确翻译为简体中文。保留医学缩写，并尽量自然。只输出翻译后的中文。"
      : "请用简体中文输出 JSON，字段为 translated_text 和 summary_text。translated_text 是准确中文翻译；summary_text 是适合泌尿外科医生快速阅读的 4-6 条中文要点。"
  ].join(" ");

  const content = title ? `标题：${title}\n\n正文：\n${text}` : text;
  const body = {
    model,
    temperature: 0.2,
    messages: [
      { role: "developer", content: prompt },
      { role: "user", content }
    ]
  };

  if (mode !== "translate") {
    body.response_format = { type: "json_object" };
  }

  const response = await fetch(`${String(baseUrl || OPENAI_BASE_URL).replace(/\/$/, "")}/chat/completions`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${apiKey}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify(body)
  });

  if (!response.ok) {
    throw new Error(`OpenAI 调用失败：${response.status}`);
  }

  const json = await response.json();
  const output = json?.choices?.[0]?.message?.content?.trim() || "";
  if (mode === "translate") {
    return { translatedText: output, summaryText: "" };
  }

  const parsed = JSON.parse(output);
  const summary = Array.isArray(parsed.summary_text)
    ? parsed.summary_text.join("\n")
    : String(parsed.summary_text || "");
  return {
    translatedText: String(parsed.translated_text || ""),
    summaryText: summary
  };
}

async function analyzeText({ title, text, mode, providerPreference, deeplApiKey, deeplApiUrl, openaiApiKey, openaiBaseUrl, openaiModel }) {
  const provider = resolveTranslationProvider(mode, {
    provider: providerPreference,
    deeplApiKey,
    openaiApiKey
  });
  if (!provider) {
    throw new Error("未配置 DeepL 或 OpenAI API，请先在后端设置 API Key");
  }

  if (provider === "deepl") {
    const translatedText = await translateWithDeepL(
      text,
      deeplApiKey || DEEPL_API_KEY,
      deeplApiUrl || DEEPL_API_URL
    );
    return {
      provider,
      translated_text: translatedText,
      summary_text: mode === "translate"
        ? ""
        : "当前后端使用 DeepL，仅支持翻译；如需摘要请配置 OpenAI API。"
    };
  }

  const result = await summarizeWithOpenAI(
    title,
    text,
    mode,
    openaiApiKey || OPENAI_API_KEY,
    openaiBaseUrl || OPENAI_BASE_URL,
    openaiModel || OPENAI_MODEL
  );
  return {
    provider,
    translated_text: result.translatedText,
    summary_text: result.summaryText
  };
}

async function requireAuth(req) {
  const authHeader = String(req.headers.authorization || "");
  const prefix = "Bearer ";
  if (!authHeader.startsWith(prefix)) {
    throw new Error("缺少登录信息");
  }

  const token = authHeader.slice(prefix.length);
  const payload = verifyToken(token);
  const db = await readDb();
  const doctor = db.doctors.find((item) => item.id === payload.doctorId && item.hospitalId === payload.hospitalId);
  const hospital = db.hospitals.find((item) => item.id === payload.hospitalId);

  if (!doctor || !hospital) {
    throw new Error("账号不存在或医院信息失效");
  }

  return { db, doctor, hospital };
}

async function handleRegister(req, res) {
  const body = await readJsonBody(req);
  const hospitalCode = normalizeHospitalCode(body.hospital_code);
  const hospitalName = String(body.hospital_name || "").trim();
  const username = normalizeUsername(body.username);
  const displayName = String(body.display_name || "").trim();
  const password = String(body.password || "");

  if (!hospitalCode) return sendError(res, 400, "医院代码不能为空");
  if (!username) return sendError(res, 400, "用户名不能为空");
  if (!displayName) return sendError(res, 400, "姓名不能为空");
  if (password.length < 4) return sendError(res, 400, "密码至少 4 位");

  const result = await queueDb(async () => {
    const db = await readDb();
    let hospital = db.hospitals.find((item) => item.code === hospitalCode);

    if (!hospital) {
      if (!hospitalName) {
        throw new Error("医院代码不存在；首次注册该医院时请同时填写医院名称");
      }
      hospital = {
        id: db.nextIds.hospital++,
        code: hospitalCode,
        name: hospitalName,
        createdAt: nowIso()
      };
      db.hospitals.push(hospital);
    }

    const usernameExists = db.doctors.some(
      (item) => item.hospitalId === hospital.id && item.username === username
    );
    if (usernameExists) {
      throw new Error("该医院下用户名已存在");
    }

    const doctor = {
      id: db.nextIds.doctor++,
      hospitalId: hospital.id,
      username,
      displayName,
      passwordHash: hashPassword(password),
      createdAt: nowIso()
    };
    db.doctors.push(doctor);
    await writeDb(db);
    return { doctor, hospital };
  });

  const token = createToken({
    doctorId: result.doctor.id,
    hospitalId: result.hospital.id,
    exp: Date.now() + 1000 * 60 * 60 * 24 * 30
  });

  sendJson(res, 200, {
    token,
    doctor: toDoctorPayload(result.doctor, result.hospital)
  });
}

async function handleLogin(req, res) {
  const body = await readJsonBody(req);
  const hospitalCode = normalizeHospitalCode(body.hospital_code);
  const username = normalizeUsername(body.username);
  const password = String(body.password || "");

  if (!hospitalCode) return sendError(res, 400, "医院代码不能为空");
  if (!username || !password) return sendError(res, 400, "请输入用户名和密码");

  const db = await readDb();
  const hospital = db.hospitals.find((item) => item.code === hospitalCode);
  if (!hospital) return sendError(res, 404, "医院代码不存在");

  const doctor = db.doctors.find(
    (item) => item.hospitalId === hospital.id && item.username === username
  );
  if (!doctor) return sendError(res, 404, "账号不存在");

  if (!verifyPassword(password, doctor.passwordHash)) {
    return sendError(res, 401, "密码不正确");
  }

  const token = createToken({
    doctorId: doctor.id,
    hospitalId: hospital.id,
    exp: Date.now() + 1000 * 60 * 60 * 24 * 30
  });

  sendJson(res, 200, {
    token,
    doctor: toDoctorPayload(doctor, hospital)
  });
}

async function handleCurrentDoctor(req, res) {
  const { doctor, hospital } = await requireAuth(req);
  sendJson(res, 200, toDoctorPayload(doctor, hospital));
}

async function handleGetFollowUps(req, res) {
  const { db, hospital } = await requireAuth(req);
  const records = db.followUps
    .filter((item) => item.hospitalId === hospital.id)
    .sort((left, right) => {
      const leftCompleted = left.completedAt ? 1 : 0;
      const rightCompleted = right.completedAt ? 1 : 0;
      if (leftCompleted !== rightCompleted) return leftCompleted - rightCompleted;
      return new Date(left.reminderDate) - new Date(right.reminderDate);
    })
    .map((item) => toFollowUpPayload(item, db));

  sendJson(res, 200, {
    items: records,
    total: records.length
  });
}

async function handleSummary(req, res) {
  const { db, hospital } = await requireAuth(req);
  const records = db.followUps.filter((item) => item.hospitalId === hospital.id);
  sendJson(res, 200, summarizeFollowUps(records));
}

async function handleCreateFollowUp(req, res) {
  const body = await readJsonBody(req);
  const auth = await requireAuth(req);
  const admissionNumber = String(body.admission_number || "").trim();
  const patientName = String(body.patient_name || "").trim();
  const diseaseType = String(body.disease_type || "").trim();
  const procedureType = String(body.procedure_type || "").trim();
  const reminderDate = String(body.reminder_date || "").trim();
  const notes = String(body.notes || "").trim();

  if (!admissionNumber) return sendError(res, 400, "住院号不能为空");
  if (!diseaseType) return sendError(res, 400, "疾病类型不能为空");
  if (!/^\d{4}-\d{2}-\d{2}$/.test(reminderDate)) return sendError(res, 400, "随访日期格式应为 YYYY-MM-DD");

  const created = await queueDb(async () => {
    const db = await readDb();
    const record = {
      id: db.nextIds.followUp++,
      hospitalId: auth.hospital.id,
      admissionNumber,
      patientName,
      contactPhone: "",
      diseaseType,
      procedureType,
      reminderDate,
      notes,
      createdByDoctorId: auth.doctor.id,
      createdAt: nowIso(),
      completionOutcome: null,
      completedByDoctorId: null,
      completedAt: null
    };
    db.followUps.push(record);
    await writeDb(db);
    return { db, record };
  });

  sendJson(res, 200, {
    message: "创建成功",
    item: toFollowUpPayload(created.record, created.db)
  });
}

async function handleCompleteFollowUp(req, res, followUpId) {
  const body = await readJsonBody(req);
  const auth = await requireAuth(req);
  const completionOutcome = String(body.completion_outcome || "").trim();

  const updated = await queueDb(async () => {
    const db = await readDb();
    const record = db.followUps.find(
      (item) => String(item.id) === String(followUpId) && item.hospitalId === auth.hospital.id
    );

    if (!record) throw new Error("随访记录不存在");
    if (record.completedAt) throw new Error("该记录已完成");

    record.completionOutcome = completionOutcome || "已完成随访";
    record.completedByDoctorId = auth.doctor.id;
    record.completedAt = nowIso();
    await writeDb(db);
    return { db, record };
  });

  sendJson(res, 200, {
    message: "已完成",
    item: toFollowUpPayload(updated.record, updated.db)
  });
}

async function handlePubMedSearch(req, res, url) {
  const query = url.searchParams.get("q") || "";
  const retmax = url.searchParams.get("retmax") || "10";
  const sort = url.searchParams.get("sort") || "date";
  const items = await searchPubMed(query, retmax, sort);
  sendJson(res, 200, { items, total: items.length, sort });
}

function buildHighlightQuery(topic) {
  const journalQuery = [
    "\"Eur Urol\"[Journal]",
    "\"Eur Urol Oncol\"[Journal]",
    "\"J Urol\"[Journal]",
    "\"BJU Int\"[Journal]"
  ].join(" OR ");

  const topicMap = {
    all: [
      "urology",
      "urologic",
      "prostate",
      "bladder",
      "renal",
      "kidney",
      "urolithiasis",
      "stone"
    ],
    prostate: ["prostate cancer", "prostatectomy", "bph", "male luts"],
    bladder: ["bladder cancer", "nmibc", "mibc"],
    kidney: ["renal cell carcinoma", "kidney cancer", "partial nephrectomy"],
    stone: ["urolithiasis", "kidney stone", "ureteral stone"],
    infection: ["urological infection", "uti", "pyelonephritis"]
  };

  const selectedTerms = topicMap[topic] || topicMap.all;
  const topicQuery = selectedTerms.map((term) => `"${term}"[Title/Abstract]`).join(" OR ");
  return `((${journalQuery})) AND ((${topicQuery})) AND ("last 365 days"[PDat])`;
}

async function handleHighlights(req, res, url) {
  const topic = String(url.searchParams.get("topic") || "all").trim().toLowerCase();
  const retmax = Math.min(Math.max(Number(url.searchParams.get("retmax") || "3"), 1), 5);
  const includeTranslation = ["1", "true", "yes"].includes(
    String(url.searchParams.get("translated") || "").trim().toLowerCase()
  );

  const query = buildHighlightQuery(topic);
  const items = await searchPubMed(query, retmax);
  const enrichedItems = [];

  for (const item of items) {
    const enriched = { ...item };
    if (includeTranslation && (DEEPL_API_KEY || OPENAI_API_KEY) && item.abstract) {
      try {
        const analysis = await analyzeText({
          title: item.title,
          text: item.abstract,
          mode: "both"
        });
        enriched.translated_abstract = analysis.translated_text || "";
        enriched.summary_text = analysis.summary_text || "";
        enriched.analysis_provider = analysis.provider || "";
      } catch {
        enriched.translated_abstract = "";
        enriched.summary_text = "";
        enriched.analysis_provider = "";
      }
    }
    enrichedItems.push(enriched);
  }

  sendJson(res, 200, {
    topic,
    query,
    items: enrichedItems
  });
}

async function handleGuidelines(req, res, url) {
  const q = String(url.searchParams.get("q") || "").trim().toLowerCase();
  const source = String(url.searchParams.get("source") || "").trim().toUpperCase();
  const guidelines = await readGuidelines();
  const filtered = guidelines.filter((item) => {
    if (source && item.source !== source) return false;
    if (!q) return true;
    const haystack = [item.topic, item.title, item.note, item.source].join(" ").toLowerCase();
    return haystack.includes(q);
  });

  sendJson(res, 200, {
    items: filtered
  });
}

async function handleResearchAnalyze(req, res) {
  const body = await readJsonBody(req);
  const text = String(body.text || "").trim();
  const title = String(body.title || "").trim();
  const mode = String(body.mode || "both").trim().toLowerCase();
  const providerPreference = String(body.provider || "").trim().toLowerCase();
  const deeplApiKey = String(body.deepl_api_key || "").trim();
  const deeplApiUrl = String(body.deepl_api_url || "").trim();
  const openaiApiKey = String(body.openai_api_key || "").trim();
  const openaiBaseUrl = String(body.openai_base_url || "").trim();
  const openaiModel = String(body.openai_model || "").trim();

  if (!text) return sendError(res, 400, "正文不能为空");
  if (!["translate", "summary", "both"].includes(mode)) {
    return sendError(res, 400, "mode 仅支持 translate、summary、both");
  }

  const result = await analyzeText({
    title,
    text,
    mode,
    providerPreference,
    deeplApiKey,
    deeplApiUrl,
    openaiApiKey,
    openaiBaseUrl,
    openaiModel
  });
  sendJson(res, 200, result);
}

async function requestHandler(req, res) {
  try {
    if (req.method === "OPTIONS") {
      return sendJson(res, 200, { ok: true });
    }

    const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
    const pathname = url.pathname;

    if (req.method === "GET" && pathname === "/health") {
      return sendJson(res, 200, { status: "ok", time: nowIso() });
    }

    if (req.method === "GET" && pathname === "/api/v1/meta/disease-options") {
      return sendJson(res, 200, { items: DEFAULT_DISEASE_OPTIONS });
    }

    if (req.method === "GET" && pathname === "/api/v1/research/pubmed/search") {
      return handlePubMedSearch(req, res, url);
    }

    if (req.method === "GET" && pathname === "/api/v1/research/highlights") {
      return handleHighlights(req, res, url);
    }

    if (req.method === "GET" && pathname === "/api/v1/research/guidelines") {
      return handleGuidelines(req, res, url);
    }

    if (req.method === "POST" && pathname === "/api/v1/research/analyze") {
      return handleResearchAnalyze(req, res);
    }

    if (req.method === "POST" && pathname === "/api/v1/auth/register") {
      return handleRegister(req, res);
    }

    if (req.method === "POST" && pathname === "/api/v1/auth/login") {
      return handleLogin(req, res);
    }

    if (req.method === "GET" && pathname === "/api/v1/auth/me") {
      return handleCurrentDoctor(req, res);
    }

    if (req.method === "GET" && pathname === "/api/v1/followups") {
      return handleGetFollowUps(req, res);
    }

    if (req.method === "GET" && pathname === "/api/v1/followups/summary") {
      return handleSummary(req, res);
    }

    if (req.method === "POST" && pathname === "/api/v1/followups") {
      return handleCreateFollowUp(req, res);
    }

    const completeMatch = pathname.match(/^\/api\/v1\/followups\/([^/]+)\/complete$/);
    if ((req.method === "PATCH" || req.method === "POST") && completeMatch) {
      return handleCompleteFollowUp(req, res, completeMatch[1]);
    }

    sendError(res, 404, "接口不存在");
  } catch (error) {
    const message = error instanceof Error ? error.message : "服务器错误";
    const statusCode =
      message.includes("不存在") ? 404 :
      message.includes("密码") || message.includes("登录") || message.includes("缺少") || message.includes("校验") ? 401 :
      message.includes("不能为空") || message.includes("格式") ? 400 :
      500;
    sendError(res, statusCode, message);
  }
}

ensureDb()
  .then(() => {
    const server = http.createServer(requestHandler);
    server.listen(PORT, HOST, () => {
      console.log(`Urology follow-up server running at http://${HOST}:${PORT}`);
      console.log(`Data file: ${DB_PATH}`);
    });
  })
  .catch((error) => {
    console.error("Failed to start server:", error);
    process.exit(1);
  });
