import { RRule } from "rrule";
import { prisma } from "./db";

type OrgSettings = {
  autoAssignEnabled?: boolean;
  autoAssignWindowHours?: number;
  autoAssignStrategy?: "most_recent" | "prompt_user" | "manual_only";
  defaultLocationId?: string | null;
};

type AssignmentResult = {
  assigned: boolean;
  collectionDayId?: string;
  collectionDate?: Date;
  reason: string;
};

/**
 * Find the most recent past occurrence of a CollectionDay before `asOf`.
 * Uses rrule for recurring events, or the fixed `date` for one-offs.
 */
function getMostRecentOccurrence(
  collectionDay: {
    isRecurring: boolean;
    rrule: string | null;
    dayOfWeek: number | null;
    date: Date | null;
  },
  asOf: Date,
  windowMs: number
): Date | null {
  const windowStart = new Date(asOf.getTime() - windowMs);

  if (!collectionDay.isRecurring && collectionDay.date) {
    const d = collectionDay.date;
    if (d >= windowStart && d <= asOf) return d;
    return null;
  }

  if (collectionDay.rrule) {
    try {
      const rule = RRule.fromString(collectionDay.rrule);
      const occurrences = rule.between(windowStart, asOf, true);
      return occurrences.length > 0 ? occurrences[occurrences.length - 1] : null;
    } catch {
      // Fall through to dayOfWeek
    }
  }

  if (collectionDay.dayOfWeek !== null) {
    const target = collectionDay.dayOfWeek;
    const d = new Date(asOf);
    const diff = (d.getDay() - target + 7) % 7;
    if (diff === 0 && d.getHours() >= 12) {
      // If today IS the target day and it's afternoon, use today
    } else if (diff === 0) {
      d.setDate(d.getDate() - 7);
    } else {
      d.setDate(d.getDate() - diff);
    }
    d.setHours(0, 0, 0, 0);

    if (d >= windowStart && d <= asOf) return d;
    return null;
  }

  return null;
}

/**
 * Get the previous Sunday relative to a date.
 * Backward compatible with the original getPreviousSunday() in ocr.ts.
 */
export function getPreviousSunday(date: Date = new Date()): Date {
  const d = new Date(date);
  const day = d.getDay();
  if (day !== 0) {
    d.setDate(d.getDate() - day);
  }
  d.setHours(0, 0, 0, 0);
  return d;
}

export type ImportContext = {
  organizationId?: string | null;
  locationId?: string | null;
  collectionDayId?: string | null;
  importTimestamp?: Date;
};

/**
 * Auto-assign a card to an event based on org settings and collection day templates.
 */
export async function autoAssignToEvent(
  cardId: string,
  context: ImportContext
): Promise<AssignmentResult> {
  if (context.collectionDayId) {
    const cd = await prisma.collectionDay.findUnique({
      where: { id: context.collectionDayId },
    });
    if (cd) {
      const collectionDate = context.importTimestamp
        ? getPreviousSunday(context.importTimestamp)
        : getPreviousSunday();

      await prisma.responseCard.update({
        where: { id: cardId },
        data: {
          collectionDayId: cd.id,
          collectionDate,
          locationId: cd.locationId,
          organizationId: context.organizationId || undefined,
        },
      });
      return {
        assigned: true,
        collectionDayId: cd.id,
        collectionDate,
        reason: "Manual pre-assignment",
      };
    }
  }

  if (!context.organizationId) {
    return { assigned: false, reason: "No organization context" };
  }

  const org = await prisma.organization.findUnique({
    where: { id: context.organizationId },
    select: { settings: true },
  });

  const settings = (org?.settings as OrgSettings) || {};

  if (settings.autoAssignStrategy === "manual_only") {
    return { assigned: false, reason: "Auto-assignment disabled (manual_only)" };
  }

  if (settings.autoAssignEnabled === false) {
    return { assigned: false, reason: "Auto-assignment disabled" };
  }

  const windowHours = settings.autoAssignWindowHours ?? 48;
  const windowMs = windowHours * 60 * 60 * 1000;
  const asOf = context.importTimestamp || new Date();

  const locationId = context.locationId || settings.defaultLocationId;

  const collectionDays = await prisma.collectionDay.findMany({
    where: {
      isActive: true,
      location: { organizationId: context.organizationId },
      ...(locationId && { locationId }),
    },
  });

  if (collectionDays.length === 0) {
    return { assigned: false, reason: "No active collection days found" };
  }

  const matches: { id: string; date: Date; locationId: string }[] = [];

  for (const cd of collectionDays) {
    const occurrence = getMostRecentOccurrence(cd, asOf, windowMs);
    if (occurrence) {
      matches.push({ id: cd.id, date: occurrence, locationId: cd.locationId });
    }
  }

  if (matches.length === 0) {
    return { assigned: false, reason: `No events within ${windowHours}h window` };
  }

  if (matches.length === 1 || settings.autoAssignStrategy === "most_recent") {
    matches.sort((a, b) => b.date.getTime() - a.date.getTime());
    const best = matches[0];

    await prisma.responseCard.update({
      where: { id: cardId },
      data: {
        collectionDayId: best.id,
        collectionDate: best.date,
        locationId: best.locationId,
        organizationId: context.organizationId,
      },
    });

    return {
      assigned: true,
      collectionDayId: best.id,
      collectionDate: best.date,
      reason: matches.length === 1 ? "Single match" : "Most recent match",
    };
  }

  // Multiple matches and strategy is prompt_user
  await prisma.responseCard.update({
    where: { id: cardId },
    data: {
      organizationId: context.organizationId,
      locationId: locationId || undefined,
    },
  });

  return {
    assigned: false,
    reason: `${matches.length} matching events - awaiting user selection`,
  };
}
