/* eslint-disable @typescript-eslint/no-explicit-any -- Convex table-generic helpers; runtime validators enforce the shapes */
import { v } from "convex/values";
import { query, type QueryCtx } from "./_generated/server";
import type { Id } from "./_generated/dataModel";
import { can, requireActor, type Actor } from "./lib/access";
import { getSettings } from "./lib/settings";
import { parseRef, looksLikeRef, REF_META, type RefKind } from "../src/lib/refs";
import { RESERVATION_STATUS_META, RISK_LEVEL_META, type RiskLevel } from "../src/lib/domain";
import { fmtMoney } from "../src/lib/format";
import { parseKey } from "./lib/days";
import { loader } from "./lib/shape";

const PERM: Record<RefKind, string[]> = {
  reservation: ["reservations.view"],
  customer: ["customers.view"],
  worker: ["workers.view"],
  contract: ["contracts.view"],
  payment: ["payments.view", "financials.view_revenue"],
  expense: ["expenses.view"],
  commission: ["commissions.view_all", "commissions.view_own"],
  maintenance: ["maintenance.view"],
  document: ["documents.view", "customers.view_documents"],
  event: ["audit.view"],
  incident: ["customers.view"],
  apartment: ["apartments.view"],
};

const range = (a: string, b: string) => `${parseKey(a).toLocaleString("en", { day: "numeric", month: "short", timeZone: "UTC" })} → ${parseKey(b).toLocaleString("en", { day: "numeric", month: "short", timeZone: "UTC" })}`;
const dt = (ms: number | null | undefined) => (ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "—");

export interface RefPreview {
  kind: RefKind;
  code: string;
  id: string;
  href: string;
  title: string;
  subtitle?: string;
  status?: string;
  facts: { label: string; value: string }[];
  redirected?: string;
}

