import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { randomUUID } from "node:crypto";

export type Appointment = {
  id: string; service_id: string; service_name: string; appointment_date: string;
  start_time: string; duration_minutes: number; customer_name: string;
  customer_email: string; customer_phone: string; notes: string;
  status: string; created_at: string;
};
const directory = () => process.env.LOCAL_BOOKINGS_DIR || path.join(process.cwd(), ".local-data");
export async function readBookings(): Promise<Appointment[]> {
  try { return JSON.parse(await readFile(path.join(directory(), "appointments.json"), "utf8")); }
  catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return []; throw error; }
}
async function mutate<T>(fn: (rows: Appointment[]) => T): Promise<T> {
  await mkdir(directory(), { recursive: true, mode: 0o700 });
  const lock = path.join(directory(), "booking.lock");
  let acquired = false;
  for (let attempt=0; attempt<100; attempt++) {
    try { await mkdir(lock); acquired=true; break; }
    catch (error) { if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error; await new Promise(resolve=>setTimeout(resolve,50)); }
  }
  if (!acquired) throw new Error("Booking storage is busy. Please try again.");
  const temporary = path.join(directory(), `${randomUUID()}.tmp`);
  try {
    const rows = await readBookings(); const result = fn(rows);
    await writeFile(temporary, JSON.stringify(rows), { mode: 0o600 });
    await rename(temporary, path.join(directory(), "appointments.json"));
    return result;
  } finally { await rm(temporary, { force:true }); await rm(lock, { recursive:true, force:true }); }
}
export const minutes = (time:string) => Number(time.slice(0,2))*60+Number(time.slice(3,5));
export function overlaps(rows: Appointment[], date:string, time:string, duration:number) {
  const start=minutes(time);
  return rows.some(a=>a.appointment_date===date && !["cancelled","no_show"].includes(a.status) && start < minutes(a.start_time)+a.duration_minutes && start+duration > minutes(a.start_time));
}
export async function saveBooking(input: Omit<Appointment,"id"|"created_at">) {
  return mutate(rows=>{
    if(overlaps(rows,input.appointment_date,input.start_time,input.duration_minutes)) throw new Error("That time was just taken. Please choose another.");
    const row={...input,id:randomUUID(),created_at:new Date().toISOString()}; rows.push(row); return row;
  });
}
export async function updateBooking(id:string,status:string) {
  return mutate(rows=>{const row=rows.find(a=>a.id===id); if(!row)throw new Error("Appointment not found.");
    if(!["cancelled","no_show"].includes(status) && overlaps(rows.filter(a=>a.id!==id),row.appointment_date,row.start_time,row.duration_minutes)) throw new Error("This time is now occupied.");
    row.status=status;
  });
}
