text: const bcrypt = require("bcryptjs");
  const jwt = require("jsonwebtoken");
  const { randomBytes } = require("crypto");
  const { promisify } = require("util");
  
  const { hasPermission } = require("../utils");
  const stripe = require("../stripe");
  const { transport, makeANiceEmail } = require("../mail");
  
  const Mutations = {
    async createItem(parent, args, ctx, info) {
      if (!ctx.request.userId) {
        throw new Error("You must be logged in to do that!");
      }
  
      const item = await ctx.db.mutation.createItem(
        {
          data: {
            // This is how to create a relationship between the Item and the User
            user: {
              connect: {
                id: ctx.request.userId
              }
            },
            ...args
          }
        },
        info
      );
  
      return item;
    },
  
    async updateItem(parent, args, ctx, info) {
      const where = { id: args.id };
      // 1. find the item
      const item = await ctx.db.query.item({ where }, `{id title user { id }}`);
      // 2. Check if they own the item
      const ownsItem = item.user.id === ctx.request.userId;
      // TODO check if they have the permissions
      if (!ownsItem) {
        throw new Error("You don't have permission to do that!");
      }
      // 3. Take a copy of the updates
      const updates = { ...args };
      // 4. remove id from updates
      delete updates.id;
      // 5. run update method
      return ctx.db.mutation.updateItem(
        {
          data: updates,
          where
        },
        info
      );
    },
    async deleteItem(parent, args, ctx, info) {
      const where = { id: args.id };
      // 1. find the item
      const item = await ctx.db.query.item({ where }, `{id title user { id }}`);
      // 2. Check if they own the item or have the permissions
      const ownsItem = item.user.id === ctx.request.userId;
  
      const hasPermissions = ctx.request.user.permissions.some(permission =>
        ["ADMIN", "ITEMDELETE", "USER"].includes(permission)
      );
  
      if (!ownsItem || !hasPermissions) {
        throw new Error("You don't have permission to do that!");
      }
  
      // 3. Delete it
      return ctx.db.mutation.deleteItem({ where }, info);
    },
    async signup(parent, args, ctx, info) {
      // lowercase their email
      args.email = args.email.toLowerCase();
      // hash their password
      const password = await bcrypt.hash(args.password, 10);
      // create the user in the database
      const user = await ctx.db.mutation.createUser(
        {
          data: {
            ...args,
            password,
            permissions: { set: ["USER"] }
          }
        },
        info
      );
      // create JWT token for them
      const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
      // We set the jwt as a cookie on the response
      ctx.response.cookie("token", token, {
        httpOnly: true,
        maxAge: 1000 * 60 * 60 * 24 * 365 // 1 year cookie
      });
      // Finally we return the user to the browser
      return user;
    },
  
    async signin(parent, { email, password }, ctx, info) {
      // 1. Check if there is a user with that email
      const user = await ctx.db.query.user({ where: { email } });
      if (!user) {
        // The error thrown will be caught on the front end Mutation/Query elements
        throw new Error(`No such user found for email ${email}`);
      }
      // 2. Check if the password is correct
      const valid = await bcrypt.compare(password, user.password);
      if (!valid) {
        throw new Error(`Invalid Password!`);
      }
      // 3. Generate the JWT Token
      const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
      // 4. Set the cookie with the token
      ctx.response.cookie("token", token, {
        httpOnly: true,
        maxAge: 1000 * 60 * 60 * 24 * 365 // 1 year cookie
      });
      // 5. Return the user
      return user;
    },
    signout(parent, args, ctx, info) {
      ctx.response.clearCookie("token");
      return {
        message: "Goodbye!"
      };
    },
    async requestReset(parent, args, ctx, info) {
      // 1. Check if this is a real user
      const user = await ctx.db.query.user({ where: { email: args.email } });
      if (!user) {
        throw new Error(`No such user found for email ${args.email}`);
      }
      // 2. Set a token and expiry on that user
      const randomBytesPromisified = promisify(randomBytes);
      const resetToken = (await randomBytesPromisified(20)).toString("hex");
      const resetTokenExpiry = Date.now() + 3600000; // 1h from now
      const res = await ctx.db.mutation.updateUser({
        where: { email: args.email },
        data: { resetToken, resetTokenExpiry }
      });
  
      // 3. Email them that reset token
      const mailRes = await transport.sendMail({
        from: "jorgemdramos@gmail.com",
        to: user.email,
        subject: "Your password reset link",
        html: makeANiceEmail(
          `Your password reset Token is here! \n \n <a href="${
            process.env.FRONTEND_URL
          }/reset?resetToken=${resetToken}">Click Here to Reset</a>`
        )
      });
  
      // 4. Return the message
      return { message: "Thanks!" };
    },
    async resetPassword(parent, args, ctx, info) {
      // 1. Check if the passwords match
      if (args.password !== args.confirmPassword) {
        throw new Error("Passwords don't match");
      }
      // 2. Check if it's a legit reset token
      // 3. Check if it's expired
      const [user] = await ctx.db.query.users({
        where: {
          resetToken: args.resetToken,
          resetTokenExpiry_gte: Date.now() - 3600000
        }
      });
      if (!user) {
        throw new Error("This token is either invalid or expired");
      }
      // 4. Hash their new pasword
      const password = await bcrypt.hash(args.password, 10);
      // 5. Save the new password to the user and remove old resetToken and fields
      const updatedUser = await ctx.db.mutation.updateUser({
        where: { email: user.email },
        data: {
          password,
          resetToken: null,
          resetTokenExpiry: null
        }
      });
      // 6. Generate JWT
      const token = jwt.sign({ userId: updatedUser.id }, process.env.APP_SECRET);
      // 7. Set JWT cookie
      ctx.response.cookie("token", token, {
        httpOnly: true,
        maxAge: 1000 * 60 * 60 * 24 * 365 // 1 year cookie
      });
      // 8. Return new user
      return updatedUser;
    },
    async updatePermissions(parent, args, ctx, info) {
      // 1. Check if they are logged in
      if (!ctx.request.userId) {
        throw new Error("You must be logged in!");
      }
      // 2. Query the current user
      const currentUser = await ctx.db.query.user(
        {
          where: {
            id: ctx.request.userId
          }
        },
        info
      );
      // 3. Check if they have permissions to do this
      hasPermission(currentUser, ["ADMIN", "PERMISSIONUPDATE"]);
      // 4. Update the permissions
      return ctx.db.mutation.updateUser(
        {
          data: {
            permissions: {
              set: args.permissions
            }
          },
          where: {
            id: args.userId
          }
        },
        info
      );
    },
    async addToCart(parent, args, ctx, info) {
      // 1. Make sure they are signed in
      const { userId } = ctx.request;
      if (!userId) {
        throw new Error("You must be signed in!");
      }
      // 2. Query the users current cart
      const [existingCartItem] = await ctx.db.query.cartItems({
        where: {
          user: { id: userId },
          item: { id: args.id }
        }
      });
      // 3. Check if that item is already in their cart and increment by 1 if it is
      if (existingCartItem) {
        return ctx.db.mutation.updateCartItem(
          {
            where: { id: existingCartItem.id },
            data: { quantity: existingCartItem.quantity + 1 }
          },
          info
        );
      }
      // 4. If it's not create a fresh cartItem for that user
      return ctx.db.mutation.createCartItem(
        {
          data: {
            user: {
              connect: { id: userId }
            },
            item: {
              connect: { id: args.id }
            }
          }
        },
        info
      );
    },
    async removeFromCart(parent, args, ctx, info) {
      //1. Find the cart item
      const cartItem = await ctx.db.query.cartItem(
        {
          where: {
            id: args.id
          }
        },
        `{id, user { id }}`
      );
      //1.5 Make sure we found an item
      if (!cartItem) throw new Error("No cart item found");
      //2. Make sure they own that cart item
      if (cartItem.user.id !== ctx.request.userId) {
        throw new Error("Cheating huhhhhhh");
      }
      //3. Delete cart item
      return ctx.db.mutation.deleteCartItem(
        {
          where: { id: args.id }
        },
        info
      );
    },
    async createOrder(parent, args, ctx, info) {
      // 1. Query current user and make sure they are signed in
      const { userId } = ctx.request;
      if (!userId)
        throw new Error("You must be signed in to complete this order.");
      const user = await ctx.db.query.user(
        { where: { id: userId } },
        `{
          id
          name
          email
          cart {
            id
            quantity
            item {
              title
              price
              id
              description
              image
              largeImage
            }
          }}`
      );
      // 2. Recalculate the total for the price
      const amount = user.cart.reduce(
        (tally, cartItem) => tally + cartItem.item.price * cartItem.quantity,
        0
      );
      console.log(`Going to charge for a total of ${amount}`);
      // 3. Create stripe charge (turn token into $$$)
      const charge = await stripe.charges.create({
        amount,
        currency: "USD",
        source: args.token
      });
      // 4. Convert cart items to order items
      const orderItems = user.cart.map(cartItem => {
        const orderItem = {
          ...cartItem.item,
          quantity: cartItem.quantity,
          user: { connect: { id: userId } }
        };
        delete orderItem.id;
        return orderItem;
      });
  
      // 5. Create the order
      const order = await ctx.db.mutation.createOrder({
        data: {
          total: charge.amount,
          charge: charge.id,
          items: { create: orderItems },
          user: { connect: { id: userId } }
        }
      });
      // 6. Clean up - clear the users cart, delete cartItems
      const cartItemIds = user.cart.map(cartItem => cartItem.id);
      await ctx.db.mutation.deleteManyCartItems({
        where: { id_in: cartItemIds }
      });
      // 7. Return the Order to the client
      return order;
    }
  };
  
  module.exports = Mutations;
  