const Sequelize = require("sequelize");
const Products = require("../../models/Products");
const ProductMenu = require("../../models/ProductMenu");
const ProductVariation = require("../../models/ProductVariation");
const ProductSubVariation = require("../../models/ProductSubVariation");
const ProductImages = require("../../models/ProductImages");
const productMapper = require("../../helpers/product-mapper");
const { createAuditTrail } = require("../../helpers/sellerAuditTrail.service");
const {
  sellerEnabledPurchaseOrders,
} = require("../../services/purchase-order.service");
const SellersCompany = require("../../models/SellersCompany");

exports.getAllSellerProducts = async (req, res, next) => {
  try {
    const { seller_ids = [], seller_account_id, parent_company } = req;
    const {
      name_sort = null,
      price_sort = null,
      stock_sort = null,
      product_name = null,
      category = "",
    } = req.query;
    const { limit, page } = req.pagination;

    let sellerIds = Array.isArray(seller_ids) ? [...seller_ids] : [];

    if (!sellerIds.length && req.seller_id) {
      sellerIds = [req.seller_id];
    }

    sellerIds = sellerIds.filter(
      (id) => id !== null && id !== undefined && id !== ""
    );

    if (parent_company && sellerIds.length > 0) {
      const childCompanies = await SellersCompany.findAll({
        where: {
          parent_company: {
            [Sequelize.Op.in]: sellerIds,
          },
        },
        attributes: ["id"],
        raw: true,
      });

      const childCompanyIds = childCompanies.map((company) => company.id);
      sellerIds = [...new Set([...sellerIds, ...childCompanyIds])];
    }

    if (!sellerIds.length) {
      return res.status(200).json({
        success: true,
        products: [],
        pagination: {
          totalPages: 0,
          currentPage: page,
          hasNextPage: false,
          hasPreviousPage: page > 1,
        },
        enabledPO: false,
      });
    }

    const whereClause = {
      seller_id: {
        [Sequelize.Op.in]: sellerIds,
      },
      product_status: true,
      [Sequelize.Op.or]: [{ parent_id: null }, { parent_id: "" }],
    };

    const sellerInclude = {
      model: SellersCompany,
      as: "seller",
    };

    const orderClause = [];

    if (product_name && product_name.trim() !== "") {
      const trimmedProductName = product_name.trim();

      if (parent_company) {
        whereClause[Sequelize.Op.and] = [
          {
            [Sequelize.Op.or]: [
              {
                menu: {
                  [Sequelize.Op.like]: `%${trimmedProductName}%`,
                },
              },
              {
                "$seller.company_name$": {
                  [Sequelize.Op.like]: `%${trimmedProductName}%`,
                },
              },
              {
                "$seller.shop_name$": {
                  [Sequelize.Op.like]: `%${trimmedProductName}%`,
                },
              },
            ],
          },
        ];
      } else {
        whereClause.menu = {
          [Sequelize.Op.like]: `%${trimmedProductName}%`,
        };
      }
    }

    if (category && category !== "" && category !== "null") {
      whereClause.menu_id = category;
    }

    if (name_sort) orderClause.push(["menu", name_sort]);
    if (price_sort) orderClause.push(["price", price_sort]);
    if (stock_sort) orderClause.push(["stocks", stock_sort]);

    if (!name_sort && !price_sort && !stock_sort) {
      orderClause.push(["id", "desc"]);
    }

    const offset = (page - 1) * limit;

    const commonInclude = [
      {
        model: ProductMenu,
        as: "menus",
        attributes: {
          exclude: ["upload_photo", "status"],
        },
      },
      {
        model: ProductImages,
        as: "product_images",
      },
      sellerInclude,
    ];

    const sellerProducts = await Products.findAll({
      where: whereClause,
      limit,
      offset,
      include: commonInclude,
      order: orderClause,
      distinct: true,
      subQuery: false,
    });

    const enabledSellerIds = [];

    for (const sellerId of sellerIds) {
      const isEnabled = await sellerEnabledPurchaseOrders(sellerId);
      if (isEnabled) {
        enabledSellerIds.push(sellerId);
      }
    }

    const _sellerProducts = await productMapper(
      sellerProducts,
      enabledSellerIds
    );

    const productsCount = await Products.count({
      where: whereClause,
      include: [
        {
          model: SellersCompany,
          as: "seller",
          attributes: [],
        },
      ],
      distinct: true,
      col: "id",
    });

    const totalPages = Math.ceil(productsCount / limit);
    const hasNextPage = page < totalPages;
    const hasPreviousPage = page > 1;
    const enabledPO = enabledSellerIds.length > 0;

    await createAuditTrail({
      seller_id: seller_account_id,
      seller_company_id: sellerIds[0] || null,
      module: "Products",
      action: "view",
      action_description: ``,
    });

    res.status(200).json({
      success: true,
      products: _sellerProducts,
      pagination: {
        totalPages,
        currentPage: page,
        hasNextPage,
        hasPreviousPage,
      },
      enabledPO,
    });
  } catch (err) {
    next(err);
  }
};

