import os from "node:os";
import path from "node:path";
import fs from "node:fs";

// Env + a throwaway DB must be in place before ../db is first imported (it
// creates the schema on import), so this runs ahead of the other imports.
const dbDir = fs.mkdtempSync(path.join(os.tmpdir(), "eatme-shift-test-"));
process.env.DB_PATH = path.join(dbDir, "test.db");
process.env.GOOGLE_CLIENT_ID = "test-client-id";
process.env.ADMIN_EMAILS = "admin@example.com";
process.env.JWT_SECRET = "test-secret";

import { db } from "../db";
import {
  assignEmployeeToSlot,
  cancelMySignup,
  createSlot,
  ensureSlotsForPeriod,
  getAvailableSlotsForEmployee,
  getMySlotsForEmployee,
  listAdminSlotsForPeriod,
  removeEmployeeFromSlot,
  signupForSlot,
  updateSlot,
} from "./shiftPlanning";

const PERIOD = "2026-09";

function makeEmployee(email: string): number {
  const result = db.prepare("INSERT INTO employees (email, name) VALUES (?, ?)").run(email, email);
  return Number(result.lastInsertRowid);
}

/** Sun-Thu = 1 generated slot/day, Fri-Sat = 2 (mirrors the template in shiftPlanning.ts). */
function expectedTemplateCount(period: string): number {
  const [year, month] = period.split("-").map(Number);
  const days = new Date(year, month, 0).getDate();
  let count = 0;
  for (let day = 1; day <= days; day++) {
    const dow = new Date(year, month - 1, day).getDay();
    count += dow === 5 || dow === 6 ? 2 : 1;
  }
  return count;
}

describe("shift planning", () => {
  test("generates the weekly template idempotently", () => {
    ensureSlotsForPeriod(PERIOD);
    const first = listAdminSlotsForPeriod(PERIOD);
    expect(first.length).toBe(expectedTemplateCount(PERIOD));

    ensureSlotsForPeriod(PERIOD);
    const second = listAdminSlotsForPeriod(PERIOD);
    expect(second.length).toBe(first.length);
  });

  test("employee sign-up fills capacity; duplicates and oversubscription are rejected; cancelling frees the slot", () => {
    const alice = makeEmployee("alice@example.com");
    const bob = makeEmployee("bob@example.com");

    const single = listAdminSlotsForPeriod(PERIOD).find((s) => s.capacity === 1)!;

    signupForSlot(alice, single.id);
    expect(getMySlotsForEmployee(alice, PERIOD).some((s) => s.slot_id === single.id)).toBe(true);
    expect(getAvailableSlotsForEmployee(alice, PERIOD).some((s) => s.slot_id === single.id)).toBe(false);

    expect(() => signupForSlot(alice, single.id)).toThrow("Na této směně už jsi přihlášen.");
    expect(() => signupForSlot(bob, single.id)).toThrow("Směna je už obsazená.");

    expect(listAdminSlotsForPeriod(PERIOD).find((s) => s.id === single.id)!.status).toBe("full");

    cancelMySignup(alice, single.id, "alice@example.com");

    const afterCancel = listAdminSlotsForPeriod(PERIOD).find((s) => s.id === single.id)!;
    expect(afterCancel.status).toBe("open");
    expect(getAvailableSlotsForEmployee(alice, PERIOD).some((s) => s.slot_id === single.id)).toBe(true);
  });

  test("rejects signing up for two shifts that overlap in time", () => {
    const carol = makeEmployee("carol@example.com");

    // The Fri/Sat template deliberately overlaps: 18:00-02:00 and 20:00-00:00.
    const byDate = new Map<string, ReturnType<typeof listAdminSlotsForPeriod>>();
    for (const slot of listAdminSlotsForPeriod(PERIOD)) {
      byDate.set(slot.date, [...(byDate.get(slot.date) ?? []), slot]);
    }
    const [firstSlot, secondSlot] = [...byDate.values()].find((group) => group.length === 2)!;

    signupForSlot(carol, firstSlot.id);
    expect(() => signupForSlot(carol, secondSlot.id)).toThrow(/překrývá/);
  });

  test("admin can create a custom slot, assign/remove employees, and capacity can't drop below occupancy", () => {
    const dave = makeEmployee("dave@example.com");

    const slotId = createSlot({
      date: "2026-09-01",
      start_time: "10:00",
      end_time: "14:00",
      capacity: 2,
      note: "brunch",
    });
    expect(() =>
      createSlot({ date: "2026-09-01", start_time: "10:00", end_time: "14:00", capacity: 2 })
    ).toThrow("Stejná směna už existuje.");

    assignEmployeeToSlot(slotId, dave);
    let slot = listAdminSlotsForPeriod(PERIOD).find((s) => s.id === slotId)!;
    expect(slot.employees.map((e) => e.employee_id)).toContain(dave);
    expect(slot.occupied).toBe(1);

    expect(() =>
      updateSlot(slotId, { date: slot.date, start_time: slot.start_time, end_time: slot.end_time, capacity: 0 })
    ).toThrow("Kapacitu nelze snížit pod počet již přihlášených zaměstnanců.");

    removeEmployeeFromSlot(slotId, dave, "admin@example.com");
    slot = listAdminSlotsForPeriod(PERIOD).find((s) => s.id === slotId)!;
    expect(slot.occupied).toBe(0);
    expect(slot.status).toBe("open");
  });
});
