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

import { availableActions } from "@/lib/task-actions"
import { relationsOf, type RelationInput } from "@/lib/task-relations"

/**
 * Six ids that are visibly different when a test fails, and never equal by
 * accident. Uuids from the database would read as noise.
 */
const ANNA = "person-anna"
const BOON = "person-boon"
const CHAI = "person-chai"
const DARA = "person-dara"
const EK = "person-ek"

const MOULDING = "Moulding"
const ASSEMBLY = "Assembly"

/**
 * A daily duty in Moulding that names four different people, so every relation
 * a case wants is one field away and no relation arrives by coincidence.
 */
const duty = (over: Partial<RelationInput> = {}): RelationInput => ({
  viewerPersonId: EK,
  viewerTier: "staff",
  viewerDepartment: MOULDING,
  assigneePersonId: ANNA,
  checkerPersonId: BOON,
  verifierPersonId: CHAI,
  createdByPersonId: DARA,
  taskDepartment: MOULDING,
  occurrence: "daily",
  ...over,
})

const of = (over: Partial<RelationInput> = {}) =>
  relationsOf(duty(over)).relations

describe("relationsOf", () => {
  test("somebody named nowhere on the duty holds no relation to it", () => {
    expect(of()).toEqual([])
  })

  test("each name on the duty gives its own relation and no other", () => {
    expect(of({ viewerPersonId: ANNA })).toEqual(["assignee"])
    expect(of({ viewerPersonId: BOON })).toEqual(["checker"])
    expect(of({ viewerPersonId: CHAI })).toEqual(["verifier"])
  })

  test("a task with no holder yet makes nobody its assignee", () => {
    // Null on both sides: the position is vacant and the viewer has no person
    // row worth matching. Two nulls must not read as the same person.
    expect(of({ assigneePersonId: null, viewerPersonId: ANNA })).toEqual([])
    expect(of({ checkerPersonId: null, verifierPersonId: null })).toEqual([])
  })

  test("a manager of the department holds the relation, and only in it", () => {
    expect(of({ viewerTier: "manager" })).toEqual(["manager_of_dept"])
    expect(of({ viewerTier: "manager", viewerDepartment: ASSEMBLY })).toEqual(
      [],
    )
  })

  /**
   * The case that decides whether the page is safe: a manager elsewhere in the
   * factory gets nothing at all on somebody else's department's work, however
   * senior their title reads.
   */
  test("a manager of another department gets nothing", () => {
    expect(
      of({
        viewerTier: "manager",
        viewerDepartment: ASSEMBLY,
        taskDepartment: MOULDING,
      }),
    ).toEqual([])
  })

  test("a manager between positions has no department and no relation", () => {
    expect(of({ viewerTier: "manager", viewerDepartment: null })).toEqual([])
  })

  test("staff in the department are not managers of it", () => {
    expect(of({ viewerTier: "staff", viewerDepartment: MOULDING })).toEqual([])
  })

  test("an admin is an admin and not a manager of the department as well", () => {
    expect(of({ viewerTier: "admin" })).toEqual(["admin"])
    expect(of({ viewerTier: "admin", viewerDepartment: ASSEMBLY })).toEqual([
      "admin",
    ])
  })

  /**
   * `close` ends something nothing will reissue. A recurring duty issues a new
   * instance tomorrow, so closing today's says nothing, and the creator's remedy
   * is to deactivate the template instead.
   */
  test("only the creator of a one-time duty is a one-time creator", () => {
    expect(of({ viewerPersonId: DARA, occurrence: "once" })).toEqual([
      "one_time_creator",
    ])
    for (const occurrence of [
      "daily",
      "weekly",
      "monthly",
      "quarterly",
      "general",
    ] as const) {
      expect(of({ viewerPersonId: DARA, occurrence })).toEqual([])
    }
  })

  test("a one-time duty created by somebody else gives nothing", () => {
    expect(of({ viewerPersonId: EK, occurrence: "once" })).toEqual([])
  })

  test("isOneTime follows the occurrence and nothing else", () => {
    expect(relationsOf(duty({ occurrence: "once" })).isOneTime).toBe(true)
    expect(relationsOf(duty({ occurrence: "daily" })).isOneTime).toBe(false)
    expect(relationsOf(duty({ occurrence: "general" })).isOneTime).toBe(false)
  })

  /**
   * Several departments on the roster hold two people, so this is the ordinary
   * arrangement and not an edge: whoever does the work also approves it. Both
   * relations have to come back, because one of them alone gives the wrong
   * buttons at every step of the lifecycle.
   */
  test("somebody who checks their own work holds both relations", () => {
    expect(
      of({
        viewerPersonId: ANNA,
        assigneePersonId: ANNA,
        checkerPersonId: ANNA,
      }),
    ).toEqual(["assignee", "checker"])
  })

  test("a manager who checks their own department's work holds both", () => {
    expect(of({ viewerPersonId: BOON, viewerTier: "manager" })).toEqual([
      "checker",
      "manager_of_dept",
    ])
  })

  test("every relation at once, on one person", () => {
    expect(
      of({
        viewerPersonId: ANNA,
        viewerTier: "admin",
        assigneePersonId: ANNA,
        checkerPersonId: ANNA,
        verifierPersonId: ANNA,
        createdByPersonId: ANNA,
        occurrence: "once",
      }),
    ).toEqual(["assignee", "checker", "verifier", "one_time_creator", "admin"])
  })
})

/**
 * The join this module exists for. `availableActions` was written and tested
 * before anything computed a relation; these two cases are the proof that what
 * comes out of here is what it takes in.
 */
describe("relationsOf feeding availableActions", () => {
  const actions = (input: RelationInput, status: "todo" | "finished") => {
    const { relations, isOneTime } = relationsOf(input)
    return availableActions(status, relations, { oneTime: isOneTime }).map(
      (a) => a.id,
    )
  }

  test("a person who is their own checker starts the work and then approves it", () => {
    const both = duty({
      viewerPersonId: ANNA,
      assigneePersonId: ANNA,
      checkerPersonId: ANNA,
    })
    expect(actions(both, "todo")).toEqual(["start", "report_issue"])
    expect(actions(both, "finished")).toEqual(["approve", "send_back"])
  })

  /** One verb. `actions_by_relation` gives the creator `close` and nothing else. */
  test("the creator of a one-time duty closes it, and cannot send it back", () => {
    const once = duty({ viewerPersonId: DARA, occurrence: "once" })
    expect(actions(once, "finished")).toEqual(["close"])
  })

  test("the creator of a recurring duty is offered nothing to close", () => {
    // Tomorrow brings another instance. Closing today's would say nothing about
    // it, and the remedy for a duty that should stop is to deactivate it.
    const daily = duty({ viewerPersonId: DARA, occurrence: "daily" })
    expect(actions(daily, "finished")).toEqual([])
  })

  test("a manager of another department is offered nothing to press", () => {
    const elsewhere = duty({
      viewerTier: "manager",
      viewerDepartment: ASSEMBLY,
    })
    expect(actions(elsewhere, "todo")).toEqual([])
    expect(actions(elsewhere, "finished")).toEqual([])
  })
})
