import express, { Request, Response } from "express";
import {
  PrismaClient,
  User,
  RefreshToken,
} from "../../prisma/app/generated/prisma/client";
import { UserLoginDto, UserRegistrationDto } from "../schemas/userSchemas";
import jwt, { TokenExpiredError } from "jsonwebtoken";
import dotenv from "dotenv";
import bcrypt from "bcryptjs";
import { StatusCodes } from "http-status-codes";
import { RefreshTokenPayload } from "../types/tokens";
const app = express();
app.use(express.json());
const prisma = new PrismaClient();
// load environment variables from .env file
dotenv.config();
const JWT_SECRET: string = process.env.TOKEN_SECRET!; // this secret is used to sign the JWT token
const REFRESH_TOKEN_SECRET: string = process.env.REFRESH_TOKEN_SECRET!;

// Generate a JWT token with the username as payload and a secret from the environment variables which expires in 1800 seconds (30 minutes)
function generateAccessToken(
  username: string,
  userId: string,
  role: string,
  refreshTokenId: string
) {
  return jwt.sign(
    { username: username, role: role, sub: userId, jti: refreshTokenId },
    JWT_SECRET,
    {
      expiresIn: "1800s",
      issuer: "VogelApi",
    }
  );
}

async function generateRefreshToken(
  userId: string
): Promise<{ token: string; id: string }> {
  const expiresAt = new Date(Date.now() + 100 * 60 * 60 * 1000); // 100 h
  let refreshToken: RefreshToken;
  {
    refreshToken = await prisma.refreshToken.create({
      data: {
        expiresAt: expiresAt,
        user: {
          connect: {
            id: userId,
          },
        },
      },
    });
  }
  return {
    token: jwt.sign({ jti: refreshToken.id }, REFRESH_TOKEN_SECRET, {
      expiresIn: "100h",
    }),
    id: refreshToken.id,
  };
}

// Endpoint to register a new user
export const registerUser = async (req: Request, res: Response) => {
  const { username, password, email }: UserRegistrationDto = await req.body; //gets the data from the request body

  const existingUser: User | null = await prisma.user.findUnique({
    // check if the user already exists
    where: {
      username: username,
    },
  });
  if (existingUser) {
    // if the user already exists, return an error message
    res.status(StatusCodes.BAD_REQUEST).json({
      error: "Invalid data",
      details: [{ message: `User "${username}" already exists` }],
    });
    return;
  }
  const existingEmailUser: User | null = await prisma.user.findUnique({
    // check if the email is already in use
    where: {
      email: email,
    },
  });
  if (existingEmailUser) {
    // if the email is already in use, return an error message
    res.status(StatusCodes.BAD_REQUEST).json({
      error: "Invalid data",
      details: [{ message: `User with "${email}" already exists` }],
    });
    return;
  }

  const hashedPassword = await bcrypt.hash(password, 10); // hash the password with bcrypt
  if (!hashedPassword) {
    // check if the password was hashed successfully
    res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
      error: "Server error",
      details: [{ message: "Server Error" }],
    });
    return;
  }
  const userData = {
    // create a new user object with the data from the request body and the hashed password
    username: username,
    email: email,
    password: hashedPassword,
  };
  const user: User | null = await prisma.user.create({ data: userData }); // create a new user in the database
  if (!user) {
    // check if the user was created successfully
    res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
      error: "Server error",
      details: [{ message: "Server Error while creating user" }],
    });
    return;
  }
  const refreshToken = await generateRefreshToken(user.id);
  res.set("Refresh-Token", refreshToken.token);
  const token: string = generateAccessToken(
    user.username,
    user.id,
    user.role,
    refreshToken.id
  ); // generate a JWT token with the username and userId as payload
  res.set("Authorization", `Bearer ${token}`); // set the token in the response header
  res.status(StatusCodes.CREATED).json({
    message: "user created",
    data: { username: username, email: email },
  }); // return the user object with the username and email
};

