const Account = require('../models/account.model');
const Product = require('../models/product.model');
const Category = require('../models/category.model');
const Bid = require('../models/bid.model');
const { uploadImage } = require('../utils/cloudinary');
const mongoose = require('mongoose');

/**
 * Product Service
 * @module services/ProductService
 * @description Handles all business logic related to products
 */
class ProductService {
  /**
   * Create a new product
   * @async
   * @param {Object} productData - Product data
   * @returns {Promise<Object>} Created product
   * @throws {Error} If validation fails or database operation fails
   */
  static async createProduct(productData) {
    try {
      // Validate required fields
      if (!productData.userId) {
        throw new Error('User ID is required');
      }
      if (!productData.price || productData.price <= 0) {
        throw new Error('Valid price is required');
      }

      // Validate user exists
      const user = await Account.findById(productData.userId);
      if (!user) {
        throw new Error('User not found');
      }

      const imageUrl = await uploadImage(productData.file);
      if (!imageUrl) {
        throw new Error('Product image upload failed');
      }

      // Define the model's direct fields (from the schema)
      const modelFields = [
        'userId',
        'brand',
        'name',
        'description',
        'price',
        'imageUrl',
        'otherImages',
        'stock',
        'isActive',
        'swappable',
        'installmentAvailable',
        'installmentTotalAmount',
        'installmentPlans',
        'views',
        'category',
        'conditionTag',
        'verificationStatus',
      ];

      // Separate the data into direct fields and metaData
      const directFields = {};
      const metaData = {};

      Object.keys(productData).forEach((key) => {
        if (modelFields.includes(key)) {
          directFields[key] = productData[key];
        } else if (key !== 'metaData' && key !== 'file' && key !== 'userId') {
          // Exclude 'metaData', 'file', and 'userId' from being nested
          metaData[key] = productData[key];
        }
      });

      directFields.imageUrl = imageUrl;

      // Handle installment options if installment is available
      if (directFields.installmentAvailable) {
        if (
          !directFields.installmentPlans ||
          !directFields.installmentPlans.length
        ) {
          throw new Error(
            'Installment plans are required when installment is available',
          );
        }

        // Validate each installment plan
        directFields.installmentPlans.forEach((plan) => {
          if (!plan.name) throw new Error('Installment plan name is required');
          if (!plan.numberOfPayments || plan.numberOfPayments < 2) {
            throw new Error('Installment plan must have at least 2 payments');
          }
          if (
            !['day', 'week', 'month', 'year'].includes(plan.paymentInterval)
          ) {
            throw new Error('Invalid payment interval');
          }
          if (!plan.intervalCount || plan.intervalCount < 1) {
            throw new Error('Interval count must be at least 1');
          }

          // Calculate and validate installment total amount
          if (
            plan.interestRate &&
            (plan.interestRate < 0 || plan.interestRate > 100)
          ) {
            throw new Error('Interest rate must be between 0 and 100');
          }

          const downPaymentPercentage = plan.downPayment || 0;
          const downPaymentAmount =
            (directFields.price * downPaymentPercentage) / 100;
          const remainingAmount = directFields.price - downPaymentAmount;

          let installmentTotalAmount = remainingAmount;
          if (plan.interestRate) {
            installmentTotalAmount =
              remainingAmount * (1 + plan.interestRate / 100);
          }

          // Calculate total amount paid (down payment + installments)
          const totalPaid = downPaymentAmount + installmentTotalAmount;

          // Validate that installment price isn't ridiculous (max 30% more than cash price)
          const maxAllowed = directFields.price * 1.3; // 30% more than cash price
          if (totalPaid > maxAllowed) {
            throw new Error(
              `Installment total (${totalPaid}) cannot exceed 30% more than cash price (${directFields.price}). Max allowed: ${maxAllowed}`,
            );
          }

          // Store the calculated total amount for this plan
          plan.totalAmount = totalPaid;
        });

        // Set the installment total amount to the highest plan total
        directFields.installmentTotalAmount = Math.max(
          ...directFields.installmentPlans.map((plan) => plan.totalAmount),
        );
      } else {
        // Clear installment-related fields if installment is not available
        directFields.installmentPlans = [];
        directFields.installmentTotalAmount = 0;
      }

      // Set default values
      directFields.isActive =
        directFields.isActive !== undefined ? directFields.isActive : true;
      directFields.swappable =
        directFields.swappable !== undefined ? directFields.swappable : false;
      directFields.views = 0;
      directFields.otherImages = directFields.otherImages || [];
      directFields.conditionTag = directFields.conditionTag || 'GOOD';
      directFields.verificationStatus =
        directFields.verificationStatus || 'VERIFY';
      directFields.approved = false; // Default to not approved

      // Handle metaData - include any existing metaData from productData
      directFields.metaData = {
        ...(productData.metaData || {}),
        ...metaData,
      };

      const product = new Product(directFields);
      await product.save();
      return product;
    } catch (error) {
      throw new Error(`Failed to create product: ${error.message}`);
    }
  }

