import { Staff, StaffCreateInput, StaffUpdateInput } from "@repo/db/types";
import { prisma as db } from "@repo/db/client";

export interface IStorage {
  getStaff(id: number): Promise<Staff | undefined>;
  getAllStaff(): Promise<Staff[]>;
  createStaff(staff: StaffCreateInput): Promise<Staff>;
  updateStaff(
    id: number,
    updates: StaffUpdateInput,
  ): Promise<Staff | undefined>;
  deleteStaff(id: number): Promise<boolean>;
  countAppointmentsByStaffId(staffId: number): Promise<number>;
  countClaimsByStaffId(staffId: number): Promise<number>;
}

export const staffStorage: IStorage = {
  // Staff methods
  async getStaff(id: number): Promise<Staff | undefined> {
    const staff = await db.staff.findUnique({ where: { id } });
    return staff ?? undefined;
  },

  async getAllStaff(): Promise<Staff[]> {
    return db.staff.findMany({
      orderBy: { displayOrder: "asc" },
    });
  },

  async createStaff(staff: StaffCreateInput): Promise<Staff> {
    const max = await db.staff.aggregate({
      where: {
        userId: staff.userId,
        displayOrder: { gt: 0 },
      },
      _max: { displayOrder: true },
    });

    return db.staff.create({
      data: {
        ...staff,
        displayOrder: (max._max.displayOrder ?? 0) + 1,
      },
    });
  },
  
  async updateStaff(
    id: number,
    updates: StaffUpdateInput,
  ): Promise<Staff | undefined> {
    return db.$transaction(async (tx: any) => {
      const staff = await tx.staff.findUnique({ where: { id } });
      if (!staff) return undefined;

      const { userId, displayOrder: oldOrder } = staff;
      const newOrder = updates.displayOrder;

      if (newOrder === undefined || newOrder === oldOrder) {
        return tx.staff.update({
          where: { id },
          data: updates,
        });
      }

      const totalStaff = await tx.staff.count({ where: { userId } });

      if (newOrder < 1 || newOrder > totalStaff) {
        throw new Error(`displayOrder must be between 1 and ${totalStaff}`);
      }

      const occupyingStaff = await tx.staff.findFirst({
        where: {
          userId,
          displayOrder: newOrder,
        },
      });

      // CASE 1: staff already had a slot → SWAP
      if (oldOrder && oldOrder > 0 && occupyingStaff) {
        await tx.staff.update({
          where: { id: occupyingStaff.id },
          data: { displayOrder: oldOrder },
        });
      }

      // CASE 2: first-time assignment (oldOrder = 0)
      if ((!oldOrder || oldOrder === 0) && occupyingStaff) {
        // find first free slot
        const usedOrders = await tx.staff.findMany({
          where: {
            userId,
            displayOrder: { gt: 0 },
          },
          select: { displayOrder: true },
          orderBy: { displayOrder: "asc" },
        });

        const usedSet = new Set(usedOrders.map((s: any) => s.displayOrder));
        let freeSlot = 1;
        while (usedSet.has(freeSlot)) freeSlot++;

        await tx.staff.update({
          where: { id: occupyingStaff.id },
          data: { displayOrder: freeSlot },
        });
      }

      return tx.staff.update({
        where: { id },
        data: {
          ...updates,
          displayOrder: newOrder,
        },
      });
    });
  },

  async deleteStaff(id: number): Promise<boolean> {
    return db.$transaction(async (tx: any) => {
      const staff = await tx.staff.findUnique({ where: { id } });
      if (!staff) return false;

      const { userId, displayOrder } = staff;

      await tx.staff.delete({ where: { id } });

      // Shift left to remove gap
      await tx.staff.updateMany({
        where: {
          userId,
          displayOrder: { gt: displayOrder },
        },
        data: {
          displayOrder: { decrement: 1 },
        },
      });

      return true;
    });
  },
  async countAppointmentsByStaffId(staffId: number): Promise<number> {
    return await db.appointment.count({ where: { staffId } });
  },

  async countClaimsByStaffId(staffId: number): Promise<number> {
    return await db.claim.count({ where: { staffId } });
  },
};