exports.getSellerProductById = async (req, res, next) => {
  const { seller_id, parent_company } = req;
  const { product_id } = req.params;

  const sellerIds = [seller_id];
  if (parent_company) {
    const childCompanies = await SellersCompany.findAll({
      where: {
        parent_company: seller_id,
      },
      attributes: ["id"],
    });
    childCompanies.forEach((company) => sellerIds.push(company.id));
  }

  Products.findOne({
    where: {
      seller_id: {
        [Sequelize.Op.in]: sellerIds,
      },
      // available: true,
      product_status: true,
      id: product_id,
    },
    include: [
      {
        model: ProductMenu,
        as: "menus",
        attributes: {
          exclude: ["upload_photo", "status"],
        },
      },
      {
        model: ProductVariation,
        as: "product_variations",
        include: [
          {
            model: ProductSubVariation,
            as: "ProductSubVariations",
            required: false,
          },
        ],
      },
      {
        model: ProductImages,
        as: "product_images",
      },
    ],
  }).then((sellerProduct) => {
    if (!sellerProduct)
      return res
        .status(404)
        .json({ success: false, msg: "Product not found." });
    const mappedProduct = productMapper(sellerProduct);
    res.status(200).json({ success: true, product: mappedProduct });
  });
};

exports.createSellerProduct = async (req, res, next) => {
  const { seller_id, seller_account_id, parent_company } = req;
  const { data } = req.body;
  const { product_images } = req.files;
  const { product_images_variation } = req.files;
  const { product_images_sub_variation } = req.files;

  if (parent_company) {
    return res.status(200).json({
      success: false,
      msg: "Parent companies are not allowed to create products.",
    });
  }
  const {
    name,
    description,
    price,
    discounted_price = 0.0,
    quantity_limit,
    stocks,
    available = true,
    sku,
    category_id,
    variations,
  } = data;

  const selectedCategory = await ProductMenu.findOne({
    where: {
      id: category_id,
      status: true,
    },
  });

  if (!selectedCategory)
    return res.status(404).json({ success: false, msg: "Invalid category" });

  Products.create({
    menu_id: selectedCategory.id,
    seller_id,
    menu: name,
    description,
    price,
    discounted_price,
    quantity_limit,
    stocks,
    available,
    sku,
    seller_dashboard_src: true,
  })
    .then(async (createdProduct) => {
      // Product Images
      const productImagesArray = product_images?.map(({ filename, path }) => ({
        product_id: createdProduct.id,
        file_name: filename,
        file_path: path,
      }));
      if (productImagesArray) {
        const productImages =
          await ProductImages.bulkCreate(productImagesArray);
        createdProduct.photo =
          productImagesArray &&
          Array.isArray(productImagesArray) &&
          productImagesArray.length > 0
            ? productImagesArray[0]?.file_name
            : null;
        await createdProduct.save();
      }
      // Product Images

      const productVariations = variations.map(
        async ({
          name,
          description,
          price,
          discounted_price,
          has_sub_variation,
          quantity_limit,
          stocks,
          sku,
          is_available,
          photo,
          product_sub_variation,
        }) => {
          const productImageVariationFile = product_images_variation?.find(
            (variationImage) => variationImage.originalname === photo,
          );
          const createdProductVariation = await ProductVariation.create({
            product_id: createdProduct.id,
            variation: name,
            description,
            quantity_limit,
            price,
            discounted_price,
            hasSubVariation: has_sub_variation,
            isAvailable: is_available,
            variationImage: productImageVariationFile
              ? productImageVariationFile.filename
              : "",
            sku,
            stocks,
          });

          const productSubVariations = product_sub_variation?.map(
            ({
              name,
              description,
              price,
              discounted_price,
              quantity_limit,
              stocks,
              sku,
              is_available,
              photo,
            }) => ({
              product_variation_id: createdProductVariation.id,
              custom: name,
              description,
              price,
              discounted_price,
              quantity_limit,
              stocks,
              sku,
              isAvailable: is_available,
              photo: product_images_sub_variation
                ? product_images_sub_variation?.find(
                    (subVariationImage) =>
                      subVariationImage.originalname === photo,
                  ).filename
                : "",
            }),
          );

          if (productSubVariations) {
            const _productSubVariations =
              await ProductSubVariation.bulkCreate(productSubVariations);
          }
        },
      );

      await createAuditTrail({
        seller_id: seller_account_id,
        seller_company_id: seller_id,
        module: "Products",
        action: "add",
        action_description: `Created new product: ${createdProduct.id}`,
      });
      res.status(201).json({
        success: true,
        msg: "Product created",
        product_id: createdProduct.id,
      });
    })
    .catch((err) => {
      next(err);
    });
};