  /**
   * Get products for user dashboard
   * @param {string} userId - User ID
   * @param {Object} filters - Filter options
   * @param {number} limit - Number of products per page
   * @param {number} skip - Skip for pagination
   * @returns {Promise<Object>} Products and total count
   */
  static async userDashboardProduct(
    userId,
    filters = {},
    limit = 10,
    skip = 0,
  ) {
    try {
      const query = { userId };

      // Apply additional filters
      if (filters.categoryId) {
        query.category = filters.categoryId;
      }
      if (filters.isActive !== undefined) {
        query.isActive = filters.isActive === 'true';
      }
      if (filters.swappable !== undefined) {
        query.swappable = filters.swappable === 'true';
      }
      if (filters.installment !== undefined) {
        query.installmentAvailable = filters.installment === 'true';
      }
      if (filters.search) {
        query.$or = [
          { name: { $regex: filters.search, $options: 'i' } },
          { description: { $regex: filters.search, $options: 'i' } },
          { brand: { $regex: filters.search, $options: 'i' } },
        ];
      }
      if (filters.minPrice) {
        query.price = { $gte: Number.parseFloat(filters.minPrice) };
      }
      if (filters.maxPrice) {
        query.price = {
          ...query.price,
          $lte: Number.parseFloat(filters.maxPrice),
        };
      }

      // Get products
      const products = await Product.find(query)
        .populate('category', 'name')
        .populate('userId', 'firstName lastName')
        .sort({ createdAt: -1 })
        .skip(skip)
        .limit(limit)
        .lean();

      // Get count
      const count = await Product.countDocuments(query);

      // Get bids count for all products
      const productIds = products.map((p) => p._id);

      const bidsCounts =
        productIds.length > 0
          ? await Bid.aggregate([
              { $match: { product: { $in: productIds } } },
              { $group: { _id: '$product', count: { $sum: 1 } } },
            ])
          : [];

      // Convert bids counts to a map for easy lookup
      const bidsCountMap = bidsCounts.reduce((acc, curr) => {
        acc[curr._id.toString()] = curr.count;
        return acc;
      }, {});

      // Format the response
      const formattedProducts = products.map((product) => {
        return {
          ...product,
          id: product._id,
          nameCount: product.name ? product.name.length : 0,
          descriptionCount: product.description
            ? product.description.length
            : 0,
          priceCount: product.price ? 1 : 0,
          categoryCount: product.category ? 1 : 0,
          bidsCount: bidsCountMap[product._id.toString()] || 0,
          viewsCount: product.views || 0,
        };
      });

      return {
        products: formattedProducts,
        count,
      };
    } catch (error) {
      throw new Error(`Failed to get user products: ${error.message}`);
    }
  }

