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

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

type AiProviderProfileWithDefault = Doc<'aiProviderProfiles'> & {
  isDefault?: boolean;
};

const provider = v.union(
  v.literal('openai'),
  v.literal('anthropic'),
  v.literal('google'),
  v.literal('openrouter'),
  v.literal('requesty'),
  v.literal('litellm'),
  v.literal('cloudflare_ai_gateway'),
  v.literal('custom_openai_compatible'),
  v.literal('opencode_openai_login'),
);

const authType = v.union(
  v.literal('api_key'),
  v.literal('opencode_auth_json'),
  v.literal('none'),
);

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

const isConfigured = (profile: Doc<'aiProviderProfiles'>) =>
  profile.authType === 'none' || Boolean(profile.encryptedSecret);

const defaultPatch = (isDefault: boolean) =>
  ({ isDefault }) as Partial<Doc<'aiProviderProfiles'>>;

const publicProfile = (
  profile: AiProviderProfileWithDefault,
  defaultProfileId?: Id<'aiProviderProfiles'>,
) => ({
  _id: profile._id,
  _creationTime: profile._creationTime,
  name: profile.name,
  provider: profile.provider,
  authType: profile.authType,
  secretPreview: profile.secretPreview,
  baseUrl: profile.baseUrl,
  defaultModel: profile.defaultModel,
  modelOptions: profile.modelOptions,
  reasoningEffort: profile.reasoningEffort,
  enabled: profile.enabled,
  configured: isConfigured(profile),
  isDefault: profile._id === defaultProfileId,
  createdAt: profile.createdAt,
  updatedAt: profile.updatedAt,
});

const requireOwnedProfile = async (
  ctx: MutationCtx,
  profileId: Id<'aiProviderProfiles'>,
  ownerId: Id<'users'>,
) => {
  const profile = await ctx.db.get(profileId);
  if (profile?.ownerId !== ownerId) {
    throw new ConvexError('AI provider profile not found.');
  }
  return profile;
};

export const listMine = query({
  args: {},
  handler: async (ctx) => {
    const ownerId = await getRequiredUserId(ctx);
    const profiles = await ctx.db
      .query('aiProviderProfiles')
      .withIndex('by_owner', (q) => q.eq('ownerId', ownerId))
      .order('desc')
      .collect();
    const configuredProfiles = profiles.filter(
      (profile) => profile.enabled && isConfigured(profile),
    );
    const explicitDefault = configuredProfiles.find(
      (profile) => (profile as AiProviderProfileWithDefault).isDefault,
    );
    const defaultProfileId =
      explicitDefault?._id ??
      (configuredProfiles.length === 1
        ? configuredProfiles[0]?._id
        : undefined);
    return profiles.map((profile) => publicProfile(profile, defaultProfileId));
  },
});

export const get = query({
  args: { profileId: v.id('aiProviderProfiles') },
  handler: async (ctx, { profileId }) => {
    const ownerId = await getRequiredUserId(ctx);
    const profile = await ctx.db.get(profileId);
    if (profile?.ownerId !== ownerId) {
      throw new ConvexError('AI provider profile not found.');
    }
    const profiles = await ctx.db
      .query('aiProviderProfiles')
      .withIndex('by_owner', (q) => q.eq('ownerId', ownerId))
      .collect();
    const configuredProfiles = profiles.filter(
      (item) => item.enabled && isConfigured(item),
    );
    const explicitDefault = configuredProfiles.find(
      (item) => (item as AiProviderProfileWithDefault).isDefault,
    );
    const defaultProfileId =
      explicitDefault?._id ??
      (configuredProfiles.length === 1
        ? configuredProfiles[0]?._id
        : undefined);
    return publicProfile(profile, defaultProfileId);
  },
});

