import { VacationStatus } from "@capakraken/db";
import { createNotification } from "./create-notification.js";

type DbClient = {
  vacation: {
    findUnique: (args: {
      where: { id: string };
      select: {
        id: true;
        resourceId: true;
        startDate: true;
        endDate: true;
        resource: { select: { chapter: true; displayName: true } };
      };
    }) => Promise<{
      id: string;
      resourceId: string;
      startDate: Date;
      endDate: Date;
      resource: { chapter: string | null; displayName: string } | null;
    } | null>;
    findMany: (args: {
      where: {
        resource: { chapter: string };
        resourceId: { not: string };
        status: { in: string[] };
        startDate: { lte: Date };
        endDate: { gte: Date };
      };
      select: {
        id: true;
        resourceId: true;
        startDate: true;
        endDate: true;
        resource: { select: { displayName: true } };
      };
    }) => Promise<
      Array<{
        id: string;
        resourceId: string;
        startDate: Date;
        endDate: Date;
        resource: { displayName: string } | null;
      }>
    >;
  };
  resource: {
    count: (args: {
      where: { chapter: string; isActive: true };
    }) => Promise<number>;
  };
  notification: {
    create: (args: {
      data: {
        userId: string;
        type: string;
        category: string;
        priority: string;
        title: string;
        body: string;
        entityId: string;
        entityType: string;
        link: string;
        channel: string;
      };
    }) => Promise<{ id: string; userId: string }>;
  };
};

/** Threshold: warn when more than 50% of a chapter is absent on any single day */
const OVERLAP_THRESHOLD = 0.5;

export interface VacationConflictResult {
  warnings: string[];
}

/**
 * Check if approving a vacation would cause >50% of a chapter to be absent
 * on any single day within the vacation period.
 *
 * Returns a list of warning strings (empty if no conflicts).
 * Does NOT block the approval — warnings are advisory only.
 */
export async function checkVacationConflicts(
  db: DbClient,
  vacationId: string,
  approverUserId?: string,
): Promise<VacationConflictResult> {
  const warnings: string[] = [];

  const vacation = await db.vacation.findUnique({
    where: { id: vacationId },
    select: {
      id: true,
      resourceId: true,
      startDate: true,
      endDate: true,
      resource: { select: { chapter: true, displayName: true } },
    },
  });

  if (!vacation?.resource?.chapter) {
    return { warnings };
  }

  const chapter = vacation.resource.chapter;

  // Count active resources in the same chapter
  const totalInChapter = await db.resource.count({
    where: { chapter, isActive: true },
  });

  if (totalInChapter <= 1) {
    return { warnings };
  }

  // Find overlapping approved/pending vacations from other resources in the same chapter
  const overlapping = await db.vacation.findMany({
    where: {
      resource: { chapter },
      resourceId: { not: vacation.resourceId },
      status: { in: [VacationStatus.APPROVED, VacationStatus.PENDING] },
      startDate: { lte: vacation.endDate },
      endDate: { gte: vacation.startDate },
    },
    select: {
      id: true,
      resourceId: true,
      startDate: true,
      endDate: true,
      resource: { select: { displayName: true } },
    },
  });

  if (overlapping.length === 0) {
    return { warnings };
  }

  // Check each day of the vacation to find the worst overlap
  const start = new Date(vacation.startDate);
  start.setUTCHours(0, 0, 0, 0);
  const end = new Date(vacation.endDate);
  end.setUTCHours(0, 0, 0, 0);

  let worstDay: string | null = null;
  let worstCount = 0;

  const cursor = new Date(start);
  while (cursor <= end) {
    // Skip weekends
    const dow = cursor.getUTCDay();
    if (dow === 0 || dow === 6) {
      cursor.setUTCDate(cursor.getUTCDate() + 1);
      continue;
    }

    // Count unique resources absent on this day (excluding the current resource)
    const absentResourceIds = new Set<string>();
    for (const ov of overlapping) {
      const ovStart = new Date(ov.startDate);
      ovStart.setUTCHours(0, 0, 0, 0);
      const ovEnd = new Date(ov.endDate);
      ovEnd.setUTCHours(0, 0, 0, 0);
      if (cursor >= ovStart && cursor <= ovEnd) {
        absentResourceIds.add(ov.resourceId);
      }
    }

    // +1 because the resource being approved would also be absent
    const totalAbsent = absentResourceIds.size + 1;
    if (totalAbsent > worstCount) {
      worstCount = totalAbsent;
      worstDay = cursor.toISOString().slice(0, 10);
    }

    cursor.setUTCDate(cursor.getUTCDate() + 1);
  }

  if (worstCount > 0 && worstCount / totalInChapter > OVERLAP_THRESHOLD) {
    const pct = Math.round((worstCount / totalInChapter) * 100);
    const absentNames = overlapping
      .map((ov) => ov.resource?.displayName ?? "Unknown")
      .slice(0, 5);
    const nameList = absentNames.join(", ");
    const suffix = overlapping.length > 5 ? ` and ${overlapping.length - 5} more` : "";

    const warning = `High absence in chapter "${chapter}" on ${worstDay}: ${worstCount}/${totalInChapter} resources (${pct}%) would be absent. Also off: ${nameList}${suffix}`;
    warnings.push(warning);

    // Create a notification for the approver if provided
    if (approverUserId) {
      await createNotification({
        db,
        userId: approverUserId,
        type: "VACATION_CONFLICT_WARNING",
        category: "NOTIFICATION",
        priority: "HIGH",
        title: `Vacation conflict warning: ${vacation.resource.displayName}`,
        body: warning,
        entityId: vacationId,
        entityType: "vacation",
        link: "/vacations",
        channel: "in_app",
      });
    }
  }

  return { warnings };
}

/**
 * Check conflicts for multiple vacations at once (used by batchApprove).
 * Returns a map of vacationId -> warnings.
 */
export async function checkBatchVacationConflicts(
  db: DbClient,
  vacationIds: string[],
  approverUserId?: string,
): Promise<Map<string, string[]>> {
  const results = new Map<string, string[]>();

  for (const id of vacationIds) {
    const result = await checkVacationConflicts(db, id, approverUserId);
    if (result.warnings.length > 0) {
      results.set(id, result.warnings);
    }
  }

  return results;
}