export async function resolveRef(ctx: QueryCtx, actor: Actor, input: string, currency: string): Promise<RefPreview | null> {
  const ref = parseRef(input);
  if (!ref) return null;
  if (!PERM[ref.kind].some((p) => can(actor, p))) return null;
  const money = can(actor, "payments.view") || can(actor, "financials.view_revenue");
  const byCode = <T extends "reservations" | "customers" | "users" | "contracts" | "payments" | "expenses" | "commissions" | "maintenanceTickets" | "documents" | "auditLog" | "customerIncidents" | "apartments">(t: T) => (ctx.db.query(t) as any).withIndex("by_code", (q: any) => q.eq("code", ref.code)).unique() as Promise<any>;
  switch (ref.kind) {
    case "reservation": {
      const r = await byCode("reservations");
      if (!r) return null;
      if (!can(actor, "reservations.view_all") && r.createdById !== actor.id && r.assignedToId !== actor.id) return null;
      const [c, a, by] = await Promise.all([ctx.db.get(r.customerId as Id<"customers">), ctx.db.get(r.apartmentId as Id<"apartments">), ctx.db.get(r.createdById as Id<"users">)]);
      return { kind: "reservation", code: r.code, id: r._id, href: REF_META.reservation.href(r._id), title: c?.fullName ?? "", subtitle: `${a?.code ?? ""} · ${range(r.checkIn, r.checkOut)}`, status: r.status, facts: [{ label: "Apartment", value: `${a?.code ?? ""} · ${a?.name ?? ""}` }, { label: "Dates", value: `${range(r.checkIn, r.checkOut)} · ${r.nights}n` }, { label: "Status", value: RESERVATION_STATUS_META[r.status as keyof typeof RESERVATION_STATUS_META]?.label ?? r.status }, ...(money ? [{ label: "Balance", value: fmtMoney(Math.max(0, r.totalAmount - r.amountPaid), currency) }] : []), { label: "Created by", value: by?.fullName ?? "" }] };
    }
    case "customer": {
      const c = await byCode("customers");
      if (!c) return null;
      if (c.mergedIntoId) {
        const w = await ctx.db.get(c.mergedIntoId as Id<"customers">);
        if (w) return { kind: "customer", code: w.code, id: w._id, href: REF_META.customer.href(w._id), title: w.fullName, subtitle: `${ref.code} was merged into ${w.code}`, facts: [{ label: "Merged", value: `${ref.code} → ${w.code}` }], redirected: ref.code };
      }
      const rs = await ctx.db.query("reservations").withIndex("by_customer_checkIn", (q) => q.eq("customerId", c._id)).order("desc").take(200);
      return { kind: "customer", code: c.code, id: c._id, href: REF_META.customer.href(c._id), title: c.fullName, subtitle: c.phone, status: c.riskLevel !== "NORMAL" ? `RISK_${c.riskLevel}` : undefined, facts: [{ label: "Phone", value: c.phone }, { label: "Stays", value: String(rs.length) }, { label: "Last stay", value: rs[0]?.checkIn ?? "—" }, { label: "Risk", value: RISK_LEVEL_META[c.riskLevel as RiskLevel]?.label ?? c.riskLevel }, { label: "Verification", value: c.verificationStatus.toLowerCase() }] };
    }
    case "worker": {
      const w = await byCode("users");
      if (!w) return null;
      const role = w.roleId ? await ctx.db.get(w.roleId as Id<"roles">) : null;
      return { kind: "worker", code: w.code, id: w._id, href: REF_META.worker.href(w._id), title: w.fullName ?? "", subtitle: role?.name ?? "", status: w.status, facts: [{ label: "Role", value: role?.name ?? "" }, { label: "Email", value: w.email ?? "" }, { label: "Last login", value: dt(w.lastLoginAt) }] };
    }
    case "contract": {
      const rows = await ctx.db.query("contracts").withIndex("by_code", (q) => q.eq("code", ref.code)).collect();
      const k = rows.sort((a, b) => b.version - a.version)[0];
      if (!k) return null;
      const [c, r] = await Promise.all([ctx.db.get(k.customerId), ctx.db.get(k.reservationId)]);
      return { kind: "contract", code: k.code, id: k._id, href: REF_META.contract.href(k._id), title: c?.fullName ?? "", subtitle: `${r?.code ?? ""} · v${k.version}`, status: k.status, facts: [{ label: "Reservation", value: r?.code ?? "" }, { label: "Version", value: String(k.version) }, { label: "Generated", value: dt(k.at) }] };
    }
    case "payment": {
      const p = await byCode("payments");
      if (!p) return null;
      const [c, r] = await Promise.all([ctx.db.get(p.customerId as Id<"customers">), ctx.db.get(p.reservationId as Id<"reservations">)]);
      return { kind: "payment", code: p.code, id: p._id, href: `/reservations/${p.reservationId}?tab=payments`, title: `${fmtMoney(p.amount, currency)} · ${c?.fullName ?? ""}`, subtitle: `${r?.code ?? ""} · ${p.method.toLowerCase()}`, status: p.reversedAt ? "REVERSED" : p.type, facts: [{ label: "Reservation", value: r?.code ?? "" }, { label: "Paid", value: dt(p.paidAt) }, { label: "Method", value: p.method }] };
    }
    case "expense": {
      const e = await byCode("expenses");
      if (!e) return null;
      const [cat, a] = await Promise.all([ctx.db.get(e.categoryId as Id<"expenseCategories">), e.apartmentId ? ctx.db.get(e.apartmentId as Id<"apartments">) : null]);
      return { kind: "expense", code: e.code, id: e._id, href: REF_META.expense.href(e._id), title: e.description, subtitle: `${cat?.name ?? ""}${a ? ` · ${a.code}` : ""}`, facts: [{ label: "Amount", value: fmtMoney(e.amount, currency) }, { label: "Date", value: e.date }] };
    }
    case "commission": {
      const c = await byCode("commissions");
      if (!c) return null;
      if (!can(actor, "commissions.view_all") && c.workerId !== actor.id) return null;
      const [w, r] = await Promise.all([ctx.db.get(c.workerId as Id<"users">), ctx.db.get(c.reservationId as Id<"reservations">)]);
      return { kind: "commission", code: c.code, id: c._id, href: can(actor, "commissions.view_all") ? `/commissions?worker=${c.workerId}` : "/my-commission", title: `${fmtMoney(c.amount, currency)} · ${w?.fullName ?? ""}`, subtitle: r?.code ?? "", status: c.status, facts: [{ label: "Reservation", value: r?.code ?? "" }, { label: "Status", value: c.status }] };
    }
    case "maintenance": {
      const m = await byCode("maintenanceTickets");
      if (!m) return null;
      const a = await ctx.db.get(m.apartmentId as Id<"apartments">);
      return { kind: "maintenance", code: m.code, id: m._id, href: REF_META.maintenance.href(m._id), title: m.title, subtitle: `${a?.code ?? ""} · ${m.category.toLowerCase()}`, status: m.status, facts: [{ label: "Priority", value: m.priority }, { label: "Reported", value: dt(m.createdAt).slice(0, 10) }] };
    }
    case "document": {
      const d = await byCode("documents");
      if (!d || d.deletedAt) return null;
      const c = d.customerId ? await ctx.db.get(d.customerId as Id<"customers">) : null;
      return { kind: "document", code: d.code, id: d._id, href: c ? `/customers/${c._id}?tab=documents` : REF_META.document.href(d._id), title: d.fileName, subtitle: `${d.category.replace(/_/g, " ").toLowerCase()}${c ? ` · ${c.fullName}` : ""}`, facts: [{ label: "Uploaded", value: dt(d.at) }, { label: "Size", value: `${Math.round(d.size / 1024)} KB` }] };
    }
    case "event": {
      const e = await byCode("auditLog");
      if (!e) return null;
      return { kind: "event", code: e.code, id: e._id, href: REF_META.event.href(e._id), title: e.action.replace(/_/g, " ").toLowerCase(), subtitle: `${e.userName} · ${dt(e.at)}`, status: `SEV_${e.severity}`, facts: [{ label: "Entity", value: e.entityLabel ?? e.entityType ?? "—" }, { label: "Module", value: e.module }] };
    }
    case "incident": {
      const i = await byCode("customerIncidents");
      if (!i) return null;
      const c = await ctx.db.get(i.customerId as Id<"customers">);
      return { kind: "incident", code: i.code, id: i._id, href: `/customers/${i.customerId}?tab=risk`, title: i.title, subtitle: c?.fullName ?? "", status: i.resolvedAt ? "RESOLVED" : i.severity, facts: [{ label: "Type", value: i.type.replace(/_/g, " ").toLowerCase() }, { label: "Occurred", value: dt(i.occurredAt).slice(0, 10) }] };
    }
    case "apartment": {
      const a = await byCode("apartments");
      if (!a || a.deletedAt) return null;
      return { kind: "apartment", code: a.code, id: a._id, href: REF_META.apartment.href(a._id), title: a.name, subtitle: `${a.building ?? ""} ${a.city}`.trim(), status: a.status, facts: [{ label: "Status", value: a.status.toLowerCase() }, { label: "Guests", value: String(a.maxGuests) }, ...(money ? [{ label: "Base price", value: fmtMoney(a.basePrice, currency) }] : [])] };
    }
  }
}

