// L1 단위 테스트 — 계산 로직 경계값. 브라우저 없이 Node 에서 직접 실행.
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import url from "node:url";
const here = path.dirname(url.fileURLToPath(import.meta.url));
const load = (rel) => {
  const m = {};
  new Function("module", fs.readFileSync(path.join(here, rel), "utf8"))(m);
  return m.exports;
};
const GAON = load("../../public/work/gaon-lms/calc.js");
const DURE = load("../../public/work/dure-enrollment/calc.js");

let passed = 0, failed = 0;
const t = (name, fn) => {
  try { fn(); passed++; console.log("PASS  " + name); }
  catch (e) { failed++; console.log("FAIL  " + name + " — " + e.message); }
};

console.log("── 가온 ──");
t("attSummary: 전원 출석 → 100%", () => assert.equal(GAON.attSummary([2, 2, 2, 2, 2]), "100%"));
t("attSummary: 혼합 4/5 → 80%", () => assert.equal(GAON.attSummary([2, 2, 2, 2, 1]), "80%"));
t("attSummary: 빈 행 → 휴학", () => assert.equal(GAON.attSummary([]), "휴학"));
t("attSummary: null/undefined 무시", () => assert.equal(GAON.attSummary([2, null, 2, undefined, 0]), "67%"));
t("attSummary: 1/3 반올림 → 33%", () => assert.equal(GAON.attSummary([2, 0, 0]), "33%"));

t("dayTotal/dayPresent: 행 배열 목록 (0/3 사고 회귀)", () => {
  const marks = [[2, 2, 2, 2, 2], [2, 1, 2, 2, 2], null];
  assert.equal(GAON.dayTotal(marks, 0), 2);
  assert.equal(GAON.dayPresent(marks, 0), 2);
  assert.equal(GAON.dayPresent(marks, 1), 1); // 지각 1명은 출석 아님
});
t("dayPresent: 전원 결석일 → 0", () => assert.equal(GAON.dayPresent([[0, 0], [0, 0]], 0), 0));
t("dayTotal: 전부 null 행 → 0 (나눗셈 가드)", () => assert.equal(GAON.dayTotal([null, null], 0), 0));

t("counts: 집계 정합", () => {
  const c = GAON.counts([[2, 1, 0], [2, 2, 2]]);
  assert.deepEqual([c.att, c.late, c.abs, c.total, c.rate], [4, 1, 1, 6, 67]);
});
t("counts: 빈 입력 → total 0, rate 0 (NaN 아님)", () => {
  const c = GAON.counts([]);
  assert.equal(c.rate, 0); assert.equal(c.total, 0); assert.ok(!Number.isNaN(c.rate));
});

t("rosterStats: 0명 명단 → 0/NaN 없음", () => {
  const s = GAON.rosterStats([]);
  assert.deepEqual([s.total, s.active, s.avgAtt, s.hwRate], [0, 0, 0, 0]);
});
t("rosterStats: 재학만 통계에 포함", () => {
  const s = GAON.rosterStats([
    { status: "재학", att: 90, hw: "8/8" },
    { status: "재학", att: 70, hw: "6/8" },
    { status: "휴학", att: 0, hw: "0/8" },
  ]);
  assert.equal(s.active, 2);
  assert.equal(s.avgAtt, 80);
  assert.deepEqual([s.hwDone, s.hwTotal, s.hwRate], [14, 16, 88]);
});
t("rosterStats: 홀수 반올림", () => assert.equal(GAON.rosterStats([{ status: "재학", att: 91, hw: "1/3" }]).avgAtt, 91));

console.log("── 두레 ──");
t("isOverlap: 같은 요일 겹침 → true", () =>
  assert.equal(DURE.isOverlap({ days: [1, 3], start: 10, end: 11.5 }, { days: [3], start: 11, end: 12 }), true));
t("isOverlap: 닿는 경계(끝==시작) → false", () =>
  assert.equal(DURE.isOverlap({ days: [1], start: 10, end: 12 }, { days: [1], start: 12, end: 14 }), false));
t("isOverlap: 다른 요일 같은 시간 → false", () =>
  assert.equal(DURE.isOverlap({ days: [1], start: 10, end: 12 }, { days: [2], start: 10, end: 12 }), false));
t("isOverlap: 동일 과목 → true", () =>
  assert.equal(DURE.isOverlap({ days: [2, 4], start: 13, end: 14.5 }, { days: [4, 2], start: 13, end: 14.5 }), true));
t("isOverlap: 부분 겹침 → true", () =>
  assert.equal(DURE.isOverlap({ days: [5], start: 9, end: 12 }, { days: [5], start: 11, end: 13 }), true));

t("credits: 합계", () => assert.equal(DURE.credits([{ credits: 3 }, { credits: 2 }]), 5));
t("credits: 빈 목록 → 0", () => assert.equal(DURE.credits([]), 0));
t("credits: credits 없는 항목 → 0 취급", () => assert.equal(DURE.credits([{}, { credits: 3 }]), 3));

t("wouldExceed: 15+3 ≤ 18 → false", () => assert.equal(DURE.wouldExceed(15, 3, 18), false));
t("wouldExceed: 16+3 > 18 → true (경계)", () => assert.equal(DURE.wouldExceed(16, 3, 18), true));
t("wouldExceed: 18+0 → false (경계)", () => assert.equal(DURE.wouldExceed(18, 0, 18), false));

t("seatsState: 만석 → 정원 마감", () => assert.deepEqual(DURE.seatsState({ seats: 0 }), { label: "정원 마감", closed: true, near: false }));
t("seatsState: 여석 5 → 임박", () => assert.ok(DURE.seatsState({ seats: 5 }).near));
t("seatsState: 여석 6 → 임박 아님 (경계)", () => assert.ok(!DURE.seatsState({ seats: 6 }).near));

t("remaining: 정상 잔여", () => assert.equal(DURE.remaining(130, 94, 3), 33));
t("remaining: 초과 → 음수", () => assert.equal(DURE.remaining(24, 21, 6), -3));

console.log(`\n${failed ? `${failed} FAIL` : "UNIT ALL PASS"} (${passed} passed)`);
process.exit(failed ? 1 : 0);
