import { v } from 'convex/values';

import type { Doc } from './_generated/dataModel';
import { mutation, query } from './_generated/server';
import { getOwnedSpoon, getRequiredUserId, optionalText } from './model';

const reasoningEffort = v.union(
  v.literal('none'),
  v.literal('minimal'),
  v.literal('low'),
  v.literal('medium'),
  v.literal('high'),
  v.literal('xhigh'),
);

const runtime = v.literal('opencode');

const envFilePath = v.union(
  v.literal('.env'),
  v.literal('.env.local'),
  v.literal('.env.production'),
  v.literal('.env.production.local'),
  v.literal('custom'),
);

const defaults = {
  enabled: true,
  runtime: 'opencode' as const,
  branchPrefix: 'spoon/agent',
  agentModel: '',
  reasoningEffort: 'medium' as const,
  maxJobDurationMs: 1_800_000,
  maxOutputBytes: 200_000,
  envFilePath: '.env.local' as const,
  materializeEnvFileByDefault: false,
  autoDetectCommands: true,
  allowUserFileEditing: true,
};

export const getForSpoon = query({
  args: { spoonId: v.id('spoons') },
  handler: async (ctx, { spoonId }) => {
    const ownerId = await getRequiredUserId(ctx);
    const spoon = await getOwnedSpoon(ctx, spoonId, ownerId);
    const settings = await ctx.db
      .query('spoonAgentSettings')
      .withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
      .first();
    return (
      settings ?? {
        spoonId,
        ownerId,
        ...defaults,
        defaultBaseBranch:
          spoon.forkDefaultBranch ?? spoon.upstreamDefaultBranch,
      }
    );
  },
});

export const update = mutation({
  args: {
    spoonId: v.id('spoons'),
    enabled: v.optional(v.boolean()),
    defaultBaseBranch: v.optional(v.string()),
    branchPrefix: v.optional(v.string()),
    installCommand: v.optional(v.string()),
    checkCommand: v.optional(v.string()),
    testCommand: v.optional(v.string()),
    runtime: v.optional(runtime),
    agentModel: v.optional(v.string()),
    reasoningEffort: v.optional(reasoningEffort),
    maxJobDurationMs: v.optional(v.number()),
    maxOutputBytes: v.optional(v.number()),
    envFilePath: v.optional(envFilePath),
    customEnvFilePath: v.optional(v.string()),
    materializeEnvFileByDefault: v.optional(v.boolean()),
    autoDetectCommands: v.optional(v.boolean()),
    allowUserFileEditing: v.optional(v.boolean()),
    aiProviderProfileId: v.optional(v.id('aiProviderProfiles')),
    clearAiProviderProfile: v.optional(v.boolean()),
  },
  handler: async (ctx, args) => {
    const ownerId = await getRequiredUserId(ctx);
    const spoon = await getOwnedSpoon(ctx, args.spoonId, ownerId);
    const now = Date.now();
    let settings = await ctx.db
      .query('spoonAgentSettings')
      .withIndex('by_spoon', (q) => q.eq('spoonId', args.spoonId))
      .first();

    if (!settings) {
      const id = await ctx.db.insert('spoonAgentSettings', {
        spoonId: args.spoonId,
        ownerId,
        ...defaults,
        defaultBaseBranch:
          spoon.forkDefaultBranch ?? spoon.upstreamDefaultBranch,
        createdAt: now,
        updatedAt: now,
      });
      settings = await ctx.db.get(id);
    }

    if (!settings) throw new Error('Agent settings not found.');

    const patch: Partial<Doc<'spoonAgentSettings'>> = { updatedAt: now };
    if (args.enabled !== undefined) patch.enabled = args.enabled;
    if (args.defaultBaseBranch !== undefined) {
      patch.defaultBaseBranch = optionalText(args.defaultBaseBranch);
    }
    if (args.branchPrefix !== undefined) {
      patch.branchPrefix =
        optionalText(args.branchPrefix) ?? defaults.branchPrefix;
    }
    if (args.installCommand !== undefined) {
      patch.installCommand = optionalText(args.installCommand);
    }
    if (args.checkCommand !== undefined) {
      patch.checkCommand = optionalText(args.checkCommand);
    }
    if (args.testCommand !== undefined) {
      patch.testCommand = optionalText(args.testCommand);
    }
    if (args.runtime !== undefined) {
      patch.runtime = 'opencode';
    }
    if (args.agentModel !== undefined) {
      patch.agentModel = optionalText(args.agentModel) ?? defaults.agentModel;
    }
    if (args.reasoningEffort !== undefined) {
      patch.reasoningEffort = args.reasoningEffort;
    }
    if (args.maxJobDurationMs !== undefined) {
      patch.maxJobDurationMs = Math.max(60_000, args.maxJobDurationMs);
    }
    if (args.maxOutputBytes !== undefined) {
      patch.maxOutputBytes = Math.max(10_000, args.maxOutputBytes);
    }
    if (args.envFilePath !== undefined) {
      patch.envFilePath = args.envFilePath;
    }
    if (args.customEnvFilePath !== undefined) {
      patch.customEnvFilePath = optionalText(args.customEnvFilePath);
    }
    if (args.materializeEnvFileByDefault !== undefined) {
      patch.materializeEnvFileByDefault = args.materializeEnvFileByDefault;
    }
    if (args.autoDetectCommands !== undefined) {
      patch.autoDetectCommands = args.autoDetectCommands;
    }
    if (args.allowUserFileEditing !== undefined) {
      patch.allowUserFileEditing = args.allowUserFileEditing;
    }
    if (args.aiProviderProfileId !== undefined) {
      const profile = await ctx.db.get(args.aiProviderProfileId);
      if (profile?.ownerId !== ownerId) {
        throw new Error('AI provider profile not found.');
      }
      patch.aiProviderProfileId = args.aiProviderProfileId;
    }
    if (args.clearAiProviderProfile) {
      patch.aiProviderProfileId = undefined;
    }

    await ctx.db.patch(settings._id, patch);
    return { success: true };
  },
});
