import { describe, expect, test } from "bun:test";
import {
  buildAmdCounts,
  buildWritePlan,
  renderRunReport,
  verifyWritePlan,
  type AmdVisitInput,
  type RipplingInspection,
  type VerificationCheckpoint,
} from "./shift-approval.js";
import { loadConfig } from "./pay-mapping.js";

const cfg = loadConfig();
const period = { start: "2026-05-16", end: "2026-05-29" };

describe("shift approval checkpoints", () => {
  test("provider mapping covers current contractor roster", () => {
    const headings = new Set(cfg.providers.map((provider) => provider.amd_column_heading));
    for (const requiredProvider of [
      "NGOMENI MBILIKIRA",
      "SERAH MAYODI",
      "BRIA HAWKINS",
      "SKYE TOLES",
      "RHONDA EMMONS",
      "JERRITT TODD",
    ]) {
      expect(headings.has(requiredProvider)).toBe(true);
    }
  });

  test("classifies mapped provider/date encounter inputs without PHI", () => {
    const visits: AmdVisitInput[] = [
      visit("1", "2026-05-19", "SERAH MAYODI", 6610, 3, "Jane Doe"),
      visit("2", "2026-05-19", "SERAH MAYODI", 6611, 3, "John Smith"),
      visit("3", "2026-05-19", "SERAH MAYODI", 6611, 10, "Alex Patient"),
      visit("4", "2026-05-19", "BRIA HAWKINS", 6608, 3, "Casey Example"),
      visit("5", "2026-05-19", "BRIA HAWKINS", 6608, 10, "Taylor Test"),
    ];

    const counts = buildAmdCounts(visits, period, cfg, "fixture");
    expect(counts.blockingIssues).toHaveLength(0);
    expect(counts.counts).toHaveLength(2);
    expect(counts.counts.find((row) => row.providerName === "SERAH MAYODI")?.role).toBe("psych");
    expect(counts.counts.find((row) => row.providerName === "BRIA HAWKINS")?.role).toBe("therapy");
    expect(counts.totals).toMatchObject({
      newPatientAppointments: 1,
      existingPatientAppointments: 2,
      billableNoShowAppointments: 1,
      providerDates: 2,
    });
    expect(JSON.stringify(counts)).not.toContain("Jane");
    expect(JSON.stringify(counts)).not.toContain("Smith");
  });

  test("fails closed on unmapped payable providers or appointment types", () => {
    const counts = buildAmdCounts([
      visit("1", "2026-05-19", "UNKNOWN PROVIDER", 6611, 3),
      visit("2", "2026-05-19", "SERAH MAYODI", 999999, 3),
    ], period, cfg, "fixture");

    expect(counts.blockingIssues.map((issue) => issue.code)).toEqual([
      "unmapped_provider",
      "unmapped_appointment_type",
    ]);
  });

  test("requires explicit billable evidence for payable no-shows", () => {
    const counts = buildAmdCounts([
      { ...visit("1", "2026-05-19", "SERAH MAYODI", 6611, 10), billableNoShow: false },
    ], period, cfg, "fixture");

    expect(counts.blockingIssues.map((issue) => issue.code)).toEqual(["unverified_billable_noshow"]);
    expect(counts.counts).toHaveLength(0);
  });

  test("recognizes Ngomeni paid rows that already contain expected inputs", () => {
    const counts = buildAmdCounts([
      ...repeatVisits(15, "ng-e", "2026-05-19", "NGOMENI MBILIKIRA", 6611, 3),
      ...repeatVisits(2, "ng-n", "2026-05-19", "NGOMENI MBILIKIRA", 6611, 10),
    ], period, cfg, "fixture");

    const plan = buildWritePlan(counts, inspection([
      {
        rowId: "ng-2026-05-19",
        providerEmail: "ngomeni.mbilikira@exulthealthcare.com",
        date: "2026-05-19",
        approved: true,
        paid: true,
        shiftInputs: {
          existingPatientAppointments: 15,
          billableNoShowAppointments: 2,
        },
      },
    ]));

    expect(plan.summary.verifyOnly).toBe(1);
    expect(plan.summary.needsHumanReview).toBe(0);
    expect(plan.actions[0].reason).toContain("Paid row already contains");
  });

  test("plans editable unapproved rows for entry and approval", () => {
    const counts = buildAmdCounts([visit("1", "2026-05-20", "SERAH MAYODI", 6611, 3)], period, cfg, "fixture");
    const plan = buildWritePlan(counts, inspection([
      {
        rowId: "se-2026-05-20",
        providerEmail: "serah.mayodi@exulthealthcare.com",
        date: "2026-05-20",
        approved: false,
        paid: false,
        shiftInputs: {},
      },
    ]));

    expect(plan.summary.enterAndApprove).toBe(1);
    expect(plan.actions[0].expectedInputs.existingPatientAppointments).toBe(1);
  });

  test("approves draft rows that already contain expected inputs", () => {
    const counts = buildAmdCounts([visit("1", "2026-05-20", "SERAH MAYODI", 6611, 3)], period, cfg, "fixture");
    const plan = buildWritePlan(counts, inspection([
      {
        rowId: "se-2026-05-20",
        providerEmail: "serah.mayodi@exulthealthcare.com",
        date: "2026-05-20",
        approved: false,
        paid: false,
        shiftInputs: { existingPatientAppointments: 1 },
      },
    ]));

    expect(plan.summary.enterAndApprove).toBe(1);
    expect(plan.summary.verifyOnly).toBe(0);
    expect(plan.actions[0].reason).toContain("still needs approval");
  });

  test("does not create missing Rippling rows", () => {
    const counts = buildAmdCounts([visit("1", "2026-05-21", "SERAH MAYODI", 6611, 3)], period, cfg, "fixture");
    const plan = buildWritePlan(counts, inspection([]));

    expect(plan.summary.needsHumanReview).toBe(1);
    expect(plan.actions[0].reason).toContain("must not create");
  });

  test("run report itemizes skipped and human-review reasons", () => {
    const counts = buildAmdCounts([visit("1", "2026-05-21", "SERAH MAYODI", 6611, 3)], period, cfg, "fixture");
    const plan = buildWritePlan(counts, inspection([]));
    const report = renderRunReport({ counts, plan });

    expect(report).toContain("## Skipped Or Human Review");
    expect(report).toContain("SERAH MAYODI 2026-05-21");
    expect(report).toContain("must not create a new time entry");
  });

  test("requires human review for locked paid mismatches without reversible controls", () => {
    const counts = buildAmdCounts([visit("1", "2026-05-22", "SERAH MAYODI", 6611, 3)], period, cfg, "fixture");
    const plan = buildWritePlan(counts, inspection([
      {
        rowId: "se-2026-05-22",
        providerEmail: "serah.mayodi@exulthealthcare.com",
        date: "2026-05-22",
        approved: true,
        paid: true,
        locked: true,
        editable: { inputs: false, approve: false, unapprove: false, markPaid: false, unmarkPaid: false },
        shiftInputs: {},
      },
    ]));

    expect(plan.summary.needsHumanReview).toBe(1);
    expect(plan.actions[0].reason).toContain("locked");
  });

  test("flags verification mismatches against the write plan", () => {
    const counts = buildAmdCounts([visit("1", "2026-05-23", "SERAH MAYODI", 6611, 3)], period, cfg, "fixture");
    const plan = buildWritePlan(counts, inspection([
      {
        rowId: "se-2026-05-23",
        providerEmail: "serah.mayodi@exulthealthcare.com",
        date: "2026-05-23",
        approved: false,
        paid: false,
        shiftInputs: {},
      },
    ]));
    const verification: VerificationCheckpoint = {
      schemaVersion: 1,
      verifiedAt: "2026-05-30T00:00:00.000Z",
      period,
      rows: [{
        providerEmail: "serah.mayodi@exulthealthcare.com",
        date: "2026-05-23",
        rowId: "se-2026-05-23",
        savedInputs: {},
        approved: false,
        paid: false,
      }],
    };

    expect(verifyWritePlan(plan, verification).mismatches).toHaveLength(2);
  });
});

