import type { Doc, Id } from "../_generated/dataModel";
import type { MutationCtx, QueryCtx } from "../_generated/server";
import { addDaysKey, eachNightKey, nightsBetweenKeys, todayKey } from "./days";
import type { ActorLite } from "./access";

/** Date the apartment is actually free again: an early check-out wins over the planned date. */
export function effectiveCheckOut(r: { checkOut: string; actualCheckOut?: string | null }): string {
  return r.actualCheckOut && r.actualCheckOut < r.checkOut ? r.actualCheckOut : r.checkOut;
}

export interface InventoryEventInput {
  apartmentId: Id<"apartments">;
  action: string;
  startDate: string;
  endDate: string;
  previousState?: string | null;
  newState?: string | null;
  reason?: string | null;
  source?: string | null;
  reservationId?: Id<"reservations"> | null;
  blockId?: Id<"apartmentBlocks"> | null;
  estimatedValue?: number | null;
  at?: number;
}

/** Append an immutable inventory ledger row. */
export async function inventoryEvent(ctx: MutationCtx, actor: ActorLite, input: InventoryEventInput) {
  return ctx.db.insert("inventoryEvents", {
    apartmentId: input.apartmentId,
    action: input.action,
    startDate: input.startDate,
    endDate: input.endDate,
    nights: Math.max(0, nightsBetweenKeys(input.startDate, input.endDate)),
    previousState: input.previousState ?? undefined,
    newState: input.newState ?? undefined,
    reason: input.reason ?? undefined,
    source: input.source ?? undefined,
    reservationId: input.reservationId ?? undefined,
    blockId: input.blockId ?? undefined,
    userId: actor?.id,
    userName: actor?.fullName ?? "System",
    estimatedValue: input.estimatedValue ?? undefined,
    at: input.at ?? Date.now(),
  });
}

/**
 * Hand a date range back to inventory according to a release policy.
 * IMMEDIATE → RELEASED event; AFTER_CLEANING → HOLD that dissolves when the
 * apartment is marked clean; APPROVAL → HOLD flagged for the admin; KEEP →
 * HOLD until manually released.
 */
export async function releaseInventory(ctx: MutationCtx, actor: ActorLite, opts: { apartmentId: Id<"apartments">; reservationId: Id<"reservations">; start: string; end: string; policy: "IMMEDIATE" | "AFTER_CLEANING" | "APPROVAL" | "KEEP"; reason: string; nightlyPrice: number; trigger: string }) {
  const nights = nightsBetweenKeys(opts.start, opts.end);
  if (nights <= 0) return { nights: 0, holdId: null as Id<"apartmentBlocks"> | null };
  const value = nights * opts.nightlyPrice;
  if (opts.policy === "IMMEDIATE") {
    await inventoryEvent(ctx, actor, { apartmentId: opts.apartmentId, action: "RELEASED", startDate: opts.start, endDate: opts.end, previousState: opts.trigger, newState: "AVAILABLE", reason: opts.reason, reservationId: opts.reservationId, estimatedValue: value });
    return { nights, holdId: null };
  }
  const holdId = await ctx.db.insert("apartmentBlocks", {
    apartmentId: opts.apartmentId,
    startDate: opts.start,
    endDate: opts.end,
    type: "HOLD",
    source: "HOLD",
    reason: opts.policy === "AFTER_CLEANING" ? "Released after cleaning" : opts.policy === "APPROVAL" ? "Awaiting admin approval" : "Kept blocked",
    reservationId: opts.reservationId,
    pendingApproval: opts.policy === "APPROVAL",
    releaseOnCleaning: opts.policy === "AFTER_CLEANING",
    createdById: actor?.id,
    notes: opts.reason,
    createdAt: Date.now(),
  });
  await inventoryEvent(ctx, actor, { apartmentId: opts.apartmentId, action: "HOLD_CREATED", startDate: opts.start, endDate: opts.end, previousState: opts.trigger, newState: `HOLD:${opts.policy}`, reason: opts.reason, reservationId: opts.reservationId, blockId: holdId, estimatedValue: value });
  return { nights, holdId };
}

