import { v } from 'convex/values';

import type { Doc } from './_generated/dataModel';
import { mutation, type MutationCtx, query, type QueryCtx } from './_generated/server';

/**
 * The account a request belongs to.
 *
 * SINGLE-USER / DOGFOOD MODE: a fixed constant, no auth. This is the one place that
 * changes when auth lands — replace the body with:
 *
 *   const identity = await ctx.auth.getUserIdentity();
 *   if (identity === null) throw new Error('Not signed in');
 *   return identity.subject;
 *
 * Everything else stays the same. The owner is ALWAYS derived here, server-side, and the
 * client's payload never carries an ownerId — so a client can't read or write another
 * account's rows. (Keep this constant in sync with DOGFOOD_OWNER in src/data/sync/owner.ts.)
 */
const DOGFOOD_OWNER = 'dogfood';
async function getOwnerId(_ctx: QueryCtx | MutationCtx): Promise<string> {
  return DOGFOOD_OWNER;
}

/** The wire shape a client pushes — the synced fields, keyed by clientId. No ownerId: the server stamps it. */
const taskInput = v.object({
  clientId: v.string(),
  title: v.string(),
  status: v.union(v.literal('open'), v.literal('ready'), v.literal('done')),
  resistance: v.number(),
  exposureCount: v.number(),
  createdAt: v.number(),
  updatedAt: v.number(),
  lastDecision: v.optional(v.union(v.literal('clear'), v.literal('resistant'))),
  completedAt: v.optional(v.number()),
  deletedAt: v.optional(v.number()),
});

/**
 * The owner's full task set, reactive. Clients subscribe and reconcile rows into their
 * local store. Tombstoned rows are included so deletes propagate; the client filters them.
 * The dataset is tiny (one user's tasks), so we return everything rather than paginate.
 */
export const getTasks = query({
  args: {},
  handler: async (ctx): Promise<Doc<'tasks'>[]> => {
    const ownerId = await getOwnerId(ctx);
    return await ctx.db
      .query('tasks')
      .withIndex('by_owner_updated', (q) => q.eq('ownerId', ownerId))
      .collect();
  },
});

/**
 * Upsert a batch of rows by (ownerId, clientId), last-write-wins on `updatedAt`. Idempotent:
 * re-delivering a batch is a no-op (a row whose updatedAt is not newer is left as-is), and the
 * client UUID key means a replay can never duplicate. Returns the authoritative server row for
 * each input so the client can confirm its push (or pick up a newer version that beat it).
 */
export const pushTasks = mutation({
  args: { tasks: v.array(taskInput) },
  handler: async (ctx, { tasks }): Promise<Doc<'tasks'>[]> => {
    const ownerId = await getOwnerId(ctx);
    const applied: Doc<'tasks'>[] = [];
    for (const incoming of tasks) {
      const existing = await ctx.db
        .query('tasks')
        .withIndex('by_owner_client', (q) => q.eq('ownerId', ownerId).eq('clientId', incoming.clientId))
        .unique();
      if (existing === null) {
        const id = await ctx.db.insert('tasks', { ...incoming, ownerId });
        applied.push((await ctx.db.get(id))!);
      } else if (incoming.updatedAt >= existing.updatedAt) {
        await ctx.db.replace(existing._id, { ...incoming, ownerId });
        applied.push((await ctx.db.get(existing._id))!);
      } else {
        applied.push(existing); // client's push is older — hand back the newer authoritative row
      }
    }
    return applied;
  },
});