function visit(
  id: string,
  date: string,
  columnheading: string,
  appointmentTypeId: number,
  apptstatus: number,
  patient_name = "redacted"
): AmdVisitInput {
  return {
    id,
    date,
    columnheading,
    appointmentType: `type-${appointmentTypeId}`,
    appointmentTypeId,
    apptstatus,
    billableNoShow: apptstatus === 10 ? true : undefined,
    patient_name,
  };
}

function repeatVisits(
  count: number,
  idPrefix: string,
  date: string,
  columnheading: string,
  appointmentTypeId: number,
  apptstatus: number
): AmdVisitInput[] {
  return Array.from({ length: count }, (_, index) => visit(`${idPrefix}-${index}`, date, columnheading, appointmentTypeId, apptstatus));
}

function inspection(rows: Partial<RipplingInspection["rows"][number]>[]): RipplingInspection {
  return {
    schemaVersion: 1,
    inspectedAt: "2026-05-30T00:00:00.000Z",
    period,
    schedules: [{ id: "psych", name: "Psych Contractor Time Card", roleName: "Psych Contractor" }],
    rows: rows.map((row) => ({
      rowId: row.rowId ?? `${row.providerEmail}-${row.date}`,
      providerEmail: row.providerEmail!,
      providerName: row.providerName,
      date: row.date!,
      approved: row.approved ?? false,
      paid: row.paid ?? false,
      locked: row.locked ?? false,
      editable: row.editable ?? {
        inputs: true,
        approve: true,
        unapprove: true,
        markPaid: true,
        unmarkPaid: true,
      },
      shiftInputs: row.shiftInputs ?? {},
    })),
  };
}