exports.updateSellerProduct = async (req, res, next) => {
  const { seller_id, seller_account_id, parent_company } = req;
  const { product_id } = req.params;
  const {
    name,
    description,
    price,
    discounted_price,
    quantity_limit,
    stocks,
    available = true,
    sku,
    category_id,
  } = req.body;

  const updateJson = {
    menu: name,
    description: description,
    price: price,
    discounted_price: discounted_price,
    quantity_limit: quantity_limit,
    stocks: stocks,
    available: available,
    sku: sku,
    category_id: category_id,
  };

  try {
    const filteredUpdateJson = Object.fromEntries(
      Object.entries(updateJson).filter(
        ([_, value]) => value != null && value !== "",
      ),
    );

    // Check if there's category id in the filtered update json
    const isUpdatingCategory = !!filteredUpdateJson.category_id;
    if (isUpdatingCategory) {
      const selectedCategory = await ProductMenu.findOne({
        where: {
          id: filteredUpdateJson.category_id,
          status: true,
        },
      });

      if (!selectedCategory)
        return res
          .status(404)
          .json({ success: false, msg: "Invalid category" });
    }

    const sellerIds = [seller_id];
    if (parent_company) {
      const childCompanies = await SellersCompany.findAll({
        where: {
          parent_company: seller_id,
        },
        attributes: ["id"],
      });
      childCompanies.forEach((company) => sellerIds.push(company.id));
    }

    // Check for seller product
    const productQuery = await Products.findOne({
      where: {
        seller_id: {
          [Sequelize.Op.in]: sellerIds,
        },
        id: product_id,
        product_status: true,
      },
    });

    if (!productQuery)
      return res.status(404).json({ success: false, msg: "Invalid product." });

    await Products.update(filteredUpdateJson, {
      where: {
        seller_id: {
          [Sequelize.Op.in]: sellerIds,
        },
        id: product_id,
      },
    });

    await createAuditTrail({
      seller_id: seller_account_id,
      seller_company_id: seller_id,
      module: "Products",
      action: "update",
      action_description: `Update product id: ${product_id}, ${JSON.stringify(
        filteredUpdateJson,
      )}`,
    });

    res.status(200).json({
      success: true,
      msg: "Product updated.",
      updatedProperties: filteredUpdateJson,
    });
  } catch (err) {
    next(err);
  }
};

exports.deleteSellerProduct = async (req, res, next) => {
  const { seller_id, seller_account_id, parent_company } = req;
  const { product_id } = req.params;

  try {
    const sellerIds = [seller_id];
    if (parent_company) {
      const childCompanies = await SellersCompany.findAll({
        where: {
          parent_company: seller_id,
        },
        attributes: ["id"],
      });
      childCompanies.forEach((company) => sellerIds.push(company.id));
    }

    const product = await Products.findOne({
      where: {
        seller_id: {
          [Sequelize.Op.in]: sellerIds,
        },
        id: product_id,
        product_status: true,
      },
    });

    if (!product)
      return res
        .status(404)
        .json({ success: false, msg: "Invalid product id" });

    product.product_status = false;
    await product.save();

    await createAuditTrail({
      seller_id: seller_account_id,
      seller_company_id: seller_id,
      module: "Products",
      action: "delete",
      action_description: `Deleted product id: ${product_id}`,
    });

    res.status(200).json({ success: true });
  } catch (err) {
    next(err);
  }
};

exports.updateProductPhoto = async (req, res, next) => {
  try {
    const { seller_id, parent_company } = req;
    const { product_id, photo_id = "000" } = req.params;

    const sellerIds = [seller_id];
    if (parent_company) {
      const childCompanies = await SellersCompany.findAll({
        where: {
          parent_company: seller_id,
        },
        attributes: ["id"],
      });
      childCompanies.forEach((company) => sellerIds.push(company.id));
    }

    if (!req.files || !req.files.product_image_update) {
      return res
        .status(400)
        .json({ success: false, msg: "Empty product image" });
    }

    const productFile = req.files.product_image_update[0];
    if (!productFile) {
      return res
        .status(400)
        .json({ success: false, msg: "Invalid file upload" });
    }

    const { filename, path } = productFile;

    const productTobeUpdated = await Products.findOne({
      where: {
        seller_id: {
          [Sequelize.Op.in]: sellerIds,
        },
        id: product_id,
        product_status: true,
      },
    });

    if (!productTobeUpdated) {
      return res
        .status(404)
        .json({ success: false, msg: "Invalid product id" });
    }

    if (photo_id !== "000") {
      const productImage = await ProductImages.findByPk(photo_id);
      if (!productImage) {
        return res
          .status(404)
          .json({ success: false, msg: "Invalid product image id" });
      }

      productImage.file_name = filename;
      productImage.file_path = path;
      await productImage.save();

      return res.status(200).json({ success: true });
    }

    productTobeUpdated.photo = filename;
    productTobeUpdated.seller_dashboard_src = true;
    await productTobeUpdated.save();

    return res.status(200).json({ success: true });
  } catch (err) {
    next(err);
  }
};