/** Dissolve a hold and record the release. */
export async function dissolveHold(ctx: MutationCtx, actor: ActorLite, holdId: Id<"apartmentBlocks">, reason: string) {
  const hold = await ctx.db.get(holdId);
  if (!hold || hold.type !== "HOLD") return null;
  const apt = await ctx.db.get(hold.apartmentId);
  await ctx.db.delete(holdId);
  await inventoryEvent(ctx, actor, { apartmentId: hold.apartmentId, action: "HOLD_RELEASED", startDate: hold.startDate, endDate: hold.endDate, previousState: "HOLD", newState: "AVAILABLE", reason, reservationId: hold.reservationId, estimatedValue: nightsBetweenKeys(hold.startDate, hold.endDate) * (apt?.basePrice ?? 0) });
  return hold;
}

/** Release every hold on an apartment that was waiting for cleaning. */
export async function releaseCleaningHolds(ctx: MutationCtx, actor: ActorLite, apartmentId: Id<"apartments">) {
  const holds = (await ctx.db.query("apartmentBlocks").withIndex("by_apartment_start", (q) => q.eq("apartmentId", apartmentId)).collect()).filter((b) => b.type === "HOLD" && b.releaseOnCleaning);
  for (const h of holds) await dissolveHold(ctx, actor, h._id, "Cleaning completed");
  return holds.length;
}

/** Nights of a new stay that re-sell inventory released in the last 90 days. */
export async function attributeRecovery(ctx: QueryCtx | MutationCtx, apartmentId: Id<"apartments">, checkIn: string, checkOut: string) {
  const since = Date.now() - 90 * 86_400_000;
  const events = (await ctx.db.query("inventoryEvents").withIndex("by_apartment_at", (q) => q.eq("apartmentId", apartmentId).gte("at", since)).collect()).filter((e) => (e.action === "RELEASED" || e.action === "HOLD_RELEASED") && e.startDate < checkOut && e.endDate > checkIn).sort((a, b) => b.at - a.at);
  if (!events.length) return { nights: 0, fromReservationId: null as Id<"reservations"> | null };
  const set = new Set<string>();
  for (const e of events) for (const k of eachNightKey(e.startDate > checkIn ? e.startDate : checkIn, e.endDate < checkOut ? e.endDate : checkOut)) set.add(k);
  return { nights: set.size, fromReservationId: events[0].reservationId ?? null };
}

/** Refresh derived apartment status from live reservations (idempotent). Manual MAINTENANCE / BLOCKED states win. */
export async function syncApartmentStatus(ctx: MutationCtx, apartmentId: Id<"apartments">, tz: string) {
  const apt = await ctx.db.get(apartmentId);
  if (!apt || apt.status === "MAINTENANCE" || apt.status === "BLOCKED") return;
  const today = todayKey(tz);
  const rs = await ctx.db.query("reservations").withIndex("by_apartment_checkIn", (q) => q.eq("apartmentId", apartmentId).gte("checkIn", addDaysKey(today, -120)).lte("checkIn", today)).collect();
  const active = rs.some((r) => r.status === "CHECKED_IN");
  const reservedToday = rs.some((r) => r.status === "CONFIRMED" && r.checkIn === today);
  const cleaning = (await ctx.db.query("cleaningTasks").withIndex("by_apartment_status", (q) => q.eq("apartmentId", apartmentId).eq("status", "NEEDS_CLEANING")).first()) ?? (await ctx.db.query("cleaningTasks").withIndex("by_apartment_status", (q) => q.eq("apartmentId", apartmentId).eq("status", "IN_PROGRESS")).first());
  const status = active ? "OCCUPIED" : cleaning ? "CLEANING" : reservedToday ? "RESERVED" : "AVAILABLE";
  if (status !== apt.status) await ctx.db.patch(apartmentId, { status, updatedAt: Date.now() });
  if (!cleaning && (apt.cleaningStatus === "READY" || apt.cleaningStatus === "CLEAN")) await releaseCleaningHolds(ctx, null, apartmentId);
}

export type ReservationDoc = Doc<"reservations">;
