import { v } from "convex/values";
import { mutation, query, type MutationCtx, type QueryCtx } from "./_generated/server";
import type { Id } from "./_generated/dataModel";
import { assertPermission, actorLite, AppError } from "./lib/access";
import { audit } from "./lib/audit";
import { getSettings } from "./lib/settings";
import { findConflicts } from "./lib/availability";
import { screenGuest } from "./lib/screening";
import { computePricing } from "../src/lib/pricing";
import { parseKey } from "./lib/days";

/**
 * Front-desk helpers: live guest screening, one-apartment availability
 * checks, short booking holds (so two workers never race for the same
 * apartment) and the share log.
 */
const DAY = /^\d{4}-\d{2}-\d{2}$/;
/** A hold lives this long without a heartbeat from the sheet that opened it. */
export const HOLD_TTL_MS = 150_000;

export async function liveHoldsFor(ctx: QueryCtx | MutationCtx, apartmentId: Id<"apartments">, checkIn: string, checkOut: string, now = Date.now()) {
  const rows = await ctx.db.query("bookingHolds").withIndex("by_apartment", (q) => q.eq("apartmentId", apartmentId)).collect();
  return rows.filter((h) => h.expiresAt > now && h.checkIn < checkOut && h.checkOut > checkIn);
}

export const screen = query({
  args: { customerId: v.optional(v.id("customers")), phone: v.optional(v.string()), idNumber: v.optional(v.string()), email: v.optional(v.string()) },
  returns: v.any(),
  handler: async (ctx, input) => {
    await assertPermission(ctx, "customers.view", "reservations.create");
    if (!input.customerId && !(input.phone && input.phone.replace(/[^\d]/g, "").length >= 8) && !(input.idNumber && input.idNumber.trim().length >= 4) && !input.email?.includes("@")) return null;
    return screenGuest(ctx, input);
  },
});

export const check = query({
  args: { apartmentId: v.id("apartments"), checkIn: v.string(), checkOut: v.string() },
  returns: v.any(),
  handler: async (ctx, { apartmentId, checkIn, checkOut }) => {
    const actor = await assertPermission(ctx, "reservations.create", "reservations.view");
    if (!DAY.test(checkIn) || !DAY.test(checkOut) || checkOut <= checkIn) return null;
    const [settings, apartment, conflicts, holds] = await Promise.all([getSettings(ctx), ctx.db.get(apartmentId), findConflicts(ctx, apartmentId, checkIn, checkOut), liveHoldsFor(ctx, apartmentId, checkIn, checkOut)]);
    if (!apartment) return null;
    const pricing = computePricing({ basePrice: apartment.basePrice, weekendPrice: apartment.weekendPrice ?? null, weekendDays: settings.weekendDays, checkIn: parseKey(checkIn), checkOut: parseKey(checkOut) });
    return { available: conflicts.length === 0, conflicts: conflicts.map((c) => ({ kind: c.kind, label: c.label })), pricing: { nights: pricing.nights, subtotal: pricing.subtotal, nightlyPrice: pricing.nightlyPrice }, heldBy: holds.filter((h) => h.userId !== actor.id).map((h) => ({ userName: h.userName, expiresAt: h.expiresAt })) };
  },
});

/** Every hold that has not expired. The client filters again against its own clock. */
export const holds = query({
  args: {},
  returns: v.array(v.object({ id: v.id("bookingHolds"), apartmentId: v.id("apartments"), checkIn: v.string(), checkOut: v.string(), userId: v.id("users"), userName: v.string(), expiresAt: v.number() })),
  handler: async (ctx) => {
    await assertPermission(ctx, "reservations.view", "apartments.view");
    const now = Date.now();
    return (await ctx.db.query("bookingHolds").withIndex("by_expires", (q) => q.gt("expiresAt", now)).take(200)).map((h) => ({ id: h._id, apartmentId: h.apartmentId, checkIn: h.checkIn, checkOut: h.checkOut, userId: h.userId, userName: h.userName, expiresAt: h.expiresAt }));
  },
});

