import Authentik from '@auth/core/providers/authentik';
import GitHub from '@auth/core/providers/github';
import {
  convexAuth,
  getAuthUserId,
  modifyAccountCredentials,
  retrieveAccount,
} from '@convex-dev/auth/server';
import { ConvexError, v } from 'convex/values';

import type { Doc, Id } from './_generated/dataModel';
import type { QueryCtx } from './_generated/server';
import { api } from './_generated/api';
import { action, mutation, query } from './_generated/server';
import { Password, validatePassword } from './custom/auth';

export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
  providers: [
    Authentik({
      allowDangerousEmailAccountLinking: true,
      clientId: process.env.AUTH_AUTHENTIK_ID,
      clientSecret: process.env.AUTH_AUTHENTIK_SECRET,
      issuer: process.env.AUTH_AUTHENTIK_ISSUER,
    }),
    GitHub({
      allowDangerousEmailAccountLinking: true,
      clientId: process.env.AUTH_GITHUB_ID ?? process.env.GITHUB_APP_CLIENT_ID,
      clientSecret:
        process.env.AUTH_GITHUB_SECRET ?? process.env.GITHUB_APP_CLIENT_SECRET,
    }),
    Password,
  ],
});

const getUserById = async (
  ctx: QueryCtx,
  userId: Id<'users'>,
): Promise<Doc<'users'> | null> => {
  const user = await ctx.db.get(userId);
  return user ?? null;
};
const getAuthAccountById = async (ctx: QueryCtx, userId: Id<'users'>) => {
  const user = await ctx.db.get(userId);
  if (!user) return null;
  const authAccount = await ctx.db
    .query('authAccounts')
    .withIndex('userIdAndProvider', (q) => q.eq('userId', userId))
    .order('desc')
    .first();
  return authAccount ?? null;
};

const getCurrentUserId = async (ctx: QueryCtx) => {
  try {
    return await getAuthUserId(ctx);
  } catch {
    return null;
  }
};
export const getUserProvider = query({
  args: { userId: v.optional(v.id('users')) },
  handler: async (ctx, args) => {
    const userId = args.userId ?? (await getCurrentUserId(ctx));
    if (!userId) return null;
    const authAccount = await getAuthAccountById(ctx, userId);
    return authAccount?.provider ?? null;
  },
});

export const getUser = query({
  args: { userId: v.optional(v.id('users')) },
  handler: async (ctx, args) => {
    const userId = args.userId ?? (await getCurrentUserId(ctx));
    if (!userId) return null;
    return getUserById(ctx, userId);
  },
});

export const updateUser = mutation({
  args: {
    name: v.optional(v.string()),
    email: v.optional(v.string()),
    image: v.optional(v.id('_storage')),
  },
  handler: async (ctx, args) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new ConvexError('Not authenticated.');
    const user = await ctx.db.get(userId);
    if (!user) throw new ConvexError('User not found.');
    const patch: Partial<{
      name: string;
      email: string;
      image: Id<'_storage'>;
    }> = {};
    if (args.name !== undefined) patch.name = args.name;
    if (args.email !== undefined) patch.email = args.email;
    if (args.image !== undefined) {
      const oldImage = user.image as Id<'_storage'> | undefined;
      patch.image = args.image;
      if (
        oldImage &&
        oldImage !== args.image &&
        !oldImage.startsWith('http://') &&
        !oldImage.startsWith('https://')
      ) {
        await ctx.storage.delete(oldImage);
      }
    }
    if (Object.keys(patch).length > 0) {
      await ctx.db.patch(userId, patch);
    }
    return { success: true };
  },
});

export const updateUserPassword = action({
  args: {
    currentPassword: v.string(),
    newPassword: v.string(),
  },
  handler: async (ctx, { currentPassword, newPassword }) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new ConvexError('Not authenticated.');
    const user = await ctx.runQuery(api.auth.getUser, { userId });
    if (!user?.email) throw new ConvexError('User not found.');
    await retrieveAccount(ctx, {
      provider: 'password',
      account: { id: user.email, secret: currentPassword },
    });

    if (!validatePassword(newPassword))
      throw new ConvexError('Invalid password.');

    await modifyAccountCredentials(ctx, {
      provider: 'password',
      account: { id: user.email, secret: newPassword },
    });

    return { success: true };
  },
});