  /**
   * Get product by ID
   * @param {string} id - Product ID
   * @param {string} userId - Current user ID
   * @param {boolean} isAdmin - Whether user is admin
   * @returns {Promise<Object>} Found product
   */
  static async getProductById(id, userId = null, isAdmin = false) {
    try {
      const product = await Product.findById(id)
        .populate('category', 'name')
        .populate('userId', 'firstName lastName verified');

      if (!product) {
        throw new Error('Product not found');
      }

      // Check permissions
      if (
        product.userId._id.toString() !== userId &&
        !product.isActive &&
        !isAdmin
      ) {
        throw new Error('Product not found');
      }

      if (
        product.userId._id.toString() !== userId &&
        !product.approved &&
        !isAdmin
      ) {
        throw new Error('Product not found');
      }

      // Increment view count
      product.views += 1;
      await product.save();

      return product;
    } catch (error) {
      throw new Error(`Failed to get product: ${error.message}`);
    }
  }

  /**
   * Update product by ID
   * @param {string} id - Product ID
   * @param {Object} updateData - Data to update
   * @param {string} userId - Current user ID
   * @returns {Promise<Object>} Updated product
   */
  static async updateProduct(id, updateData, userId) {
    try {
      const product = await Product.findById(id);
      if (!product) {
        throw new Error('Product not found');
      }

      if (product.userId.toString() !== userId) {
        throw new Error('You do not have permission to update this product');
      }

      // Validate category exists if being updated
      if (updateData.categoryId) {
        const category = await Category.findById(updateData.categoryId);
        if (!category) {
          throw new Error('Category not found');
        }
        updateData.category = updateData.categoryId;
        delete updateData.categoryId;
      }

      // Update fields
      Object.keys(updateData).forEach((key) => {
        if (key !== 'id' && key !== '_id') {
          product[key] = updateData[key];
        }
      });

      await product.save();
      return product;
    } catch (error) {
      throw new Error(`Failed to update product: ${error.message}`);
    }
  }

  /**
   * Delete product by ID
   * @param {string} id - Product ID
   * @returns {Promise<boolean>} True if deleted successfully
   */
  static async deleteProduct(id) {
    try {
      const product = await Product.findById(id);
      if (!product) {
        throw new Error('Product not found');
      }

      await product.deleteOne();
      return true;
    } catch (error) {
      throw new Error(`Failed to delete product: ${error.message}`);
    }
  }

  /**
   * List products with optional filters
   * @param {Object} filters - Filter options
   * @param {number} limit - Number of products per page
   * @param {number} skip - Skip for pagination
   * @param {string} userId - Current user ID
   * @param {boolean} isAdmin - Whether user is admin
   * @returns {Promise<Object>} Products and total count
   */
  static async listProducts(
    filters = {},
    limit = 10,
    skip = 0,
    userId = null,
    isAdmin = false,
  ) {
    try {
      const query = {};

      // Standard filters
      if (filters.categoryId) {
        query.category = filters.categoryId;
      }
      if (filters.isActive) {
        query.isActive = filters.isActive === 'true';
      }
      if (filters.swappable) {
        query.swappable = filters.swappable === 'true';
      }
      if (filters.installment) {
        query.installmentAvailable = filters.installment === 'true';
      }
      if (filters.search) {
        query.$or = [
          { name: { $regex: filters.search, $options: 'i' } },
          { description: { $regex: filters.search, $options: 'i' } },
          { brand: { $regex: filters.search, $options: 'i' } },
        ];
      }
      if (filters.minPrice || filters.maxPrice) {
        query.price = {};
        if (filters.minPrice) {
          query.price.$gte = Number.parseFloat(filters.minPrice);
        }
        if (filters.maxPrice) {
          query.price.$lte = Number.parseFloat(filters.maxPrice);
        }
      }

      let finalQuery = {};

      if (isAdmin) {
        // Admins see all, apply only filters
        finalQuery = { ...query };
      } else {
        // Non-admin users: products that are active, approved, and from non-suspended/banned users
        finalQuery = {
          ...query,
          isActive: true,
          approved: true,
        };
      }

      // Get products
      const products = await Product.find(finalQuery)
        .populate('category', 'name')
        .populate(
          'userId',
          'firstName lastName suspended banned badge verified',
        )
        .sort({ createdAt: -1 })
        .skip(skip)
        .limit(limit)
        .lean();

      // Filter out products from suspended/banned users for non-admins
      const filteredProducts = isAdmin
        ? products
        : products.filter(
            (p) => p.userId && !p.userId.suspended && !p.userId.banned,
          );

      // Get count (this is approximate due to the filtering above)
      const count = await Product.countDocuments(finalQuery);

      return { products: filteredProducts, count };
    } catch (error) {
      throw new Error(`Failed to list products: ${error.message}`);
    }
  }

