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

import { parseHolidays, yearsIn } from "@/lib/holiday-csv"

const ok = (text: string) => {
  const result = parseHolidays(text)
  if (!result.ok) throw new Error(`expected a parse, got: ${result.problem}`)
  return result.holidays
}

describe("what it reads", () => {
  test("a date and a name", () => {
    expect(ok("2026-01-01,New Year's Day")).toEqual([
      { date: "2026-01-01", name: "New Year's Day" },
    ])
  })

  test("a header, or none", () => {
    const withHeader = "date,name\n2026-01-01,New Year's Day"
    const without = "2026-01-01,New Year's Day"
    expect(ok(withHeader)).toEqual(ok(without))
  })

  /**
   * Quoted, as every spreadsheet writes it. The first version of this parser
   * took everything after the first comma as the name, and the client's own
   * export has four columns: that would have imported a tradition and a
   * description as part of the day's name.
   */
  test("a name with a comma in it, quoted", () => {
    expect(ok('2026-04-13,"Songkran, first day"')).toEqual([
      { date: "2026-04-13", name: "Songkran, first day" },
    ])
  })

  /**
   * The real file has Date, name, Tradition and Description. Anything past the
   * second column is somebody else's business.
   */
  test("extra columns are ignored, not refused", () => {
    expect(
      ok('2026-05-31,Visakha Bucha,Theravada,"Birth, awakening and death."'),
    ).toEqual([{ date: "2026-05-31", name: "Visakha Bucha" }])
  })

  test("a doubled quote inside a quoted field is one quote", () => {
    expect(ok('2026-01-01,"The ""big"" day")')[0]!.name).toBe('The "big" day)')
  })

  test("blank lines and a byte order mark", () => {
    expect(ok("﻿2026-01-01,A\n\n2026-01-02,B\n")).toHaveLength(2)
  })

  test("the Thai name is kept exactly", () => {
    expect(ok("2026-04-13,วันสงกรานต์")[0]!.name).toBe("วันสงกรานต์")
  })
})

/**
 * Every one of these moves a real due date. A duty landing on a day the factory
 * is shut is a duty nobody does, so this is the half worth being strict about.
 */
describe("what it refuses, and where", () => {
  test("a date that is not one, naming the line", () => {
    const bad = parseHolidays("2026-01-01,A\n13/04/2026,B")
    expect(bad.ok).toBe(false)
    if (bad.ok) return
    expect(bad.line).toBe(2)
    expect(bad.problem).toMatch(/13\/04\/2026/)
  })

  test("a day that does not exist", () => {
    expect(parseHolidays("2026-02-30,A").ok).toBe(false)
  })

  test("a line with no name", () => {
    const bad = parseHolidays("2026-01-01,")
    expect(bad.ok).toBe(false)
    if (bad.ok) return
    expect(bad.problem).toMatch(/no name/)
  })

  test("a line with a date and nothing else", () => {
    const bad = parseHolidays("2026-01-01")
    expect(bad.ok).toBe(false)
    if (bad.ok) return
    expect(bad.problem).toMatch(/one field/)
  })

  /**
   * The date is the primary key, so a repeat would let one row win silently and
   * which one would depend on the order of a file.
   */
  test("the same day twice, naming both lines", () => {
    const bad = parseHolidays("2026-01-01,A\n2026-01-02,B\n2026-01-01,C")
    expect(bad.ok).toBe(false)
    if (bad.ok) return
    expect(bad.line).toBe(3)
    expect(bad.problem).toMatch(/already on line 1/)
  })

  test("an empty file, and a file that is only a header", () => {
    expect(parseHolidays("").ok).toBe(false)
    expect(parseHolidays("date,name").ok).toBe(false)
  })
})

describe("yearsIn", () => {
  test("names the years a file covers, in order, once each", () => {
    expect(
      yearsIn([
        { date: "2027-01-01", name: "A" },
        { date: "2026-12-31", name: "B" },
        { date: "2026-01-01", name: "C" },
      ]),
    ).toEqual([2026, 2027])
  })
})
