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

import { requireAdmin, requireUser } from "./rbac"
import { ensureMobileDeviceFields } from "./deviceFieldDefaults"

const FIELD_TYPES = ["text", "number", "select", "multiselect", "date", "boolean"] as const
type FieldType = (typeof FIELD_TYPES)[number]

type AnyCtx = MutationCtx | QueryCtx

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

async function ensureUniqueKey(ctx: AnyCtx, tenantId: string, key: string, excludeId?: Id<"deviceFields">) {
  const existing = await ctx.db
    .query("deviceFields")
    .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" || type === "multiselect") && (!options || options.length === 0)) {
    throw new ConvexError("Campos de seleção precisam de pelo menos uma opção")
  }
}

function matchesScope(fieldScope: string | undefined, scope: string | undefined) {
  if (!scope || scope === "all") return true
  if (!fieldScope || fieldScope === "all") return true
  return fieldScope === scope
}

function matchesCompany(fieldCompanyId: Id<"companies"> | undefined, companyId: Id<"companies"> | undefined, includeScoped?: boolean) {
  if (!companyId) {
    if (includeScoped) return true
    return fieldCompanyId ? false : true
  }
  return !fieldCompanyId || fieldCompanyId === companyId
}

export const list = query({
  args: {
    tenantId: v.string(),
    viewerId: v.id("users"),
    companyId: v.optional(v.id("companies")),
    scope: v.optional(v.string()),
  },
  handler: async (ctx, { tenantId, viewerId, companyId, scope }) => {
    await requireAdmin(ctx, viewerId, tenantId)
    const fieldsQuery = ctx.db
      .query("deviceFields")
      .withIndex("by_tenant_order", (q) => q.eq("tenantId", tenantId))

    const fields = await fieldsQuery.take(100)
    return fields
      .filter((field) => matchesCompany(field.companyId, companyId, true))
      .filter((field) => matchesScope(field.scope, 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: Boolean(field.required),
        options: field.options ?? [],
        order: field.order,
        scope: field.scope ?? "all",
        companyId: field.companyId ?? null,
      }))
  },
})

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

    return fields
      .filter((field) => matchesCompany(field.companyId, companyId, false))
      .filter((field) => matchesScope(field.scope, 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: Boolean(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.optional(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, args) => {
    await requireAdmin(ctx, args.actorId, args.tenantId)
    const normalizedLabel = args.label.trim()
    if (normalizedLabel.length < 2) {
      throw new ConvexError("Informe um rótulo para o campo")
    }
    if (!FIELD_TYPES.includes(args.type as FieldType)) {
      throw new ConvexError("Tipo de campo inválido")
    }
    validateOptions(args.type as FieldType, args.options ?? undefined)

    const key = normalizeKey(normalizedLabel)
    await ensureUniqueKey(ctx, args.tenantId, key)

    const existing = await ctx.db
      .query("deviceFields")
      .withIndex("by_tenant_order", (q) => q.eq("tenantId", args.tenantId))
      .take(100)
    const maxOrder = existing.reduce((acc, item) => Math.max(acc, item.order ?? 0), 0)
    const now = Date.now()

    const id = await ctx.db.insert("deviceFields", {
      tenantId: args.tenantId,
      key,
      label: normalizedLabel,
      description: args.description ?? undefined,
      type: args.type,
      required: Boolean(args.required),
      options: args.options ?? undefined,
      scope: args.scope ?? "all",
      companyId: args.companyId ?? undefined,
      order: maxOrder + 1,
      createdAt: now,
      updatedAt: now,
      createdBy: args.actorId,
      updatedBy: args.actorId,
    })

    return id
  },
})

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

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

    await ctx.db.patch(args.fieldId, {
      key,
      label: normalizedLabel,
      description: args.description ?? undefined,
      type: args.type,
      required: Boolean(args.required),
      options: args.options ?? undefined,
      scope: args.scope ?? "all",
      companyId: args.companyId ?? undefined,
      updatedAt: Date.now(),
      updatedBy: args.actorId,
    })
  },
})

export const remove = mutation({
  args: {
    tenantId: v.string(),
    actorId: v.id("users"),
    fieldId: v.id("deviceFields"),
  },
  handler: async (ctx, args) => {
    await requireAdmin(ctx, args.actorId, args.tenantId)
    const field = await ctx.db.get(args.fieldId)
    if (!field || field.tenantId !== args.tenantId) {
      throw new ConvexError("Campo não encontrado")
    }
    await ctx.db.delete(args.fieldId)
  },
})

export const reorder = mutation({
  args: {
    tenantId: v.string(),
    actorId: v.id("users"),
    orderedIds: v.array(v.id("deviceFields")),
  },
  handler: async (ctx, args) => {
    await requireAdmin(ctx, args.actorId, args.tenantId)
    const now = Date.now()
    await Promise.all(
      args.orderedIds.map((fieldId, index) =>
        ctx.db.patch(fieldId, {
          order: index + 1,
          updatedAt: now,
          updatedBy: args.actorId,
        })
      )
    )
  },
})

export const ensureDefaults = mutation({
  args: {
    tenantId: v.string(),
    actorId: v.id("users"),
  },
  handler: async (ctx, { tenantId, actorId }) => {
    await requireAdmin(ctx, actorId, tenantId)
    await ensureMobileDeviceFields(ctx, tenantId)
    return { ok: true }
  },
})
