import { mutation, query } from "./_generated/server";
import type { MutationCtx, QueryCtx } from "./_generated/server";
import { ConvexError, v } from "convex/values";
import type { Doc, Id } from "./_generated/dataModel";

import { requireAdmin, requireUser } from "./rbac";

const FIELD_TYPES = ["text", "number", "select", "date", "boolean"] as const;

type FieldType = (typeof FIELD_TYPES)[number];

function normalizeKey(label: string) {
  return label
    .trim()
    .toLowerCase()
    .normalize("NFD")
    .replace(/[^\w\s-]/g, "")
    .replace(/\s+/g, "_")
    .replace(/_+/g, "_");
}

type AnyCtx = QueryCtx | MutationCtx;

async function ensureUniqueKey(ctx: AnyCtx, tenantId: string, key: string, excludeId?: Id<"ticketFields">) {
  const existing = await ctx.db
    .query("ticketFields")
    .withIndex("by_tenant_key", (q) => q.eq("tenantId", tenantId).eq("key", key))
    .first();
  if (existing && (!excludeId || existing._id !== excludeId)) {
    throw new ConvexError("Já existe um campo com este identificador");
  }
}

function validateOptions(type: FieldType, options: { value: string; label: string }[] | undefined) {
  if (type === "select" && (!options || options.length === 0)) {
    throw new ConvexError("Campos de seleção precisam de pelo menos uma opção");
  }
}

async function validateCompanyScope(ctx: AnyCtx, tenantId: string, companyId?: Id<"companies"> | null) {
  if (!companyId) return undefined;
  const company = await ctx.db.get(companyId);
  if (!company || company.tenantId !== tenantId) {
    throw new ConvexError("Empresa inválida para o campo");
  }
  return companyId;
}

export const list = query({
  args: { tenantId: v.string(), viewerId: v.id("users"), scope: v.optional(v.string()) },
  handler: async (ctx, { tenantId, viewerId, scope }) => {
    await requireAdmin(ctx, viewerId, tenantId);
    const fields = await ctx.db
      .query("ticketFields")
      .withIndex("by_tenant_order", (q) => q.eq("tenantId", tenantId))
      .take(100);

    return fields
      .filter((field) => {
        if (!scope) return true;
        const fieldScope = (field.scope ?? "all").trim();
        if (fieldScope === "all" || fieldScope.length === 0) return true;
        return fieldScope === scope;
      })
      .sort((a, b) => a.order - b.order)
      .map((field) => ({
        id: field._id,
        key: field.key,
        label: field.label,
        description: field.description ?? "",
        type: field.type as FieldType,
        required: field.required,
        options: field.options ?? [],
        order: field.order,
        scope: field.scope ?? "all",
        companyId: field.companyId ?? null,
        createdAt: field.createdAt,
        updatedAt: field.updatedAt,
      }));
  },
});

export const listForTenant = query({
  args: { tenantId: v.string(), viewerId: v.id("users"), scope: v.optional(v.string()) },
  handler: async (ctx, { tenantId, viewerId, scope }) => {
    await requireUser(ctx, viewerId, tenantId);
    const fields = await ctx.db
      .query("ticketFields")
      .withIndex("by_tenant_order", (q) => q.eq("tenantId", tenantId))
      .take(100);

    return fields
      .filter((field) => {
        if (!scope) return true;
        const fieldScope = (field.scope ?? "all").trim();
        if (fieldScope === "all" || fieldScope.length === 0) return true;
        return fieldScope === scope;
      })
      .sort((a, b) => a.order - b.order)
      .map((field) => ({
        id: field._id,
        key: field.key,
        label: field.label,
        description: field.description ?? "",
        type: field.type as FieldType,
        required: field.required,
        options: field.options ?? [],
        order: field.order,
        scope: field.scope ?? "all",
        companyId: field.companyId ?? null,
      }));
  },
});