  /**
   * Get products by user ID
   * @param {string} targetUserId - User ID to get products for
   * @param {Object} filters - Filter options
   * @param {number} limit - Number of products per page
   * @param {number} skip - Skip for pagination
   * @param {boolean} isAdmin - Whether user is admin
   * @param {string} owner - Current user ID
   * @returns {Promise<Object>} Products and total count
   */
  static async getProductsByUser(
    targetUserId,
    filters = {},
    limit = 10,
    skip = 0,
    isAdmin = false,
    owner = null,
  ) {
    try {
      let query = { userId: targetUserId };

      // Apply filters
      if (filters.categoryId) {
        query.category = filters.categoryId;
      }
      if (filters.isActive !== undefined) {
        query.isActive = filters.isActive === 'true';
      }
      if (filters.swappable !== undefined) {
        query.swappable = filters.swappable === 'true';
      }
      if (filters.installment !== undefined) {
        query.installmentAvailable = filters.installment === 'true';
      }
      if (filters.search) {
        query.$or = [
          { name: { $regex: filters.search, $options: 'i' } },
          { description: { $regex: filters.search, $options: 'i' } },
          { brand: { $regex: filters.search, $options: 'i' } },
        ];
      }
      if (filters.minPrice || filters.maxPrice) {
        query.price = {};
        if (filters.minPrice) {
          query.price.$gte = Number.parseFloat(filters.minPrice);
        }
        if (filters.maxPrice) {
          query.price.$lte = Number.parseFloat(filters.maxPrice);
        }
      }

      if (!isAdmin && owner !== targetUserId) {
        // Regular user viewing someone else's products: only active and approved
        query.isActive = true;
        query.approved = true;
      }

      const products = await Product.find(query)
        .populate('category', 'name')
        .populate('userId', 'firstName lastName')
        .sort({ createdAt: -1 })
        .skip(skip)
        .limit(limit)
        .lean();

      const count = await Product.countDocuments(query);

      return { products, count };
    } catch (error) {
      throw new Error(`Failed to get user products: ${error.message}`);
    }
  }

  /**
   * Toggle product active status
   * @param {string} id - Product ID
   * @returns {Promise<Object>} Updated product
   */
  static async toggleProductStatus(id) {
    try {
      const product = await Product.findById(id);
      if (!product) {
        throw new Error('Product not found');
      }

      product.isActive = !product.isActive;
      await product.save();
      return product;
    } catch (error) {
      throw new Error(`Failed to toggle product status: ${error.message}`);
    }
  }

