import assert from "node:assert/strict";
import test from "node:test";
import { buildClassFromBmi, computeBmi, parseHeightToInches } from "@/lib/mortgage/bmi";
import { STUB_CARRIERS, stubDecision } from "@/lib/mortgage/decisionStub";
import { computeProcess, detectStopPath, missingRequired } from "@/lib/mortgage/gates";
import { emptyProfile } from "@/lib/mortgage/schema";
import { heuristicPatch, mergeProfile } from "@/lib/mortgage/extract";

test("S3-09 malformed height leaves BMI undefined", () => {
  assert.equal(parseHeightToInches("kind of tall, dunno inches"), null);
  assert.equal(computeBmi("kind of tall, dunno inches", 190), undefined);
});

test("S3-10 to S3-15 BMI build-class boundaries", () => {
  assert.equal(buildClassFromBmi(18.5), "si_level");
  assert.equal(buildClassFromBmi(18.4), "underweight_postpone");
  assert.equal(buildClassFromBmi(38), "si_level");
  assert.equal(buildClassFromBmi(42), "si_level");
  assert.equal(buildClassFromBmi(42.1), "graded");
  assert.equal(buildClassFromBmi(46), "graded");
  assert.equal(buildClassFromBmi(46.1), "gi_or_decline");
});

test("S3-08 tobacco still required after height and weight", () => {
  const p = mergeProfile(emptyProfile(), {
    name: "Ann",
    state: "FL",
    intent: "mortgage_protection",
    coverage_on_whom: "primary",
    age: 40,
    loan_balance: 200000,
    height_ft_in: "5'10\"",
    weight_lb: 190,
  });
  assert.ok(missingRequired(p, 3).includes("tobacco"));
  assert.equal(computeProcess(p).step, 3);
});

test("S6-01 through S6-06 and S6-08 decision precedence", () => {
  const clean = stubDecision({ ...emptyProfile(), bmi_computed: 25, diseases_none_reported: true });
  assert.equal(clean?.offer_family, "si_level");
  assert.ok(clean?.reasons.some((r) => /No disease/i.test(r)));

  assert.equal(stubDecision({ ...emptyProfile(), bmi_computed: 40 })?.offer_family, "si_level");
  assert.equal(stubDecision({ ...emptyProfile(), bmi_computed: 43 })?.offer_family, "graded");
  assert.equal(stubDecision({ ...emptyProfile(), bmi_computed: 47 })?.offer_family, "gi");

  const hard = stubDecision({
    ...emptyProfile(),
    bmi_computed: 24,
    impairments: [{ module: "kidney", attributes: { disclosed: true, notes: "dialysis" } }],
  });
  assert.equal(hard?.offer_family, "graded");

  const empty = stubDecision(emptyProfile());
  assert.ok(empty?.reasons.some((r) => /Illustrative path/i.test(r)));

  const combo = stubDecision({
    ...emptyProfile(),
    bmi_computed: 47,
    impairments: [{ module: "kidney", attributes: { dialysis: true } }],
  });
  assert.equal(combo?.offer_family, "gi", "S6-08: BMI>46 short-circuits before hard-impairment graded");

  const comboGraded = stubDecision({
    ...emptyProfile(),
    bmi_computed: 44,
    impairments: [{ module: "lung", attributes: { oxygen: true } }],
  });
  assert.equal(comboGraded?.offer_family, "graded");
});

test("S6-07 decision_result missing keeps step 6", () => {
  const p = mergeProfile(emptyProfile(), {
    name: "Ann",
    state: "FL",
    intent: "mortgage_protection",
    coverage_on_whom: "primary",
    age: 40,
    loan_balance: 200000,
    height_ft_in: "5'6\"",
    weight_lb: 140,
    tobacco_answered: true,
    diseases_complete: true,
    annual_income: 85000,
    beneficiary_relationship: "self",
  });
  assert.ok(missingRequired(p, 6).includes("decision_result"));
  assert.equal(computeProcess(p).step, 6);
});

test("S7-01 STUB_CARRIERS and S7-02 empty shortlist", () => {
  assert.ok(STUB_CARRIERS.length >= 1);
  const p = mergeProfile(emptyProfile(), {
    name: "Ann",
    state: "FL",
    intent: "mortgage_protection",
    coverage_on_whom: "primary",
    age: 40,
    loan_balance: 200000,
    height_ft_in: "5'6\"",
    weight_lb: 140,
    tobacco_answered: true,
    diseases_complete: true,
    annual_income: 85000,
    beneficiary_relationship: "self",
    decision_result: {
      offer_family: "gi",
      reasons: ["x"],
      drivers: [],
      suggested_next_step: "agent",
    },
  });
  assert.ok(missingRequired(p, 7).includes("carrier_shortlist"));
  const listed = mergeProfile(p, { carrier_shortlist: [...STUB_CARRIERS] });
  assert.equal(computeProcess(listed).step, 8);
});

test("S8-02 handoff ack missing", () => {
  const p = mergeProfile(emptyProfile(), {
    name: "Ann",
    state: "FL",
    intent: "mortgage_protection",
    coverage_on_whom: "primary",
    age: 40,
    loan_balance: 200000,
    height_ft_in: "5'6\"",
    weight_lb: 140,
    tobacco_answered: true,
    diseases_complete: true,
    annual_income: 85000,
    beneficiary_relationship: "self",
    decision_result: {
      offer_family: "si_level",
      reasons: ["x"],
      drivers: [],
      suggested_next_step: "agent",
    },
    carrier_shortlist: ["A"],
  });
  assert.ok(missingRequired(p, 8).includes("agent_handoff_ack"));
  assert.equal(computeProcess(p).step, 8);
});

test("SP-04 refinance plus protect does not WR-LOAN", () => {
  assert.equal(detectStopPath("I want to refinance and also protect the loan"), null);
});

test("SP-09 I rent still NO-MTG even with protect wording", () => {
  assert.equal(detectStopPath("I rent but want to protect my future home purchase"), "NO-MTG");
});

test("SP-02 paid off without protect is NO-MTG", () => {
  assert.equal(detectStopPath("My mortgage is paid off"), "NO-MTG");
});

test("S2-05 income must not become loan_balance", () => {
  const p = heuristicPatch("I make 85000 a year");
  assert.equal(p.annual_income, 85000);
  assert.equal(p.loan_balance, undefined);
});

test("MISC-04 mergeProfile ignores garbage patches", () => {
  assert.equal(mergeProfile(emptyProfile(), null).intent, null);
  assert.equal(mergeProfile(emptyProfile(), [1, 2, 3] as unknown as object).age, undefined);
  assert.equal(mergeProfile(emptyProfile(), 42 as unknown as object).age, undefined);
});

test("MISC-06 accented given name is truncated to ASCII prefix Ren", () => {
  const p = mergeProfile(emptyProfile(), heuristicPatch("My name is Renée O'Connor"));
  assert.equal(p.name, "Ren");
});
