/**
 * Uniqueness Constraint Validation for Convex
 *
 * Convex has no built-in unique constraints — this utility checks
 * uniqueness before inserts/updates, mirroring every @@unique
 * directive from the Prisma schema.
 *
 * Usage:
 *   await assertUnique(ctx, "integrations", { userId, type });
 *   await assertCompositeUnique(ctx, "bookings", "booking_slot_uniqueness",
 *     { appointmentDate, appointmentTime, staffMember, organizationId });
 */

import { GenericMutationCtx, GenericDataModel } from "convex/server";

type Ctx = GenericMutationCtx<GenericDataModel>;

/**
 * Complete map of every @@unique constraint from prisma/schema.prisma.
 *
 * Format: tableName → [ { name, fields } ]
 * `name` is either the explicit Prisma constraint name or auto-generated
 * from the field list.
 */
export const UNIQUE_CONSTRAINTS: Record<
  string,
  { name: string; fields: string[] }[]
> = {
  user_api_keys: [
    {
      name: "user_api_keys_userId_service_keyName_key",
      fields: ["userId", "service", "keyName"],
    },
  ],
  workflow_analytics: [
    {
      name: "workflow_analytics_userId_workflowType_date_key",
      fields: ["userId", "workflowType", "date"],
    },
  ],
  bookings: [
    {
      name: "booking_slot_uniqueness",
      fields: [
        "appointmentDate",
        "appointmentTime",
        "staffMember",
        "organizationId",
      ],
    },
  ],
  customers: [
    { name: "customers_userId_phone_key", fields: ["userId", "phone"] },
  ],
  integrations: [
    { name: "integrations_userId_type_key", fields: ["userId", "type"] },
  ],
  organization_members: [
    {
      name: "organization_members_organizationId_userId_key",
      fields: ["organizationId", "userId"],
    },
  ],
  system_message_views: [
    {
      name: "system_message_views_messageId_userId_key",
      fields: ["messageId", "userId"],
    },
  ],
  waitlist_signups: [
    {
      name: "waitlist_signups_waitlistId_email_key",
      fields: ["waitlistId", "email"],
    },
  ],
  email_nurture_sequence_steps: [
    {
      name: "email_nurture_sequence_steps_sequenceId_stepNumber_key",
      fields: ["sequenceId", "stepNumber"],
    },
  ],
  user_nurture_sequence_states: [
    {
      name: "user_nurture_sequence_states_userId_sequenceId_key",
      fields: ["userId", "sequenceId"],
    },
  ],
  org_whatsapp_conversations: [
    {
      name: "org_whatsapp_conversations_organizationId_userPhone_key",
      fields: ["organizationId", "userPhone"],
    },
  ],
  social_accounts: [
    {
      name: "social_accounts_userId_platform_platformAccountId_key",
      fields: ["userId", "platform", "platformAccountId"],
    },
  ],
  voice_agent_kb_sync: [
    {
      name: "voice_agent_kb_sync_userId_documentId_key",
      fields: ["userId", "documentId"],
    },
  ],
  lead_pipeline_stages: [
    {
      name: "lead_pipeline_stages_userId_name_key",
      fields: ["userId", "name"],
    },
  ],
  lead_nurture_memberships: [
    {
      name: "lead_nurture_memberships_leadId_sequenceId_key",
      fields: ["leadId", "sequenceId"],
    },
  ],
  review_question_templates: [
    {
      name: "review_question_templates_userId_industry_key",
      fields: ["userId", "industry"],
    },
  ],
  quotation_reminders: [
    {
      name: "quotation_reminders_quotationId_trigger_dayOffset_key",
      fields: ["quotationId", "trigger", "dayOffset"],
    },
  ],
  property_match: [
    {
      name: "property_match_propertyId_requirementId_key",
      fields: ["propertyId", "requirementId"],
    },
  ],
};

/**
 * Assert that a record with the given field values does not already exist.
 * Throws a descriptive error if a duplicate is found.
 *
 * @param ctx - Convex mutation context
 * @param table - Table name
 * @param fields - Object of field name → value pairs to check uniqueness on
 * @param excludeId - Optional document _id to exclude (for updates)
 */
export async function assertUnique(
  ctx: Ctx,
  table: string,
  fields: Record<string, unknown>,
  excludeId?: string,
): Promise<void> {
  const fieldEntries = Object.entries(fields);

  // Build a filter chain for all fields
  let query = ctx.db.query(table as never);

  for (const [key, value] of fieldEntries) {
    // Dynamic table/field names require type bypass — safe because UNIQUE_CONSTRAINTS is static
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    query = (query as any).filter((q: any) => q.eq(q.field(key), value));
  }

  const existing = await query.first();

  if (
    existing &&
    (!excludeId || (existing as { _id: string })._id !== excludeId)
  ) {
    const fieldDesc = fieldEntries
      .map(([k, v]) => `${k}=${JSON.stringify(v)}`)
      .join(", ");
    throw new Error(
      `Unique constraint violation on ${table}: A record with {${fieldDesc}} already exists.`,
    );
  }
}

/**
 * Assert a named composite unique constraint.
 * Looks up the constraint fields from UNIQUE_CONSTRAINTS and validates.
 *
 * @param ctx - Convex mutation context
 * @param table - Table name
 * @param constraintName - The named constraint (e.g. "booking_slot_uniqueness")
 * @param values - Record of field name → value for the constraint fields
 * @param excludeId - Optional document _id to exclude (for updates)
 */
export async function assertCompositeUnique(
  ctx: Ctx,
  table: string,
  constraintName: string,
  values: Record<string, unknown>,
  excludeId?: string,
): Promise<void> {
  const constraints = UNIQUE_CONSTRAINTS[table];
  if (!constraints) {
    throw new Error(`No unique constraints defined for table: ${table}`);
  }

  const constraint = constraints.find((c) => c.name === constraintName);
  if (!constraint) {
    throw new Error(
      `Unknown constraint "${constraintName}" on table "${table}". ` +
        `Available: ${constraints.map((c) => c.name).join(", ")}`,
    );
  }

  // Build the fields object from only the constraint fields
  const constraintFields: Record<string, unknown> = {};
  for (const field of constraint.fields) {
    if (!(field in values)) {
      throw new Error(
        `Missing field "${field}" for constraint "${constraintName}". ` +
          `Required fields: ${constraint.fields.join(", ")}`,
      );
    }
    constraintFields[field] = values[field];
  }

  await assertUnique(ctx, table, constraintFields, excludeId);
}

/**
 * Get all unique constraints for a given table.
 */
export function getConstraints(
  table: string,
): { name: string; fields: string[] }[] {
  return UNIQUE_CONSTRAINTS[table] || [];
}