  /**
   * Get top products based on views and bids
   * @param {number} limit - Number of products to return
   * @returns {Promise<Array>} Array of top products
   */
  static async getTopProducts(limit = 10) {
    try {
      const products = await Product.aggregate([
        {
          $match: {
            isActive: true,
            approved: true,
            stock: { $gt: 0 },
          },
        },
        {
          $lookup: {
            from: 'accounts',
            localField: 'userId',
            foreignField: '_id',
            as: 'userInfo',
          },
        },
        {
          $unwind: '$userInfo',
        },
        {
          $match: {
            'userInfo.suspended': false,
            'userInfo.banned': false,
          },
        },
        {
          $lookup: {
            from: 'bids',
            localField: '_id',
            foreignField: 'product',
            as: 'bids',
          },
        },
        {
          $addFields: {
            bidCount: { $size: '$bids' },
          },
        },
        {
          $lookup: {
            from: 'categories',
            localField: 'category',
            foreignField: '_id',
            as: 'categoryInfo',
          },
        },
        {
          $project: {
            userId: 1,
            brand: 1,
            name: 1,
            price: 1,
            description: 1,
            imageUrl: 1,
            stock: 1,
            views: 1,
            bidCount: 1,
            createdAt: 1,
            category: { $arrayElemAt: ['$categoryInfo.name', 0] },
            user: {
              firstName: '$userInfo.firstName',
              lastName: '$userInfo.lastName',
            },
          },
        },
        {
          $sort: { views: -1, bidCount: -1, createdAt: -1 },
        },
        { $limit: limit },
      ]);

      return products;
    } catch (error) {
      console.error(error);
      throw new Error(`Failed to get top products: ${error.message}`);
    }
  }

  /**
   * Get products by category name
   * @param {string} categoryName - Category name
   * @param {Object} filters - Filter options
   * @param {number} limit - Number of products per page
   * @param {number} skip - Skip for pagination
   * @param {string} userId - Current user ID
   * @param {boolean} isAdmin - Whether user is admin
   * @returns {Promise<Object>} Products and total count
   */
  static async getProductsByCategory(
    categoryName,
    filters = {},
    limit = 10,
    skip = 0,
    userId = null,
    isAdmin = false,
  ) {
    try {
      // Find category by name (case insensitive)
      const category = await Category.findOne({
        name: {
          $regex: new RegExp(`^${categoryName.replace(/-/g, ' ')}$`, 'i'),
        },
      });

      if (!category) {
        throw new Error('Category not found');
      }

      const query = { category: category._id };

      // Apply additional filters
      if (filters.isActive !== undefined) {
        query.isActive = filters.isActive === 'true';
      }
      if (filters.swappable !== undefined) {
        query.swappable = filters.swappable === 'true';
      }
      if (filters.installment !== undefined) {
        query.installmentAvailable = filters.installment === 'true';
      }
      if (filters.search) {
        query.$or = [
          { name: { $regex: filters.search, $options: 'i' } },
          { description: { $regex: filters.search, $options: 'i' } },
          { brand: { $regex: filters.search, $options: 'i' } },
        ];
      }
      if (filters.minPrice || filters.maxPrice) {
        query.price = {};
        if (filters.minPrice) {
          query.price.$gte = Number.parseFloat(filters.minPrice);
        }
        if (filters.maxPrice) {
          query.price.$lte = Number.parseFloat(filters.maxPrice);
        }
      }

      let finalQuery = {};

      if (isAdmin) {
        finalQuery = { ...query };
      } else {
        finalQuery = {
          ...query,
          isActive: true,
          approved: true,
        };
      }

      const products = await Product.find(finalQuery)
        .populate('category', 'name')
        .populate(
          'userId',
          'firstName lastName suspended banned badge verified',
        )
        .sort({ createdAt: -1 })
        .skip(skip)
        .limit(limit)
        .lean();

      // Filter out products from suspended/banned users for non-admins
      const filteredProducts = isAdmin
        ? products
        : products.filter(
            (p) => p.userId && !p.userId.suspended && !p.userId.banned,
          );

      const count = await Product.countDocuments(finalQuery);

      return { products: filteredProducts, count };
    } catch (error) {
      throw new Error(`Failed to get products by category: ${error.message}`);
    }
  }
}

module.exports = ProductService;
