import { describe, expect, test } from "bun:test"

import { parseRoster } from "@/lib/db/roster"

const HEADER = "Departments\tPositions\tEmployee Code\tName \tLast Name\tLevel"
const ROW = "Accounting\tเสมียน 1\t10000038\tอรัญญา\tจันทร์หอม\tStaff"

const file = (...rows: string[]) => [HEADER, ...rows].join("\n")

describe("what it reads", () => {
  test("a row, with the Thai left exactly as written", () => {
    expect(parseRoster(file(ROW))).toEqual([
      {
        department: "Accounting",
        position: "เสมียน 1",
        code: "10000038",
        firstName: "อรัญญา",
        lastName: "จันทร์หอม",
        level: "staff",
      },
    ])
  })

  test("the six ranks the client writes", () => {
    const rows = [
      ["Admin", "admin"],
      ["Manager", "manager"],
      ["Assistant Manager", "assistant_manager"],
      ["Supervisor", "supervisor"],
      ["Line Leader", "line_leader"],
      ["Staff", "staff"],
    ] as const
    const text = file(
      ...rows.map(
        ([label], i) => `D\tP${i}\t1000000${i}\tFirst\tLast\t${label}`,
      ),
    )
    expect(parseRoster(text).map((r) => r.level)).toEqual(
      rows.map(([, level]) => level),
    )
  })

  /**
   * The real export's fourth column is "Name " with a trailing space, and its
   * first byte is a byte order mark. Neither is worth failing over, and both
   * would otherwise hide a column that is present.
   */
  test("a trailing space in a header, and a byte order mark", () => {
    expect(parseRoster("﻿" + file(ROW))).toHaveLength(1)
  })

  test("blank lines, including the one at the end", () => {
    expect(parseRoster(file(ROW, "", ROW) + "\n")).toHaveLength(2)
  })
})

/**
 * The half-import is the failure worth preventing. A roster that loads 190 of
 * 193 rows looks like a success, and nobody counts until somebody is missing
 * from a screen months later.
 */
describe("what it refuses", () => {
  test("a rank it does not know, naming the line and the value", () => {
    expect(() =>
      parseRoster(file("D\tP\t10000038\tFirst\tLast\tTeam Lead")),
    ).toThrow(/Line 2.*Team Lead.*not a level/)
  })

  test("an employee code that is not eight digits", () => {
    expect(() => parseRoster(file("D\tP\t1234\tFirst\tLast\tStaff"))).toThrow(
      /Line 2.*not an eight digit/,
    )
    expect(() =>
      parseRoster(file("D\tP\tABCD1234\tFirst\tLast\tStaff")),
    ).toThrow(/not an eight digit/)
  })

  test("a renamed column, saying which columns it did find", () => {
    const renamed = HEADER.replace("Employee Code", "Emp. Code")
    expect(() => parseRoster([renamed, ROW].join("\n"))).toThrow(
      /no "Employee Code" column.*Emp\. Code/,
    )
  })

  test("an empty name, which would otherwise become a person with no name", () => {
    expect(() => parseRoster(file("D\tP\t10000038\t\tLast\tStaff"))).toThrow(
      /Line 2: the name is empty/,
    )
    expect(() =>
      parseRoster(file("\tP\t10000038\tFirst\tLast\tStaff")),
    ).toThrow(/the department is empty/)
  })

  /**
   * And the case that is not a refusal at all. One person in the real roster,
   * VAN QUANG TRAN, has their whole name in the Name column and an empty Last
   * Name. Requiring a surname rejected them, which is a validation deciding
   * how a name may be shaped rather than whether one is there.
   */
  test("no last name at all, which is a name and not a gap", () => {
    const [row] = parseRoster(
      file(
        "Production\tProduction Specialist\t00003329\tVAN QUANG TRAN\t\tStaff",
      ),
    )
    expect(row!.firstName).toBe("VAN QUANG TRAN")
    expect(row!.lastName).toBe("")
  })

  test("an empty file", () => {
    expect(() => parseRoster("")).toThrow(/empty/)
  })

  /**
   * The line number is the whole point of these messages: it has to count the
   * header, so the number matches what the spreadsheet shows.
   */
  test("the line number counts the header, as the spreadsheet does", () => {
    expect(() =>
      parseRoster(file(ROW, ROW, "D\tP\t10000038\tFirst\tLast\tNope")),
    ).toThrow(/Line 4/)
  })
})