// Endpoint to login a user (unfinished)
export const loginUser = async (req: Request, res: Response) => {
  const { username, password }: UserLoginDto = req.body; // get the data from the request body

  const user: User | null = await prisma.user.findUnique({
    // check if the user exists
    where: {
      username: username,
    },
  });
  if (!user) {
    // if the user does not exist, return an error message
    res.status(StatusCodes.NOT_FOUND).json({
      error: "user not found",
      details: [{ message: `User "${username}" not found` }],
    });
    return;
  }
  const isPasswordValid = await bcrypt.compare(password, user.password); // compare the password with the hashed password in the database
  if (!isPasswordValid) {
    // if the password is not valid, return an error message
    res.status(StatusCodes.UNAUTHORIZED).json({
      error: "invalid credentials",
      details: [{ message: "Invalid password" }],
    });
    return;
  }
  const refreshToken = await generateRefreshToken(user.id);
  res.set("Refresh-Token", refreshToken.token);
  const token: string = generateAccessToken(
    user.username,
    user.id,
    user.role,
    refreshToken.id
  ); // generate a JWT token with the username and userId as payload
  res.set("Authorization", `Bearer ${token}`); // set the token in the response header
  res.status(StatusCodes.OK).json({ message: "User logged in successfully" });
};

export const refreshToken = async (req: Request, res: Response) => {
  const refreshToken: string | undefined = req.headers[
    "refresh-token"
  ] as string;
  if (!refreshToken) {
    res.status(StatusCodes.UNAUTHORIZED).json({
      error: "Unauthorized",
      details: [{ message: "No token provided" }],
    });
    return;
  }

  await jwt.verify(
    refreshToken,
    REFRESH_TOKEN_SECRET,
    async (err: any, decoded: any) => {
      if (err) {
        if (err instanceof jwt.TokenExpiredError) {
          res.status(StatusCodes.UNAUTHORIZED).json({
            error: "Refreshtoken expired",
            details: [{ message: "Refreshtoken has expired" }],
          });
          return;
        }
        res.status(StatusCodes.FORBIDDEN).json({
          error: "Invalid refreshtoken",
          details: [{ message: "Refreshtoken is invalid" }],
        });
        return;
      }

      const payload = decoded as RefreshTokenPayload;
      try {
        const now = new Date();
        const storedToken = await prisma.refreshToken.findUnique({
          where: {
            id: payload.jti,
            expiresAt: {
              gt: now, // expiresAt > now
            },
          },
          include: { user: true },
        });
        if (!storedToken) {
          res.status(StatusCodes.UNAUTHORIZED).json({
            error: "InvalidRefreshToken",
            details: [
              { message: "Refresh token is invalid or no longer exists" },
            ],
          });
          return;
        }
        const existingToken = await prisma.refreshToken.findUnique({
          where: { id: payload.jti },
        });

        if (existingToken) {
          await prisma.refreshToken.deleteMany({
            where: { id: payload.jti },
          });
        }
        const refreshToken = await generateRefreshToken(storedToken.user.id);
        res.set("Refresh-Token", refreshToken.token);
        const token: string = generateAccessToken(
          storedToken.user.username,
          storedToken.user.id,
          storedToken.user.role,
          refreshToken.id
        ); // generate a JWT token with the username and userId as payload
        res.set("Authorization", `Bearer ${token}`); // set the token in the response header
        res.status(StatusCodes.OK).send();
      } catch (error) {
        console.log(error);
        res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
          error: "Server error",
          details: [{ message: "Server Error" }],
        });
        return;
      }
    }
  );
};

export const logout = async (req: Request, res: Response) => {
  const jti: string = req.user!.jti as string;
  try {
    await prisma.refreshToken.delete({ where: { id: jti } });
    res.status(StatusCodes.NO_CONTENT).send();
  } catch (err) {
    console.log(err);
    res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
      error: "Server error",
      details: [{ message: "Server Error" }],
    });
  }
};