export const holdStart = mutation({
  args: { apartmentId: v.id("apartments"), checkIn: v.string(), checkOut: v.string() },
  returns: v.object({ id: v.union(v.id("bookingHolds"), v.null()), heldBy: v.union(v.string(), v.null()) }),
  handler: async (ctx, { apartmentId, checkIn, checkOut }) => {
    const user = await assertPermission(ctx, "reservations.create");
    if (!DAY.test(checkIn) || !DAY.test(checkOut) || checkOut <= checkIn) throw new AppError("Invalid dates", "VALIDATION");
    const now = Date.now();
    for (const h of await ctx.db.query("bookingHolds").withIndex("by_expires", (q) => q.lt("expiresAt", now)).take(50)) await ctx.db.delete(h._id);
    for (const h of await ctx.db.query("bookingHolds").withIndex("by_user", (q) => q.eq("userId", user.id)).collect()) await ctx.db.delete(h._id);
    const other = (await liveHoldsFor(ctx, apartmentId, checkIn, checkOut, now)).find((h) => h.userId !== user.id);
    if (other) return { id: null, heldBy: other.userName };
    const id = await ctx.db.insert("bookingHolds", { apartmentId, checkIn, checkOut, userId: user.id, userName: user.fullName, startedAt: now, expiresAt: now + HOLD_TTL_MS });
    return { id, heldBy: null };
  },
});

export const holdPing = mutation({
  args: { id: v.id("bookingHolds") },
  returns: v.null(),
  handler: async (ctx, { id }) => {
    const user = await assertPermission(ctx, "reservations.create");
    const h = await ctx.db.get(id);
    if (h && h.userId === user.id) await ctx.db.patch(id, { expiresAt: Date.now() + HOLD_TTL_MS });
    return null;
  },
});

export const holdRelease = mutation({
  args: { id: v.id("bookingHolds") },
  returns: v.null(),
  handler: async (ctx, { id }) => {
    const user = await assertPermission(ctx, "reservations.create");
    const h = await ctx.db.get(id);
    if (h && h.userId === user.id) await ctx.db.delete(id);
    return null;
  },
});

/** Business card for share messages. */
export const business = query({
  args: {},
  returns: v.object({ name: v.string(), phone: v.string(), email: v.string(), address: v.string(), checkInTime: v.string(), checkOutTime: v.string(), currency: v.string() }),
  handler: async (ctx) => {
    await assertPermission(ctx, "apartments.view", "reservations.view");
    const s = await getSettings(ctx);
    return { name: s.businessName, phone: s.contactPhone, email: s.contactEmail, address: s.address, checkInTime: s.checkInTime, checkOutTime: s.checkOutTime, currency: s.currency };
  },
});

export const logShare = mutation({
  args: { apartmentId: v.id("apartments"), channel: v.string(), checkIn: v.optional(v.string()), checkOut: v.optional(v.string()), to: v.optional(v.string()), photos: v.optional(v.number()) },
  returns: v.null(),
  handler: async (ctx, { apartmentId, channel, checkIn, checkOut, to, photos }) => {
    const user = await assertPermission(ctx, "apartments.view", "reservations.view");
    const a = await ctx.db.get(apartmentId);
    if (!a) return null;
    await audit(ctx, actorLite(user), { action: "APARTMENT_SHARED", module: "apartments", entityType: "apartment", entityId: apartmentId, entityLabel: `${a.code} · ${a.name}`, newValue: { channel, checkIn: checkIn ?? null, checkOut: checkOut ?? null, to: to ? to.replace(/\d(?=\d{2})/g, "•") : null, photos: photos ?? 0 }, apartmentId });
    return null;
  },
});

/** Online bookings waiting for a human confirmation (PENDING + WEBSITE). */
export const webBookings = query({
  args: {},
  returns: v.array(v.any()),
  handler: async (ctx) => {
    await assertPermission(ctx, "reservations.view");
    const rows = (await ctx.db.query("reservations").withIndex("by_status_checkIn", (q) => q.eq("status", "PENDING")).order("asc").take(300)).filter((r) => r.source === "WEBSITE");
    return Promise.all(rows.map(async (r) => {
      const [c, a] = await Promise.all([ctx.db.get(r.customerId), ctx.db.get(r.apartmentId)]);
      return { id: r._id, code: r.code, checkIn: r.checkIn, checkOut: r.checkOut, nights: r.nights, guests: r.adults + r.children, totalAmount: r.totalAmount, createdAt: r.createdAt, note: r.customerRequests ?? null, customer: { id: r.customerId, fullName: c?.fullName ?? "", phone: c?.phone ?? "", riskLevel: c?.riskLevel ?? "NORMAL" }, apartment: { id: r.apartmentId, code: a?.code ?? "", name: a?.name ?? "" } };
    }));
  },
});
