import { ConvexError, v } from 'convex/values';

import type { Id } from './_generated/dataModel';
import type { QueryCtx } from './_generated/server';
import { internalQuery, mutation, query } from './_generated/server';
import { deriveHomeUsername, getRequiredUserId, optionalText } from './model';

const loadSettings = async (ctx: QueryCtx, ownerId: Id<'users'>) =>
  await ctx.db
    .query('userEnvironment')
    .withIndex('by_owner', (q) => q.eq('ownerId', ownerId))
    .unique();

/** Current user's environment settings + the resolved home username/first name. */
export const getMine = query({
  args: {},
  handler: async (ctx) => {
    const ownerId = await getRequiredUserId(ctx);
    const [user, settings] = await Promise.all([
      ctx.db.get(ownerId),
      loadSettings(ctx, ownerId),
    ]);
    const firstName = (user?.name ?? '').trim().split(/\s+/)[0] || 'you';
    const username = settings?.homeUsername ?? deriveHomeUsername(user?.name);
    return {
      enabled: settings?.enabled ?? true,
      username,
      firstName,
      dotfilesRepoUrl: settings?.dotfilesRepoUrl,
      dotfilesRepoRef: settings?.dotfilesRepoRef,
      setupCommand: settings?.setupCommand,
    };
  },
});

export const updateMine = mutation({
  args: {
    enabled: v.optional(v.boolean()),
    dotfilesRepoUrl: v.optional(v.string()),
    dotfilesRepoRef: v.optional(v.string()),
    setupCommand: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const ownerId = await getRequiredUserId(ctx);
    const repoUrl = optionalText(args.dotfilesRepoUrl);
    if (repoUrl && !/^https?:\/\//.test(repoUrl)) {
      throw new ConvexError('Dotfiles repo must be a public http(s) URL.');
    }
    const existing = await loadSettings(ctx, ownerId);
    const patch = {
      enabled: args.enabled ?? existing?.enabled ?? true,
      dotfilesRepoUrl: repoUrl,
      dotfilesRepoRef: optionalText(args.dotfilesRepoRef),
      setupCommand: optionalText(args.setupCommand),
      updatedAt: Date.now(),
    };
    if (existing) {
      await ctx.db.patch(existing._id, patch);
      return { success: true };
    }
    await ctx.db.insert('userEnvironment', { ownerId, ...patch });
    return { success: true };
  },
});

// Worker-facing: everything needed to materialize a job's owner's environment.
// Content stays encrypted here; the Node action decrypts it. Resolves the owner
// from the job.
export const getRawEnvironmentForJobInternal = internalQuery({
  args: { jobId: v.id('agentJobs') },
  handler: async (ctx, { jobId }) => {
    const job = await ctx.db.get(jobId);
    if (!job) return null;
    const ownerId = job.ownerId;
    const [user, settings, files] = await Promise.all([
      ctx.db.get(ownerId),
      loadSettings(ctx, ownerId),
      ctx.db
        .query('userDotfiles')
        .withIndex('by_owner', (q) => q.eq('ownerId', ownerId))
        .collect(),
    ]);
    return {
      username: settings?.homeUsername ?? deriveHomeUsername(user?.name),
      enabled: settings?.enabled ?? true,
      dotfilesRepoUrl: settings?.dotfilesRepoUrl,
      dotfilesRepoRef: settings?.dotfilesRepoRef,
      setupCommand: settings?.setupCommand,
      files: files.map((f) => ({
        path: f.path,
        encryptedContent: f.encryptedContent,
        isExecutable: f.isExecutable ?? false,
      })),
    };
  },
});
