import { ConvexError, v } from 'convex/values';
import {
  convexAuth,
  getAuthUserId,
  retrieveAccount,
  modifyAccountCredentials,
} from '@convex-dev/auth/server';
import { api } from './_generated/api';
import { type Id } from './_generated/dataModel';
import {
  action,
  mutation,
  query,
  type MutationCtx,
  type QueryCtx,
} from './_generated/server';
import Authentik from '@auth/core/providers/authentik';
import { Entra, Password, validatePassword } from './custom/auth';

export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
  providers: [
    Authentik({ allowDangerousEmailAccountLinking: true }),
    Entra,
    Password,
  ],
});

export const PASSWORD_MIN = 8;
export const PASSWORD_MAX = 100;
export const PASSWORD_REGEX =
  /^(?=.{8,100}$)(?!.*\s)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\p{P}\p{S}]).*$/u;

type RWCtx = MutationCtx | QueryCtx;
type User = {
  id: Id<'users'>;
  email: string | null;
  name: string | null;
  image: Id<'_storage'> | null;
  lunchTime: string | null;
  automaticLunch: boolean;
  provider: string | null;
};

const getUserData = async (ctx: RWCtx, userId: Id<'users'>): Promise<User> => {
  const user = await ctx.db.get(userId);
  if (!user) throw new ConvexError('User not found.');

  const image: Id<'_storage'> | null =
    typeof user.image === 'string' && user.image.length > 0
      ? (user.image as Id<'_storage'>)
      : null;

  const authAccount = await getUserAuthAccountData(ctx, userId);

  return {
    id: user._id,
    email: user.email ?? null,
    name: user.name ?? null,
    image,
    lunchTime: user.lunchTime ?? null,
    automaticLunch: user.automaticLunch ?? false,
    provider: authAccount?.provider ?? null,
  };
};

const getUserAuthAccountData = async (ctx: RWCtx, userId: Id<'users'>) => {
  const user = await ctx.db.get(userId);
  if (!user) throw new ConvexError('User not found.');
  const authAccountData = await ctx.db
    .query('authAccounts')
    .withIndex('userIdAndProvider', (q) => q.eq('userId', userId))
    .first();
  return authAccountData;
};

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

export const getUserAuthAccount = query(async (ctx) => {
  const userId = await getAuthUserId(ctx);
  if (!userId) return null;
  return getUserAuthAccountData(ctx, userId);
});

export const getAllUsers = query(async (ctx) => {
  const users = await ctx.db.query('users').collect();
  return Promise.all(users.map((u) => getUserData(ctx, u._id)));
});

export const getAllUserIds = query(async (ctx) => {
  const users = await ctx.db.query('users').collect();
  return users.map((u) => u._id);
});

export const updateUser = mutation({
  args: {
    name: v.optional(v.string()),
    email: v.optional(v.string()),
    image: v.optional(v.id('_storage')),
    lunchTime: v.optional(v.string()),
    automaticLunch: v.optional(v.boolean()),
  },
  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.');
    if (args.lunchTime !== undefined && !args.lunchTime.includes(':')) {
      throw new ConvexError('Lunch time is invalid.');
    }
    const patch: Partial<{
      name: string;
      email: string;
      image: Id<'_storage'>;
      lunchTime: string;
      automaticLunch: boolean;
    }> = {};

    if (args.name !== undefined) patch.name = args.name;
    if (args.email !== undefined) patch.email = args.email;
    if (args.lunchTime !== undefined) patch.lunchTime = args.lunchTime;
    if (args.automaticLunch !== undefined)
      patch.automaticLunch = args.automaticLunch;

    if (args.image !== undefined) {
      const oldImage = user.image as Id<'_storage'> | undefined;
      patch.image = args.image;
      if (oldImage && oldImage !== args.image) {
        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.');
    const verified = await retrieveAccount(ctx, {
      provider: 'password',
      account: { id: user.email, secret: currentPassword },
    });
    if (!verified) throw new ConvexError('Current password is incorrect.');

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

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

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