import bcrypt from "bcryptjs";
import { createCookieSessionStorage, redirect } from "remix";
import { db } from "./db.server";

type LoginForm = {
  username: string;
  password: string;
};

type RegisterForm = {
  username: string;
  password: string;
  icon: string;
  teamId: string;
};

type UpdateUserForm = {
  id: string;
  password?: string;
  icon?: string;
  teamId?: string;
};

export async function register({
  username,
  password,
  icon,
  teamId,
}: RegisterForm) {
  const passwordHash = await bcrypt.hash(password, 10);
  const team = await db.team.findUnique({ where: { id: teamId } });
  if (!team) {
    await db.team.create({
      data: {
        id: teamId,
        icon: teamId[0],
      },
    });
  }
  const user = await db.user.create({
    data: { username, passwordHash, icon: icon ?? username[0], teamId },
  });
  return user;
}

export async function updateUser({ id, ...data }: UpdateUserForm) {
  if (data.teamId) {
    const team = await db.team.findUnique({ where: { id: data.teamId } });
    if (!team) {
      await db.team.create({
        data: {
          id: data.teamId,
          icon: data.teamId[0],
        },
      });
    }
  }

  const passwordHash = await bcrypt.hash(data.password ?? "", 10);
  const user = await db.user.update({
    data: {
      ...(data?.icon ? { icon: data.icon } : {}),
      ...(data?.password ? { passwordHash } : {}),
      ...(data?.teamId ? { teamId: data.teamId } : {}),
    },
    where: { id },
  });
  return user;
}

export async function deleteUser(request: Request) {
  const userId = await getUserId(request);
  if (!userId) return null;

  const deletedUser = await db.user.delete({ where: { id: userId } });
  if (!deletedUser) return null;

  const session = await storage.getSession(request.headers.get("Cookie"));
  return redirect("/", {
    headers: {
      "Set-Cookie": await storage.destroySession(session),
    },
  });
}

export async function login({ username, password }: LoginForm) {
  const user = await db.user.findUnique({
    where: { username },
  });
  if (!user) return null;
  const isCorrectPassword = await bcrypt.compare(password, user.passwordHash);
  if (!isCorrectPassword) return null;
  return user;
}

const sessionSecret = process.env.SESSION_SECRET;
if (!sessionSecret) {
  throw new Error("SESSION_SECRET must be set");
}

const storage = createCookieSessionStorage({
  cookie: {
    name: "RJ_session",
    // normally you want this to be `secure: true`
    // but that doesn't work on localhost for Safari
    // https://web.dev/when-to-use-local-https/
    secure: process.env.NODE_ENV === "production",
    secrets: [sessionSecret],
    sameSite: "lax",
    path: "/",
    maxAge: 60 * 60 * 24 * 30,
    httpOnly: true,
  },
});

export function getUserSession(request: Request) {
  return storage.getSession(request.headers.get("Cookie"));
}

export async function getUserId(request: Request) {
  const session = await getUserSession(request);
  const userId = session.get("userId");
  if (!userId || typeof userId !== "string") return null;
  return userId;
}

export async function requireUserId(
  request: Request,
  redirectTo: string = new URL(request.url).pathname
) {
  const session = await getUserSession(request);
  const userId = session.get("userId");
  if (!userId || typeof userId !== "string") {
    const searchParams = new URLSearchParams([["redirectTo", redirectTo]]);
    throw redirect(`/login?${searchParams}`);
  }
  return userId;
}

export async function getUser(request: Request) {
  const userId = await getUserId(request);
  if (typeof userId !== "string") {
    return null;
  }

  try {
    const user = await db.user.findUnique({
      where: { id: userId },
      include: {
        team: {
          include: {
            members: true,
          },
        },
      },
    });
    return user;
  } catch {
    throw logout(request);
  }
}

export async function getUsersByTeam(request: Request) {
  const user = await getUser(request);
  if (!user) {
    return null;
  }

  try {
    const users = await db.user.findMany({
      where: { teamId: user.teamId },
    });
    return users;
  } catch {
    throw logout(request);
  }
}

export async function logout(request: Request) {
  const session = await storage.getSession(request.headers.get("Cookie"));
  return redirect("/", {
    headers: {
      "Set-Cookie": await storage.destroySession(session),
    },
  });
}

export async function createUserSession(userId: string, redirectTo: string) {
  const session = await storage.getSession();
  session.set("userId", userId);
  return redirect(redirectTo, {
    headers: {
      "Set-Cookie": await storage.commitSession(session),
    },
  });
}