export const upsertEncryptedInternal = internalMutation({
  args: {
    ownerId: v.id('users'),
    profileId: v.optional(v.id('aiProviderProfiles')),
    name: v.string(),
    provider,
    authType,
    encryptedSecret: v.optional(v.string()),
    secretPreview: v.optional(v.string()),
    baseUrl: v.optional(v.string()),
    defaultModel: v.string(),
    modelOptions: v.optional(v.array(v.string())),
    reasoningEffort,
    enabled: v.boolean(),
  },
  handler: async (ctx, args) => {
    const now = Date.now();
    const patch: Partial<Doc<'aiProviderProfiles'>> = {
      name: args.name.trim() || 'AI provider',
      provider: args.provider,
      authType: args.authType,
      baseUrl: optionalText(args.baseUrl),
      defaultModel: args.defaultModel.trim() || 'gpt-5.5',
      modelOptions: args.modelOptions
        ?.map((model) => model.trim())
        .filter(Boolean),
      reasoningEffort: args.reasoningEffort,
      enabled: args.enabled,
      updatedAt: now,
    };
    if (args.encryptedSecret !== undefined) {
      patch.encryptedSecret = args.encryptedSecret;
      patch.secretPreview = args.secretPreview;
    }
    if (args.profileId) {
      await requireOwnedProfile(ctx, args.profileId, args.ownerId);
      await ctx.db.patch(args.profileId, patch);
      return args.profileId;
    }
    const existingProfiles = await ctx.db
      .query('aiProviderProfiles')
      .withIndex('by_owner', (q) => q.eq('ownerId', args.ownerId))
      .collect();
    const shouldBecomeDefault =
      args.enabled &&
      (args.authType === 'none' || Boolean(args.encryptedSecret)) &&
      existingProfiles.filter(
        (profile) => profile.enabled && isConfigured(profile),
      ).length === 0;
    const profileId = await ctx.db.insert('aiProviderProfiles', {
      ownerId: args.ownerId,
      name: patch.name ?? 'AI provider',
      provider: args.provider,
      authType: args.authType,
      encryptedSecret: args.encryptedSecret,
      secretPreview: args.secretPreview,
      baseUrl: optionalText(args.baseUrl),
      defaultModel: patch.defaultModel ?? 'gpt-5.5',
      modelOptions: patch.modelOptions,
      reasoningEffort: args.reasoningEffort,
      enabled: args.enabled,
      createdAt: now,
      updatedAt: now,
    });
    if (shouldBecomeDefault) {
      await ctx.db.patch(profileId, defaultPatch(true));
    }
    return profileId;
  },
});

export const updateMetadata = mutation({
  args: {
    profileId: v.id('aiProviderProfiles'),
    name: v.string(),
    baseUrl: v.optional(v.string()),
    defaultModel: v.string(),
    modelOptions: v.optional(v.array(v.string())),
    reasoningEffort,
    enabled: v.boolean(),
  },
  handler: async (ctx, args) => {
    const ownerId = await getRequiredUserId(ctx);
    await requireOwnedProfile(ctx, args.profileId, ownerId);
    await ctx.db.patch(args.profileId, {
      name: args.name.trim() || 'AI provider',
      baseUrl: optionalText(args.baseUrl),
      defaultModel: args.defaultModel.trim() || 'gpt-5.5',
      modelOptions: args.modelOptions
        ?.map((model) => model.trim())
        .filter(Boolean),
      reasoningEffort: args.reasoningEffort,
      enabled: args.enabled,
      updatedAt: Date.now(),
    });
    return { success: true };
  },
});

export const remove = mutation({
  args: { profileId: v.id('aiProviderProfiles') },
  handler: async (ctx, { profileId }) => {
    const ownerId = await getRequiredUserId(ctx);
    const profile = (await requireOwnedProfile(
      ctx,
      profileId,
      ownerId,
    )) as AiProviderProfileWithDefault;
    await ctx.db.delete(profileId);
    if (profile.isDefault) {
      const remaining = await ctx.db
        .query('aiProviderProfiles')
        .withIndex('by_owner', (q) => q.eq('ownerId', ownerId))
        .collect();
      const nextDefault = remaining.find(
        (item) => item.enabled && isConfigured(item),
      );
      if (nextDefault) {
        await ctx.db.patch(nextDefault._id, defaultPatch(true));
      }
    }
    return { success: true };
  },
});

export const setDefault = mutation({
  args: { profileId: v.id('aiProviderProfiles') },
  handler: async (ctx, { profileId }) => {
    const ownerId = await getRequiredUserId(ctx);
    const target = await requireOwnedProfile(ctx, profileId, ownerId);
    if (!target.enabled || !isConfigured(target)) {
      throw new ConvexError('Default provider must be enabled and configured.');
    }
    const profiles = await ctx.db
      .query('aiProviderProfiles')
      .withIndex('by_owner', (q) => q.eq('ownerId', ownerId))
      .collect();
    await Promise.all(
      profiles.map((profile) =>
        ctx.db.patch(profile._id, defaultPatch(profile._id === profileId)),
      ),
    );
    return { success: true };
  },
});
