// 中間サーバ連携基盤 — 参照実装（標準準拠システム間の情報連携ハブのモック）
//
// 標準のモデルを体現する:
//  - 住民は「宛名番号」で名寄せ。マイナンバーは保持も伝送もしない。
//  - 機関(システム)ごとに「機関別符号」を宛名番号から導出（符号同士の突合で連携、生のマイナンバーを使わない）。
//  - 情報照会は「別表」(事務ごとに照会可能な情報)で許可されたものだけ。範囲外は拒否。
//  - 照会・提供はすべて「連携ログ」に記録（監査）。
import { createServer } from "node:http";
import { readFile, writeFile } from "node:fs/promises";
import { createHash, randomUUID } from "node:crypto";
import { fileURLToPath } from "node:url";
import { dirname, join, extname } from "node:path";
import { existsSync } from "node:fs";

const __dirname = dirname(fileURLToPath(import.meta.url));
const dataDir = process.env.CHUUKAN_DATA_DIR ? process.env.CHUUKAN_DATA_DIR : join(__dirname, "data");
const webRoot = join(__dirname, "..", "web");
const PORT = Number(process.env.PORT || 4290);
const CODE_SALT = process.env.CHUUKAN_CODE_SALT || "reference-impl-salt"; // 参照実装用の固定ソルト
// 機関認証（参照実装）: 照会/提供は X-Agency-Token 必須。実運用では機関ごとの証明書・専用線等に相当
const AGENCY_TOKEN = process.env.CHUUKAN_AGENCY_TOKEN || "demo-linkage-token";

// 機関(システム)辞書: key=機関コード, 値=名称
const AGENCIES = {
  juki: "住民基本台帳", tax: "地方税", kokuho: "国民健康保険", kaigo: "介護保険",
  kouki: "後期高齢者医療", nenkin: "国民年金", jido: "児童手当",
  kosodate: "子ども・子育て支援", seikatsu: "生活保護", shogai: "障害者福祉",
};

const j = (file, fallback) =>
  readFile(join(dataDir, file), "utf8").then(JSON.parse).catch(() => fallback);

// データ（起動時ロード、副本登録/照会で更新）
let residents = [], holdings = [], infoItems = [], linkDefs = [], auditLog = [];
async function load() {
  residents = await j("residents.json", []);
  holdings = await j("holdings.json", []);
  infoItems = await j("info-items.json", []);
  linkDefs = await j("link-definitions.json", []);
  auditLog = await j("linkage-log.json", []);
}

// 機関別符号: 宛名番号×機関 から導出した不可逆の符号（マイナンバーは関与しない）
function agencyCode(atena, agency) {
  return createHash("sha256").update(`${atena}:${agency}:${CODE_SALT}`).digest("hex").slice(0, 16);
}
function maskCode(code) { return code.slice(0, 4) + "…" + code.slice(-2); }

async function persistAudit() {
  try { await writeFile(join(dataDir, "linkage-log.json"), JSON.stringify(auditLog, null, 2)); } catch {}
}
async function record(row) {
  const entry = { id: randomUUID(), ts: new Date().toISOString(), ...row };
  auditLog.unshift(entry);
  if (auditLog.length > 5000) auditLog.length = 5000;
  await persistAudit();
  return entry;
}

const send = (res, status, payload) => {
  const body = JSON.stringify(payload);
  res.writeHead(status, { "content-type": "application/json; charset=utf-8", "content-length": Buffer.byteLength(body), "cache-control": "no-store" });
  res.end(body);
};
async function parseBody(req) {
  const chunks = [];
  for await (const c of req) chunks.push(c);
  if (!chunks.length) return {};
  try { return JSON.parse(Buffer.concat(chunks).toString("utf8")); } catch { return {}; }
}

const MIME = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".json": "application/json; charset=utf-8", ".svg": "image/svg+xml" };
async function serveStatic(req, res, pathname) {
  const rel = pathname === "/" ? "/index.html" : pathname;
  const filePath = join(webRoot, rel);
  if (!filePath.startsWith(webRoot) || !existsSync(filePath)) return false;
  const buf = await readFile(filePath);
  res.writeHead(200, { "content-type": MIME[extname(filePath)] || "application/octet-stream" });
  res.end(buf);
  return true;
}

// 名寄せビュー: 宛名番号→基本情報＋保有機関（符号はマスク表示）
function nameMatch(atena) {
  const r = residents.find((x) => x.atena === atena);
  if (!r) return null;
  const held = (holdings.find((h) => h.atena === atena)?.agencies) || [];
  return {
    ...r,
    agencies: held.map((a) => ({ agency: a, name: AGENCIES[a] || a, codeMasked: maskCode(agencyCode(atena, a)) })),
    items: infoItems.filter((i) => i.atena === atena).map((i) => ({ holder: i.holder, holderName: AGENCIES[i.holder] || i.holder, item: i.item, fiscalYear: i.fiscalYear })),
  };
}