export const resolve = query({
  args: { ref: v.string() },
  returns: v.union(v.null(), v.any()),
  handler: async (ctx, { ref }) => {
    const actor = await requireActor(ctx);
    const settings = await getSettings(ctx);
    return resolveRef(ctx, actor, ref, settings.currency);
  },
});

/** Global search (command palette). */
export const global = query({
  args: { q: v.string() },
  returns: v.array(v.any()),
  handler: async (ctx, { q: raw }) => {
    const actor = await requireActor(ctx);
    const q = raw.trim();
    if (q.length < 2) return [];
    const settings = await getSettings(ctx);
    const lower = q.toLowerCase();
    const digits = q.replace(/[^\d]/g, "");
    const hits: any[] = [];
    const customersL = loader(ctx, "customers");
    const aptsL = loader(ctx, "apartments");
    const ref = parseRef(q);
    if (ref) {
      const hit = await resolveRef(ctx, actor, q, settings.currency);
      if (hit) hits.push({ group: "Reference", id: `ref-${hit.id}`, title: `${hit.code} · ${hit.title}`, subtitle: hit.redirected ? `${hit.redirected} was merged into this profile` : hit.subtitle, href: hit.href, badge: REF_META[hit.kind].label, preview: { kind: hit.kind, code: hit.code, status: hit.status, facts: hit.facts } });
    }
    const viewAllRes = can(actor, "reservations.view_all");
    if (looksLikeRef(q) && !ref) {
      const up = q.toUpperCase().replace(/\s+/g, "-");
      if (up.startsWith("RES") && can(actor, "reservations.view")) {
        const rs = (await ctx.db.query("reservations").withIndex("by_code", (x) => x.gte("code", up).lt("code", up + "￿")).take(5)).filter((r) => viewAllRes || r.createdById === actor.id || r.assignedToId === actor.id);
        for (const r of rs) hits.push({ group: "Reference", id: r._id, title: `${r.code} · ${(await customersL(r.customerId))?.fullName ?? ""}`, subtitle: `${(await aptsL(r.apartmentId))?.code ?? ""} · ${range(r.checkIn, r.checkOut)}`, href: `/reservations/${r._id}`, badge: RESERVATION_STATUS_META[r.status as keyof typeof RESERVATION_STATUS_META]?.label });
      } else if (up.startsWith("CUS") && can(actor, "customers.view")) {
        for (const c of (await ctx.db.query("customers").withIndex("by_code", (x) => x.gte("code", up).lt("code", up + "￿")).take(5)).filter((c) => !c.deletedAt)) hits.push({ group: "Reference", id: c._id, title: `${c.code} · ${c.fullName}`, subtitle: c.phone, href: `/customers/${c._id}`, badge: "Customer" });
      }
    }
    if (can(actor, "customers.view")) {
      const found = new Map<string, any>();
      for (const c of await ctx.db.query("customers").withSearchIndex("search", (s) => s.search("fullName", q)).take(6)) found.set(c._id, c);
      if (found.size < 6) for (const c of await ctx.db.query("customers").withIndex("by_updated").order("desc").take(600)) { if (found.size >= 6) break; if (c.deletedAt) continue; if ((digits.length >= 3 && c.phone.replace(/[^\d]/g, "").includes(digits)) || (c.idNumber ?? "").toLowerCase().includes(lower) || (c.email ?? "").includes(lower) || c.code.toLowerCase().includes(lower)) found.set(c._id, c); }
      for (const c of [...found.values()].filter((c) => !c.deletedAt && !c.mergedIntoId)) hits.push({ group: "Customers", id: c._id, title: c.fullName, subtitle: `${c.phone}${c.idNumber ? " · " + c.idNumber : ""}`, href: `/customers/${c._id}`, badge: c.riskLevel !== "NORMAL" ? RISK_LEVEL_META[c.riskLevel as RiskLevel].label : c.code, tone: c.riskLevel !== "NORMAL" ? RISK_LEVEL_META[c.riskLevel as RiskLevel].tone : undefined });
    }
    if (can(actor, "reservations.view")) {
      const recent = (await ctx.db.query("reservations").withIndex("by_checkIn").order("desc").take(800)).filter((r) => viewAllRes || r.createdById === actor.id || r.assignedToId === actor.id);
      const out: typeof recent = [];
      for (const r of recent) {
        if (out.length >= 6) break;
        const c = await customersL(r.customerId);
        if (r.code.toLowerCase().includes(lower) || (r.externalRef ?? "").toLowerCase().includes(lower) || (c?.fullName ?? "").toLowerCase().includes(lower) || (digits.length >= 3 && (c?.phone ?? "").replace(/[^\d]/g, "").includes(digits))) out.push(r);
      }
      for (const r of out) if (!hits.some((h) => h.href === `/reservations/${r._id}`)) hits.push({ group: "Reservations", id: r._id, title: `${r.code} · ${(await customersL(r.customerId))?.fullName ?? ""}`, subtitle: `${(await aptsL(r.apartmentId))?.code ?? ""} · ${range(r.checkIn, r.checkOut)}`, href: `/reservations/${r._id}`, badge: RESERVATION_STATUS_META[r.status as keyof typeof RESERVATION_STATUS_META]?.label });
    }
    if (can(actor, "apartments.view")) for (const a of (await ctx.db.query("apartments").collect()).filter((a) => !a.deletedAt && [a.code, a.name, a.building ?? "", a.address].some((s) => s.toLowerCase().includes(lower))).slice(0, 5)) hits.push({ group: "Apartments", id: a._id, title: `${a.code} · ${a.name}`, subtitle: `${a.building ?? ""} ${a.city}`.trim(), href: `/apartments/${a._id}`, badge: a.status.toLowerCase() });
    if (can(actor, "workers.view")) {
      const roles = await ctx.db.query("roles").collect();
      for (const w of (await ctx.db.query("users").collect()).filter((w) => !w.deletedAt && w.roleId && [w.fullName ?? "", w.email ?? "", w.phone ?? "", w.code ?? ""].some((s) => s.toLowerCase().includes(lower))).slice(0, 4)) hits.push({ group: "Workers", id: w._id, title: w.fullName ?? "", subtitle: `${roles.find((r) => r._id === w.roleId)?.name ?? ""} · ${w.email ?? ""}`, href: `/workers/${w._id}`, badge: w.code ?? undefined });
    }
    if (can(actor, "contracts.view")) {
      const rows = await ctx.db.query("contracts").order("desc").take(300);
      const out: typeof rows = [];
      for (const k of rows) { if (out.length >= 4) break; const c = await customersL(k.customerId); if (k.code.toLowerCase().includes(lower) || (c?.fullName ?? "").toLowerCase().includes(lower)) out.push(k); }
      for (const k of out) hits.push({ group: "Contracts", id: k._id, title: `${k.code} · ${(await customersL(k.customerId))?.fullName ?? ""}`, subtitle: (await ctx.db.get(k.reservationId))?.code ?? "", href: `/contracts/${k._id}`, badge: k.status.toLowerCase() });
    }
    if (can(actor, "maintenance.view") && q.length >= 3) for (const m of (await ctx.db.query("maintenanceTickets").order("desc").take(300)).filter((m) => m.code.toLowerCase().includes(lower) || m.title.toLowerCase().includes(lower)).slice(0, 3)) hits.push({ group: "Maintenance", id: m._id, title: `${m.code} · ${m.title}`, subtitle: (await aptsL(m.apartmentId))?.code ?? "", href: `/maintenance?ticket=${m._id}`, badge: m.status.toLowerCase() });
    return hits;
  },
});
