import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import * as productRepo from '../repositories/productRepository.js';
import logAudit from '../utils/audit.js';
import { normalizeVariants, syncProductFromVariants, getVariants } from '../utils/variantUtils.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const prepareProductData = (data) => {
  const prepared = { ...data };
  if (typeof prepared.variants === 'string') {
    try {
      prepared.variants = JSON.parse(prepared.variants);
    } catch {
      prepared.variants = [];
    }
  }
  if (typeof prepared.sizeVariants === 'string') {
    try {
      prepared.sizeVariants = JSON.parse(prepared.sizeVariants);
    } catch {
      prepared.sizeVariants = [];
    }
  }
  const trackingType = prepared.trackingType || 'QUANTITY';

  let variants = [];
  if (Array.isArray(prepared.variants) && prepared.variants.length > 0) {
    variants = normalizeVariants(prepared.variants, {
      trackingType,
      sellingUnit: prepared.unit || 'pcs',
    });
  }

  prepared.variants = variants;

  if (variants.length > 0) {
    delete prepared.sizeVariants;
    delete prepared.currentStock;
    delete prepared.purchasePrice;
    delete prepared.sellingPrice;
    delete prepared.lowStockThreshold;
    delete prepared.unit;
  } else if (trackingType === 'SIZE' && Array.isArray(prepared.sizeVariants) && prepared.sizeVariants.length > 0) {
    prepared.sizeVariants = prepared.sizeVariants.map((v) => ({
      size: v.size,
      stock: Number(v.stock) || 0,
      lowStockThreshold: Number(v.lowStockThreshold) || 5,
    }));
  }

  return prepared;
};

const getAllProducts = async (query) => {
  return productRepo.findAll(query);
};

const getProductById = async (id) => {
  const product = await productRepo.findById(id);
  if (!product) throw Object.assign(new Error('Product not found'), { statusCode: 404 });
  return product;
};

const BARCODE_FORMAT = /^[A-Za-z0-9\-_]{3,64}$/;

const assertBarcodesAvailable = async (variants, excludeProductId = null) => {
  const barcodes = [
    ...new Set(
      (Array.isArray(variants) ? variants : [])
        .map((v) => (v?.barcode || '').trim())
        .filter(Boolean)
    ),
  ];
  if (barcodes.length === 0) return;

  for (const b of barcodes) {
    if (!BARCODE_FORMAT.test(b)) {
      throw Object.assign(
        new Error(`Invalid barcode format: ${b}. Use 3-64 letters, numbers, dashes or underscores.`),
        { statusCode: 400 }
      );
    }
  }

  const query = { 'variants.barcode': { $in: barcodes } };
  if (excludeProductId) query._id = { $ne: excludeProductId };
  const conflicts = await productRepo.findAllRaw(query);
  if (conflicts.length > 0) {
    throw Object.assign(
      new Error(`Barcode already in use: ${barcodes.join(', ')}`),
      { statusCode: 400 }
    );
  }

  const seen = new Set();
  for (const b of barcodes) {
    if (seen.has(b)) {
      throw Object.assign(new Error(`Duplicate barcode in form: ${b}`), { statusCode: 400 });
    }
    seen.add(b);
  }
};

const findByBarcode = async (barcode) => {
  const code = String(barcode || '').trim();
  if (!code) {
    throw Object.assign(new Error('Barcode is required'), { statusCode: 400 });
  }
  const products = await productRepo.findAllRaw({ 'variants.barcode': code });
  for (const product of products) {
    const variants = getVariants(product);
    const variant = variants.find((v) => (v.barcode || '') === code);
    if (variant) {
      return {
        found: true,
        product: { _id: product._id, name: product.name, category: product.category, image: product.image },
        variant,
      };
    }
  }
  throw Object.assign(new Error(`No product found for barcode ${code}`), { statusCode: 404 });
};

const createProduct = async (data, req) => {
  const prepared = prepareProductData(data);
  await assertBarcodesAvailable(prepared.variants);
  const product = await productRepo.create(prepared);
  if (product.variants && product.variants.length > 0) {
    product.variants = normalizeVariants(product.variants, {
      trackingType: product.trackingType || 'QUANTITY',
      sellingUnit: 'pcs',
    });
    syncProductFromVariants(product);
    await product.save();
  }
  logAudit(req, 'PRODUCT_CREATED', 'product', product._id, { name: product.name });
  return product;
};

const updateProduct = async (id, data, req) => {
  const prepared = prepareProductData(data);
  await assertBarcodesAvailable(prepared.variants, id);
  const existing = await productRepo.findById(id);
  if (!existing) throw Object.assign(new Error('Product not found'), { statusCode: 404 });
  const product = await productRepo.update(id, prepared);
  if (!product) throw Object.assign(new Error('Product not found'), { statusCode: 404 });
  if (product.variants && product.variants.length > 0) {
    product.variants = normalizeVariants(product.variants, {
      trackingType: product.trackingType || 'QUANTITY',
      sellingUnit: 'pcs',
    });
    syncProductFromVariants(product);
    await product.save();
  }
  logAudit(req, 'PRODUCT_UPDATED', 'product', product._id, { name: product.name });

  // Price change detection: compare variant-level and flat selling prices
  const priceChanges = [];
  if (prepared.variants?.length && existing.variants?.length) {
    for (const nv of prepared.variants) {
      const ov = existing.variants.find((v) => v.variantId === nv.variantId);
      if (ov && Number(ov.sellingPrice) !== Number(nv.sellingPrice)) {
        priceChanges.push({
          variant: nv.label,
          from: Number(ov.sellingPrice),
          to: Number(nv.sellingPrice),
        });
      }
    }
  } else if (
    prepared.sellingPrice != null &&
    Number(existing.sellingPrice) !== Number(prepared.sellingPrice)
  ) {
    priceChanges.push({ variant: '', from: Number(existing.sellingPrice), to: Number(prepared.sellingPrice) });
  }
  if (priceChanges.length > 0) {
    logAudit(req, 'PRICE_CHANGED', 'product', product._id, { name: product.name, changes: priceChanges });
  }
  return product;
};

const deleteProduct = async (id, req) => {
  const product = await productRepo.deleteOne(id);
  if (!product) throw Object.assign(new Error('Product not found'), { statusCode: 404 });
  logAudit(req, 'PRODUCT_DELETED', 'product', product._id, { name: product.name });
  return product;
};

const deleteImageFile = async (imagePath) => {
  try {
    if (!imagePath) return;
    const fullPath = path.join(__dirname, '..', '..', imagePath);
    if (fs.existsSync(fullPath)) {
      fs.unlinkSync(fullPath);
    }
  } catch {
    // Silently handle missing files
  }
};

const getLowStockProducts = async () => {
  return productRepo.findLowStock();
};

const getOutOfStockProducts = async () => {
  return productRepo.findOutOfStock();
};

const getCategories = async () => {
  return productRepo.findDistinctCategories();
};

const getCategoryStats = async () => {
  return productRepo.aggregateByCategory();
};

export default { getAllProducts, getProductById, findByBarcode, createProduct, updateProduct, deleteProduct, deleteImageFile, getLowStockProducts, getOutOfStockProducts, getCategories, getCategoryStats };