const server = createServer(async (req, res) => {
  try {
    const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
    const p = url.pathname;

    if (req.method === "GET" && (p === "/api/health" || p === "/health"))
      return send(res, 200, { ok: true, service: "中間サーバ連携基盤", residents: residents.length, audit: auditLog.length });

    if (req.method === "GET" && p === "/api/v1/agencies")
      return send(res, 200, Object.entries(AGENCIES).map(([code, name]) => ({ code, name })));

    if (req.method === "GET" && p === "/api/v1/residents")
      return send(res, 200, residents.map((r) => ({
        atena: r.atena, name: r.name, kana: r.kana,
        agencyCount: (holdings.find((h) => h.atena === r.atena)?.agencies || []).length,
      })));

    if (req.method === "GET" && p.startsWith("/api/v1/residents/")) {
      const atena = decodeURIComponent(p.split("/").pop());
      const m = nameMatch(atena);
      return m ? send(res, 200, m) : send(res, 404, { error: "宛名番号が見つかりません" });
    }

    if (req.method === "GET" && p === "/api/v1/link-definitions")
      return send(res, 200, linkDefs.map((d) => ({ ...d, requesterName: AGENCIES[d.requester] || d.requester })));

    if (req.method === "GET" && p === "/api/v1/audit")
      return send(res, 200, auditLog.slice(0, Number(url.searchParams.get("limit") || 100)));

    // 機関認証: 照会・提供は X-Agency-Token 必須（GET系のダッシュボード閲覧は対象外）
    if (req.method === "POST" && (p === "/api/v1/query" || p === "/api/v1/provide")) {
      if ((req.headers["x-agency-token"] || "") !== AGENCY_TOKEN)
        return send(res, 401, { error: "機関認証が必要です（X-Agency-Token ヘッダ）" });
    }

    // 情報照会: 別表で許可された事務のみ。範囲外は拒否（いずれもログ記録）
    if (req.method === "POST" && p === "/api/v1/query") {
      const { requester, atena, holder, item, purpose } = await parseBody(req);
      if (!requester || !atena || !holder || !item)
        return send(res, 400, { error: "requester, atena, holder, item は必須です" });
      const resident = residents.find((x) => x.atena === atena);
      if (!resident) return send(res, 404, { error: "宛名番号が見つかりません" });
      const def = linkDefs.find((d) => d.requester === requester);
      const allowed = !!def && def.allowed.some((a) => a.holder === holder && a.item === item);
      if (!allowed) {
        const logged = await record({ type: "拒否", requester, requesterName: AGENCIES[requester] || requester, holder, item, atena, purpose: purpose || def?.procedure || "", result: "別表範囲外のため拒否" });
        return send(res, 403, { error: "この事務では照会できない情報です（別表範囲外）", audit: logged });
      }
      const found = infoItems.find((i) => i.atena === atena && i.holder === holder && i.item === item);
      if (!found) {
        await record({ type: "照会", requester, requesterName: AGENCIES[requester] || requester, holder, item, atena, purpose: purpose || def.procedure, result: "該当データなし" });
        return send(res, 404, { error: "保有機関に該当データがありません" });
      }
      // 機関別符号で名寄せした体裁（生のマイナンバーは使わない）
      const requesterCode = agencyCode(atena, requester);
      const holderCode = agencyCode(atena, holder);
      const logged = await record({ type: "照会", requester, requesterName: AGENCIES[requester] || requester, holder, holderName: AGENCIES[holder] || holder, item, atena, purpose: purpose || def.procedure, result: "提供", requesterCodeMasked: maskCode(requesterCode), holderCodeMasked: maskCode(holderCode) });
      return send(res, 200, { atena, requester, holder, item, value: found.value, fiscalYear: found.fiscalYear, via: "機関別符号による名寄せ", audit: logged });
    }

    // 副本登録: 機関が保有する特定個人情報を中間サーバへ提供（登録）
    if (req.method === "POST" && p === "/api/v1/provide") {
      const { holder, atena, item, value, fiscalYear } = await parseBody(req);
      if (!holder || !atena || !item || value === undefined)
        return send(res, 400, { error: "holder, atena, item, value は必須です" });
      if (!residents.find((x) => x.atena === atena)) return send(res, 404, { error: "宛名番号が見つかりません" });
      const idx = infoItems.findIndex((i) => i.atena === atena && i.holder === holder && i.item === item);
      const row = { atena, holder, item, value, fiscalYear: fiscalYear || new Date().getFullYear() };
      if (idx >= 0) infoItems[idx] = row; else infoItems.push(row);
      try { await writeFile(join(dataDir, "info-items.json"), JSON.stringify(infoItems, null, 2)); } catch {}
      const logged = await record({ type: "提供登録", requester: holder, requesterName: AGENCIES[holder] || holder, holder, item, atena, result: "副本を登録/更新" });
      return send(res, 200, { ok: true, item: row, audit: logged });
    }

    if (await serveStatic(req, res, p)) return;
    send(res, 404, { error: "not found" });
  } catch (e) {
    send(res, 500, { error: String(e && e.message || e) });
  }
});

await load();
server.listen(PORT, () => console.log(`中間サーバ連携基盤 (参照実装) listening on http://localhost:${PORT}`));

export { server };