export const create = mutation({
  args: {
    tenantId: v.string(),
    actorId: v.id("users"),
    label: v.string(),
    description: v.optional(v.string()),
    type: v.string(),
    required: v.boolean(),
    options: v.optional(
      v.array(
        v.object({
          value: v.string(),
          label: v.string(),
        })
      )
    ),
    scope: v.optional(v.string()),
    companyId: v.optional(v.id("companies")),
  },
  handler: async (ctx, { tenantId, actorId, label, description, type, required, options, scope, companyId }) => {
    await requireAdmin(ctx, actorId, tenantId);
    const normalizedLabel = label.trim();
    if (normalizedLabel.length < 2) {
      throw new ConvexError("Informe um rótulo para o campo");
    }
    if (!FIELD_TYPES.includes(type as FieldType)) {
      throw new ConvexError("Tipo de campo inválido");
    }
    validateOptions(type as FieldType, options ?? undefined);
    const key = normalizeKey(normalizedLabel);
    await ensureUniqueKey(ctx, tenantId, key);
    const normalizedScope = (() => {
      const raw = scope?.trim();
      if (!raw || raw.length === 0) return "all";
      const safe = raw.toLowerCase();
      if (!/^[a-z0-9_\-]+$/i.test(safe)) {
        throw new ConvexError("Escopo inválido para o campo");
      }
      return safe;
    })();
    const companyRef = await validateCompanyScope(ctx, tenantId, companyId ?? undefined);

    const existing = await ctx.db
      .query("ticketFields")
      .withIndex("by_tenant_order", (q) => q.eq("tenantId", tenantId))
      .take(100);
    const maxOrder = existing.reduce((acc: number, item: Doc<"ticketFields">) => Math.max(acc, item.order ?? 0), 0);

    const now = Date.now();
    const id = await ctx.db.insert("ticketFields", {
      tenantId,
      key,
      label: normalizedLabel,
      description,
      type,
      required,
      options,
      order: maxOrder + 1,
      scope: normalizedScope,
      companyId: companyRef,
      createdAt: now,
      updatedAt: now,
    });
    return id;
  },
});

export const update = mutation({
  args: {
    tenantId: v.string(),
    fieldId: v.id("ticketFields"),
    actorId: v.id("users"),
    label: v.string(),
    description: v.optional(v.string()),
    type: v.string(),
    required: v.boolean(),
    options: v.optional(
      v.array(
        v.object({
          value: v.string(),
          label: v.string(),
        })
      )
    ),
    scope: v.optional(v.string()),
    companyId: v.optional(v.id("companies")),
  },
  handler: async (ctx, { tenantId, fieldId, actorId, label, description, type, required, options, scope, companyId }) => {
    await requireAdmin(ctx, actorId, tenantId);
    const field = await ctx.db.get(fieldId);
    if (!field || field.tenantId !== tenantId) {
      throw new ConvexError("Campo não encontrado");
    }
    if (!FIELD_TYPES.includes(type as FieldType)) {
      throw new ConvexError("Tipo de campo inválido");
    }
    const normalizedLabel = label.trim();
    if (normalizedLabel.length < 2) {
      throw new ConvexError("Informe um rótulo para o campo");
    }
    validateOptions(type as FieldType, options ?? undefined);

    const normalizedScope = (() => {
      const raw = scope?.trim();
      if (!raw || raw.length === 0) return "all";
      const safe = raw.toLowerCase();
      if (!/^[a-z0-9_\-]+$/i.test(safe)) {
        throw new ConvexError("Escopo inválido para o campo");
      }
      return safe;
    })();
    const companyRef = await validateCompanyScope(ctx, tenantId, companyId ?? undefined);

    let key = field.key;
    if (field.label !== normalizedLabel) {
      key = normalizeKey(normalizedLabel);
      await ensureUniqueKey(ctx, tenantId, key, fieldId);
    }

    await ctx.db.patch(fieldId, {
      key,
      label: normalizedLabel,
      description,
      type,
      required,
      options,
      scope: normalizedScope,
      companyId: companyRef,
      updatedAt: Date.now(),
    });
  },
});

export const remove = mutation({
  args: {
    tenantId: v.string(),
    fieldId: v.id("ticketFields"),
    actorId: v.id("users"),
  },
  handler: async (ctx, { tenantId, fieldId, actorId }) => {
    await requireAdmin(ctx, actorId, tenantId);
    const field = await ctx.db.get(fieldId);
    if (!field || field.tenantId !== tenantId) {
      throw new ConvexError("Campo não encontrado");
    }
    await ctx.db.delete(fieldId);
  },
});
export const reorder = mutation({
  args: {
    tenantId: v.string(),
    actorId: v.id("users"),
    orderedIds: v.array(v.id("ticketFields")),
  },
  handler: async (ctx, { tenantId, actorId, orderedIds }) => {
    await requireAdmin(ctx, actorId, tenantId);
    const fields = await Promise.all(orderedIds.map((id) => ctx.db.get(id)));
    fields.forEach((field) => {
      if (!field || field.tenantId !== tenantId) {
        throw new ConvexError("Campo inválido para reordenação");
      }
    });
    const now = Date.now();
    await Promise.all(
      orderedIds.map((fieldId, index) =>
        ctx.db.patch(fieldId, {
          order: index + 1,
          updatedAt: now,
        })
      )
    );
  },
});
