text: const bcrypt = require("bcryptjs");
  const jwt = require("jsonwebtoken");
  const { randomBytes } = require("crypto");
  const { promisify } = require("util");
  
  const { transport, emailTemplate } = require("../mail");
  const { hasPermission } = require("../utils");
  const stripe = require("../stripe");
  
  const Mutation = {
    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: {
            user: {
              connect: {
                id: ctx.request.userId
              }
            },
            ...args
          }
        },
        info
      );
  
      return item;
    },
  
    updateItem(parent, args, ctx, info) {
      const updates = { ...args };
      delete updates.id;
  
      return ctx.db.mutation.updateItem(
        {
          data: updates,
          where: {
            id: args.id
          }
        },
        info
      );
    },
  
    async deleteItem(parent, args, ctx, info) {
      const where = { id: args.id };
  
      const item = await ctx.db.query.item({ where }, `{ id title user { id } }`);
  
      const ownsItem = item.user.id === ctx.request.userId;
      const hasPermissions = ctx.request.user.permissions.some(permission =>
        ["ADMIN", "ITEMDELETE"].includes(permission)
      );
  
      if (!ownsItem || !hasPermissions) {
        throw new Error("You don't have permission to do that");
      }
  
      return ctx.db.mutation.deleteItem({ where }, info);
    },
  
    async signup(parent, args, ctx, info) {
      args.email = args.email.toLowerCase();
  
      const password = await bcrypt.hash(args.password, 10);
  
      const user = await ctx.db.mutation.createUser(
        {
          data: {
            ...args,
            password,
            permissions: { set: ["USER"] }
          }
        },
        info
      );
  
      const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
  
      ctx.response.cookie("token", token, {
        httpOnly: true,
        maxAge: 1000 * 60 * 60 * 24 * 365
      });
  
      return user;
    },
  
    async signin(parent, { email, password }, ctx, info) {
      email = email.toLowerCase();
  
      const user = await ctx.db.query.user({
        where: { email }
      });
  
      if (!user) throw new Error(`No user found for email ${email}`);
  
      const valid = await bcrypt.compare(password, user.password);
  
      if (!valid) throw new Error("Invalid password");
  
      const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
  
      ctx.response.cookie("token", token, {
        httpOnly: true,
        maxAge: 1000 * 60 * 60 * 24 * 365
      });
  
      return user;
    },
  
    signout(parent, args, ctx, info) {
      ctx.response.clearCookie("token");
  
      return { message: "Signed out" };
    },
  
    async requestReset(parent, { email }, ctx, info) {
      const user = await ctx.db.query.user({ where: { email } });
  
      if (!user) throw new Error(`No user found for email ${email}`);
  
      const randomBytesPromise = await promisify(randomBytes)(20);
      const resetToken = randomBytesPromise.toString("hex");
      const resetTokenExpiry = Date.now() + 3600000;
  
      await ctx.db.mutation.updateUser({
        where: { email },
        data: { resetToken, resetTokenExpiry }
      });
  
      await transport.sendMail({
        from: "sanj@email.com",
        to: email,
        subject: "Your password reset",
        html: emailTemplate(`
          Click <a href="${
            process.env.FRONTEND_URL
          }/reset?resetToken=${resetToken}">here</a> to reset your password
        `)
      });
  
      return { message: "Password reset email sent" };
    },
  
    async resetPassword(parent, args, ctx, info) {
      const { resetToken, password, confirmPassword } = args;
  
      if (password !== confirmPassword) throw new Error("Passwords do not match");
  
      const [user] = await ctx.db.query.users({
        where: {
          resetToken,
          resetTokenExpiry_gte: Date.now() - 3600000
        }
      });
  
      if (!user) throw new Error("This token is either invalid or expired");
  
      const hashedPassword = await bcrypt.hash(password, 10);
  
      const updatedUser = await ctx.db.mutation.updateUser(
        {
          where: { email: user.email },
          data: {
            password: hashedPassword,
            resetToken: null,
            resetTokenExpiry: null
          }
        },
        info
      );
  
      const token = jwt.sign({ userId: updatedUser.id }, process.env.APP_SECRET);
  
      ctx.response.cookie("token", token, {
        httpOnly: true,
        maxAge: 1000 * 60 * 60 * 24 * 365
      });
  
      return updatedUser;
    },
  
    async updatePermissions(parent, args, ctx, info) {
      if (!ctx.request.userId)
        throw new Error("You must be logged in to do that");
  
      const currentUser = await ctx.db.query.user(
        {
          where: {
            id: ctx.request.userId
          }
        },
        info
      );
  
      hasPermission(currentUser, ["ADMIN", "PERMISSIONUPDATE"]);
  
      return ctx.db.mutation.updateUser(
        {
          where: {
            id: args.userId
          },
          data: {
            permissions: {
              set: args.permissions
            }
          }
        },
        info
      );
    },
  
    async addToCart(parent, args, ctx, info) {
      const { userId } = ctx.request;
  
      if (!userId) throw new Error("You must be logged in to do that");
  
      const [existingCartItem] = await ctx.db.query.cartItems(
        {
          where: {
            item: {
              id: args.id
            },
            user: {
              id: userId
            }
          }
        },
        info
      );
  
      if (existingCartItem) {
        return ctx.db.mutation.updateCartItem(
          {
            where: {
              id: existingCartItem.id
            },
            data: {
              quantity: existingCartItem.quantity + 1
            }
          },
          info
        );
      } else {
        return ctx.db.mutation.createCartItem(
          {
            data: {
              user: {
                connect: { id: userId }
              },
              item: {
                connect: { id: args.id }
              }
            }
          },
          info
        );
      }
    },
  
    async removeFromCart(parent, args, ctx, info) {
      const cartItem = await ctx.db.query.cartItem(
        {
          where: {
            id: args.id
          }
        },
        `{ id, user { id } }`
      );
  
      if (!cartItem) throw new Error("No Cart item found");
  
      if (cartItem.user.id !== ctx.request.userId)
        throw new Error("Not your cart item");
  
      return ctx.db.mutation.deleteCartItem(
        {
          where: {
            id: args.id
          }
        },
        info
      );
    },
  
    async createOrder(parent, args, ctx, info) {
      const { userId } = ctx.request;
  
      if (!userId) throw new Error("You must be logged in to do that");
  
      const user = await ctx.db.query.user(
        {
          where: {
            id: userId
          }
        },
        `{ id name email cart { id quantity item { title price id description image largeImage } } }`
      );
  
      const amount = user.cart.reduce(
        (acc, cartItem) => acc + cartItem.item.price * cartItem.quantity,
        0
      );
  
      const charge = await stripe.charges.create({
        amount,
        currency: "GBP",
        source: args.token
      });
  
      const orderItems = user.cart.map(cartItem => {
        const orderItem = {
          ...cartItem.item,
          quantity: cartItem.quantity,
          user: { connect: { id: userId } }
        };
  
        delete orderItem.id;
  
        return orderItem;
      });
  
      const order = await ctx.db.mutation.createOrder({
        data: {
          total: charge.amount,
          charge: charge.id,
          items: { create: orderItems },
          user: { connect: { id: userId } }
        }
      });
  
      const cartItemIds = user.cart.map(cartItem => cartItem.id);
  
      await ctx.db.mutation.deleteManyCartItems({
        where: { id_in: cartItemIds }
      });
  
      return order;
    }
  };
  
  module.exports = Mutation;
  