import { TRPCError } from "@trpc/server";
import { deleteCookie, getCookies, type OptionsType } from "cookies-next";
import { setCookie } from "cookies-next/server";
import type { Context } from "~/server/api/trpc";
import {
  findUser_byId,
  findUser_byEmail,
  addUser,
  findUser_byId_MINI,
} from "~/server/services/user.service";
import {
  ACCESS_TOKEN_COOKIE_NAME,
  REFRESH_TOKEN_COOKIE_NAME,
  signToken,
} from "~/server/auth/jwt";
import { checkCtx } from "~/server/utils/ctxChecker";
import type { UsersId } from "~/schemas/public/Users";
import { db } from "~/server/db";
import { isBanned } from "~/server/services/banlist.service";
import { env } from "~/env.mjs";
import { add } from "date-fns";
import {
  comparePasswords,
  generateSalt,
  hashPassword,
} from "~/server/services/password.service";

// [...] Cookie options
const cookieOptions: OptionsType = {
  httpOnly: true,
  secure: env.NODE_ENV === "production",
  sameSite: "strict",
  path: "/",
};

const accessTokenCookieOptions: OptionsType = {
  ...cookieOptions,
  expires: add(new Date(), { hours: 1 }),
};

const refreshTokenCookieOptions: OptionsType = {
  ...cookieOptions,
  expires: add(new Date(), { days: 7 }),
};

export const createUserAdmin = async ({
  email,
  password,
  nome,
  cognome,
  telefono,
}: {
  email: string;
  password: string;
  nome: string;
  cognome: string;
  telefono: string;
}) => {
  const banned = await isBanned({ db, data: { email } });
  if (banned) {
    throw new TRPCError({
      code: "FORBIDDEN",
      message: "Utente bannato",
    });
  }

  const salt = generateSalt();
  const hashedPassword = await hashPassword(password, salt);

  const user = await addUser({
    db,
    data: {
      email,
      password: hashedPassword,
      username: `${nome.trim()} ${cognome.trim()}`,
      nome: nome.trim(),
      cognome: cognome.trim(),
      telefono,
      isVerified: true,
      isAdminMade: true,
      salt,
    },
  });
  if (!user) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Errore in fase di registrazione",
    });
  }

  return { status: "success" as const };
};

export const logIn = async ({
  email,
  password,
  ctx: { req, res },
}: {
  email: string;
  password: string;
  ctx: Context;
}) => {
  try {
    checkCtx({ req, res });

    // Get the user from the collection
    const user = await findUser_byEmail({ email });

    if (!user) {
      throw new TRPCError({
        code: "NOT_FOUND",
        message: "Utente non trovato",
      });
    }
    //Check if user is blocked or unverified

    if (user.isBlocked) {
      throw new TRPCError({
        code: "FORBIDDEN",
        message: "Utente bloccato",
      });
    }

    // If Admin and password is "changeme", skip password check
    const skipPwCheck = user.isAdmin && user.password == "changeme";

    if (!skipPwCheck) {
      const passworIsMatch = await comparePasswords({
        password,
        salt: user.salt,
        hashedPassword: user.password,
      });

      if (!passworIsMatch) {
        throw new TRPCError({
          code: "BAD_REQUEST",
          message: "Invalid email or password",
        });
      }
    }

    const payload = {
      id: user.id,
      username: user.username,
      email: user.email,
      isAdmin: user.isAdmin,
      isBlocked: user.isBlocked,
      nome: user.nome,
    };

    const accessToken = await signToken(payload, "1h", "accessToken");
    const refreshToken = await signToken(payload, "7d", "refreshToken");

    // Send Access Token in Cookie
    await setCookie(ACCESS_TOKEN_COOKIE_NAME, accessToken, {
      ...accessTokenCookieOptions,
      req,
      res,
    });
    await setCookie(REFRESH_TOKEN_COOKIE_NAME, refreshToken, {
      ...refreshTokenCookieOptions,
      req,
      res,
    });

    const cookies = await getCookies({ req, res });
    if (!cookies) {
      throw new TRPCError({
        code: "INTERNAL_SERVER_ERROR",
        message: "Errore durante il login, cookies non impostati",
      });
    }
    if (!cookies[ACCESS_TOKEN_COOKIE_NAME]) {
      throw new TRPCError({
        code: "INTERNAL_SERVER_ERROR",
        message: "Errore durante il login, access token non impostato",
      });
    }
    if (!cookies[REFRESH_TOKEN_COOKIE_NAME]) {
      throw new TRPCError({
        code: "INTERNAL_SERVER_ERROR",
        message: "Errore durante il login, refresh token non impostato",
      });
    }
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: `Errore login: ${(e as Error).message}`,
    });
  }
};

export const swapAuth = async ({
  ctx: { req, res },
  userId,
}: {
  ctx: Context;
  userId: UsersId;
}) => {
  checkCtx({ req, res });
  try {
    const user = await findUser_byId_MINI({ userId });
    if (!user) {
      throw new TRPCError({
        code: "NOT_FOUND",
        message: "Utente non trovato",
      });
    }

    const accessToken = await signToken(user, "1h", "accessToken");
    const refreshToken = await signToken(user, "7d", "refreshToken");

    // Send Access Token in Cookie
    await setCookie(ACCESS_TOKEN_COOKIE_NAME, accessToken, {
      ...accessTokenCookieOptions,
      req,
      res,
    });
    await setCookie(REFRESH_TOKEN_COOKIE_NAME, refreshToken, {
      ...refreshTokenCookieOptions,
      req,
      res,
    });
  } catch {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Errore interno",
    });
  }
};

export const logOut = async ({ ctx: { req, res } }: { ctx: Context }) => {
  checkCtx({ req, res });
  try {
    await deleteCookie(ACCESS_TOKEN_COOKIE_NAME, { req, res });
    await deleteCookie(REFRESH_TOKEN_COOKIE_NAME, { req, res });
    return { status: "success" };
  } catch (err: unknown) {
    console.error(err);
    throw err;
  }
};

export const passwordChange = async ({
  userId,
  oldpassword,
  newpassword,
}: {
  userId: UsersId;
  oldpassword: string;
  newpassword: string;
}) => {
  const dbUser = await findUser_byId({ id: userId });
  if (!dbUser) {
    throw new TRPCError({
      code: "NOT_FOUND",
      message: "Utente non trovato",
    });
  }
  const banned = await isBanned({ db, data: { email: dbUser.email } });
  if (banned) {
    throw new TRPCError({
      code: "FORBIDDEN",
      message: "Utente bannato",
    });
  }

  const isPasswordCorrect = await comparePasswords({
    password: oldpassword,
    salt: dbUser.salt,
    hashedPassword: dbUser.password,
  });
  if (!isPasswordCorrect) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: "Password errata",
    });
  }

  const salt = generateSalt();
  const hashedPassword = await hashPassword(newpassword, salt);
  await db
    .updateTable("users")
    .set({
      password: hashedPassword,
      salt,
    })
    .where("id", "=", userId)
    .execute();
  return {
    status: "success",
  };
};
