import { v } from "convex/values";
import { mutation, query, type MutationCtx } from "./_generated/server";
import type { Id } from "./_generated/dataModel";
import { assertPermission, actorLite, AppError } from "./lib/access";
import { audit } from "./lib/audit";
import { IMAGE_CATEGORIES } from "../src/lib/domain";
import { upsertCoverFromImages } from "./apartments";

const ALLOWED = ["image/jpeg", "image/png", "image/webp", "image/heic", "image/avif", "image/svg+xml"];

export const generateUploadUrl = mutation({
  args: {},
  returns: v.string(),
  handler: async (ctx) => {
    await assertPermission(ctx, "apartments.media", "apartments.edit");
    return ctx.storage.generateUploadUrl();
  },
});

export const register = mutation({
  args: { apartmentId: v.id("apartments"), storageId: v.id("_storage"), mimeType: v.string(), size: v.number(), fileName: v.optional(v.string()), category: v.optional(v.string()), caption: v.optional(v.union(v.string(), v.null())), width: v.optional(v.number()), height: v.optional(v.number()) },
  returns: v.object({ id: v.id("apartmentImages") }),
  handler: async (ctx, data) => {
    const user = await assertPermission(ctx, "apartments.media", "apartments.edit");
    const apt = await ctx.db.get(data.apartmentId);
    if (!apt) throw new AppError("Apartment not found", "NOT_FOUND");
    const category = data.category ?? "OTHER";
    if (!IMAGE_CATEGORIES.includes(category as (typeof IMAGE_CATEGORIES)[number])) throw new AppError("Invalid category", "VALIDATION");
    if (!ALLOWED.includes(data.mimeType)) throw new AppError("Only JPG, PNG, WEBP, HEIC or AVIF images are accepted", "VALIDATION");
    if (data.size > 15 * 1024 * 1024) throw new AppError("Image too large (max 15 MB)", "VALIDATION");
    const existing = (await ctx.db.query("apartmentImages").withIndex("by_apartment", (q) => q.eq("apartmentId", apt._id)).collect()).filter((i) => !i.archivedAt);
    const id = await ctx.db.insert("apartmentImages", { apartmentId: apt._id, storageId: data.storageId, mimeType: data.mimeType, width: data.width, height: data.height, size: data.size, caption: data.caption?.slice(0, 140) || undefined, category, isCover: existing.length === 0, sortOrder: existing.length, uploadedById: user.id });
    await upsertCoverFromImages(ctx, apt._id);
    await audit(ctx, actorLite(user), { action: "APARTMENT_MEDIA_UPDATED", module: "apartments", entityType: "apartment", entityId: apt._id, entityLabel: apt.code, newValue: { uploaded: data.fileName ?? "photo", category, bytes: data.size }, apartmentId: apt._id });
    return { id };
  },
});

async function loadImage(ctx: MutationCtx, id: Id<"apartmentImages">) {
  const img = await ctx.db.get(id);
  if (!img) throw new AppError("Image not found", "NOT_FOUND");
  const apt = await ctx.db.get(img.apartmentId);
  if (!apt) throw new AppError("Apartment not found", "NOT_FOUND");
  return { img, apt };
}

export const update = mutation({
  args: { id: v.id("apartmentImages"), caption: v.optional(v.union(v.string(), v.null())), category: v.optional(v.string()) },
  returns: v.null(),
  handler: async (ctx, { id, caption, category }) => {
    const user = await assertPermission(ctx, "apartments.media", "apartments.edit");
    const { img, apt } = await loadImage(ctx, id);
    if (category && !IMAGE_CATEGORIES.includes(category as (typeof IMAGE_CATEGORIES)[number])) throw new AppError("Invalid category", "VALIDATION");
    await ctx.db.patch(id, { caption: caption === undefined ? undefined : caption?.slice(0, 140) || undefined, category: category ?? undefined });
    await audit(ctx, actorLite(user), { action: "APARTMENT_MEDIA_UPDATED", module: "apartments", entityType: "apartment", entityId: apt._id, entityLabel: apt.code, previousValue: { caption: img.caption, category: img.category }, newValue: { caption, category }, apartmentId: apt._id });
    return null;
  },
});

