import { scryptSync, randomBytes } from "node:crypto";
import { database } from "@/db/database";
import { token, user } from "@/db/schema";
import { countDistinct, eq, sql } from "drizzle-orm";
import { PAGINATION } from "@/db/constants";
import { nanoid } from "nanoid";
import { compare, genSalt, hash } from "bcrypt";

const SALT_ROUND = Number(import.meta.env.SALT_ROUND ?? 10);

export const hashPassword = async (password: string) => {
  const salt = await genSalt(SALT_ROUND);
  return await hash(password, salt);
};

export const verifyPassword = async ({
  password,
  hash,
}: {
  password: string;
  hash: string;
}) => {
  return await compare(password, hash);
};

export const validateCredentials = async ({
  email,
  password,
}: {
  email: string;
  password: string;
}) => {
  const user = await getOneUserEmail(email);

  const match = user?.password
    ? await verifyPassword({ password, hash: user.password })
    : undefined;
  if (!match) {
    return null;
  }

  return user;
};

export const newUser = async ({
  firstName,
  lastName,
  email,
  password,
  username,
}: {
  firstName: string;
  lastName: string;
  email: string;
  password: string;
  username?: string;
}) => {
  try {
    return await database
      .insert(user)
      .values({
        lastName,
        firstName,
        email,
        password,
        username: username ?? firstName.trim().replace(" ", ".").toLowerCase(),
      })
      .prepare()
      .execute();
  } catch (error) {
    console.log(error);
    throw new Error(JSON.stringify(error, null, 2));
  }
};

export const updateUser = async (
  data: Partial<{
    firstName: string;
    lastName: string;
    email: string;
    username: string;
    address: string;
    phone: string;
  }>,
  id: number
) => {
  try {
    return await database
      .update(user)
      .set(data)
      .where(eq(user.id, id))
      // .values(data)
      .prepare()
      .execute();
  } catch (error) {
    console.log(error);
    throw new Error(JSON.stringify(error, null, 2));
  }
};

export const deleteUsers = async (id: number) => {
  try {
    return await database
      .delete(user)
      .where(eq(user.id, id))
      .prepare()
      .execute();
  } catch (error) {
    console.log(error);
    throw new Error(JSON.stringify(error, null, 2));
  }
};

export const changeUserPassword = async ({
  email,
  password,
}: {
  email: string;
  password: string;
}) => {
  try {
    const hash = await hashPassword(password);
    return await database
      .update(user)
      .set({ password: hash })
      .where(eq(user.email, email))
      .prepare()
      .execute();
  } catch (error) {
    console.log(error);
    throw new Error(JSON.stringify(error, null, 2));
  }
};

export const getAllUsers = async (page = 1) => {
  try {
    return await database.transaction(async (tx) => {
      const users = await tx.query.user
        .findMany({
          offset: (page - 1) * PAGINATION,
          limit: page * PAGINATION,
          orderBy: user.createdAt,
        })
        .prepare()
        .execute();
      const amount = await tx
        .select({ total: countDistinct(user.id) })
        .from(user)
        .prepare()
        .execute();

      const total = amount.map((e) => e.total).reduce((a, b) => a + b);

      return {
        total,
        users,
        page,
        total_pages: Math.ceil(total / PAGINATION),
      };
    });
    // return await database.query.user.findMany().prepare().execute();
  } catch (error) {
    console.log(error);
    throw new Error(JSON.stringify(error, null, 2));
  }
};

export const getOneUser = async (id: number) => {
  try {
    return await database.query.user
      .findFirst({
        where: (user, { eq }) => eq(user.id, id),
      })
      .prepare()
      .execute();
  } catch (error) {
    console.log(error);
    throw new Error("User was not found");
  }
};

export const getOneUserEmail = async (email: string) => {
  try {
    return await database.query.user
      .findFirst({
        where: (user, { eq }) => eq(user.email, email),
      })
      .prepare()
      .execute();
  } catch (error) {
    console.log(error);
    throw new Error("User was not found");
  }
};

export const createToken = async (email: string) => {
  try {
    const now = new Date();
    const rand = nanoid();
    const reset_token = `${rand}_${now.getTime().toString(16)}`;
    const expiredAt = new Date(new Date().setHours(now.getHours() + 2));

    const t = await database.transaction(async (tx) => {
      await tx.delete(token).where(eq(token.email, email)).prepare().execute();
      await tx
        .delete(token)
        .where(sql`${token.expiredAt} < ${new Date()}`)
        .prepare()
        .execute();

      const u = tx.query.user.findFirst({
        where: (user, { eq }) => eq(user.email, email),
      });

      if (!u) {
        throw new Error("User was not found");
      }
      await tx
        .insert(token)
        .values({
          email,
          expiredAt,
          token: reset_token,
        })
        .prepare()
        .execute();

      return u;
    });

    return { token: reset_token, user: t };
  } catch (error) {
    console.log(error);
    throw new Error(JSON.stringify(error, null, 2));
  }
};

export const getToken = async (reset_token: string) => {
  try {
    const t = await database.query.token.findFirst({
      where: (token, { eq }) => eq(token.token, reset_token),
    });
    return t;
  } catch (error) {
    console.log(error);
    throw new Error(JSON.stringify(error, null, 2));
  }
};

export const verifyToken = async ({
  token,
  email,
}: {
  token: string;
  email: string;
}) => {
  try {
    const reset_token = await getToken(token);
    const now = new Date();
    if (
      !reset_token ||
      reset_token.email !== email ||
      reset_token.expiredAt === null ||
      reset_token.expiredAt! < now
    ) {
      return false;
    }
    return true;
  } catch (error) {
    console.log(error);
    throw new Error(JSON.stringify(error, null, 2));
  }
};