export const setCover = mutation({
  args: { id: v.id("apartmentImages") },
  returns: v.null(),
  handler: async (ctx, { id }) => {
    const user = await assertPermission(ctx, "apartments.media", "apartments.edit");
    const { img, apt } = await loadImage(ctx, id);
    for (const i of await ctx.db.query("apartmentImages").withIndex("by_apartment", (q) => q.eq("apartmentId", img.apartmentId)).collect()) if (i.isCover) await ctx.db.patch(i._id, { isCover: false });
    await ctx.db.patch(id, { isCover: true, archivedAt: undefined });
    await upsertCoverFromImages(ctx, apt._id);
    await audit(ctx, actorLite(user), { action: "APARTMENT_MEDIA_UPDATED", module: "apartments", entityType: "apartment", entityId: apt._id, entityLabel: apt.code, newValue: { cover: id }, apartmentId: apt._id });
    return null;
  },
});

export const reorder = mutation({
  args: { apartmentId: v.id("apartments"), orderedIds: v.array(v.id("apartmentImages")) },
  returns: v.null(),
  handler: async (ctx, { apartmentId, orderedIds }) => {
    const user = await assertPermission(ctx, "apartments.media", "apartments.edit");
    const apt = await ctx.db.get(apartmentId);
    if (!apt) throw new AppError("Apartment not found", "NOT_FOUND");
    for (let i = 0; i < orderedIds.length; i++) {
      const img = await ctx.db.get(orderedIds[i]);
      if (img && img.apartmentId === apartmentId) await ctx.db.patch(img._id, { sortOrder: i });
    }
    await audit(ctx, actorLite(user), { action: "APARTMENT_MEDIA_UPDATED", module: "apartments", entityType: "apartment", entityId: apt._id, entityLabel: apt.code, newValue: { reordered: orderedIds.length }, apartmentId: apt._id });
    return null;
  },
});

export const archive = mutation({
  args: { id: v.id("apartmentImages"), archived: v.boolean() },
  returns: v.null(),
  handler: async (ctx, { id, archived }) => {
    const user = await assertPermission(ctx, "apartments.media", "apartments.edit");
    const { img, apt } = await loadImage(ctx, id);
    await ctx.db.patch(id, { archivedAt: archived ? Date.now() : undefined, isCover: archived ? false : img.isCover });
    await upsertCoverFromImages(ctx, apt._id);
    await audit(ctx, actorLite(user), { action: "APARTMENT_MEDIA_UPDATED", module: "apartments", entityType: "apartment", entityId: apt._id, entityLabel: apt.code, newValue: { image: id, archived }, apartmentId: apt._id });
    return null;
  },
});

export const remove = mutation({
  args: { id: v.id("apartmentImages"), reason: v.optional(v.string()) },
  returns: v.null(),
  handler: async (ctx, { id, reason }) => {
    const user = await assertPermission(ctx, "apartments.media", "apartments.edit");
    const { img, apt } = await loadImage(ctx, id);
    await ctx.db.delete(id);
    await ctx.storage.delete(img.storageId);
    await upsertCoverFromImages(ctx, apt._id);
    await audit(ctx, actorLite(user), { action: "APARTMENT_MEDIA_UPDATED", module: "apartments", entityType: "apartment", entityId: apt._id, entityLabel: apt.code, previousValue: { image: id, category: img.category, caption: img.caption }, newValue: "DELETED", reason, apartmentId: apt._id, severity: "WARNING" });
    return null;
  },
});

/** Public presentation content (galleries, cards, showroom): storage URL for the original. */
export const publicSource = query({
  args: { id: v.id("apartmentImages") },
  returns: v.union(v.null(), v.object({ url: v.union(v.string(), v.null()), mimeType: v.string() })),
  handler: async (ctx, { id }) => {
    const img = await ctx.db.get(id);
    if (!img) return null;
    return { url: await ctx.storage.getUrl(img.storageId), mimeType: img.mimeType };
  },
});

/** Showroom slides for the sign-in page — no business data. */
export const showroom = query({
  args: {},
  returns: v.array(v.object({ id: v.id("apartments"), imageId: v.id("apartmentImages"), code: v.string(), name: v.string(), city: v.string(), building: v.union(v.string(), v.null()), meta: v.string() })),
  handler: async (ctx) => {
    const apartments = (await ctx.db.query("apartments").withIndex("by_active", (q) => q.eq("isActive", true)).collect()).filter((a) => !a.deletedAt && a.coverImageId).sort((a, b) => a.code.localeCompare(b.code)).slice(0, 8);
    return apartments.map((a) => ({ id: a._id, imageId: a.coverImageId!, code: a.code, name: a.name, city: a.city, building: a.building ?? null, meta: `${a.bedrooms ? `${a.bedrooms} bedroom${a.bedrooms > 1 ? "s" : ""}` : "Studio"} · up to ${a.maxGuests} guests` }));
  },
});
