import mongoose from 'mongoose';
import Sale from '../models/Sale.js';
import SaleReturn from '../models/SaleReturn.js';
import Counter from '../models/Counter.js';
import Product from '../models/Product.js';
import * as stockHistoryRepo from '../repositories/stockHistoryRepository.js';
import * as returnRepo from '../repositories/saleReturnRepository.js';
import logAudit from '../utils/audit.js';
import { syncProductFromVariants } from '../utils/variantUtils.js';

const RETURN_REASONS = [
  'CUSTOMER_RETURN',
  'DAMAGED_PRODUCT',
  'DEFECTIVE_PRODUCT',
  'EXPIRED_PRODUCT',
  'WRONG_PRODUCT',
  'OTHER',
];
const REFUND_METHODS = ['CASH', 'UPI', 'CARD', 'STORE_CREDIT'];

const generateReturnNumber = async (session) => {
  const seq = await Counter.getNextSequence('saleReturn', session);
  return `RET-${String(seq).padStart(6, '0')}`;
};

const seedReturnCounter = async () => {
  const latest = await returnRepo.findLatest();
  if (!latest || !latest.returnNumber) return;
  const match = latest.returnNumber.match(/RET-(\d{6})/);
  if (!match) return;
  await Counter.updateOne(
    { _id: 'saleReturn' },
    { $setOnInsert: { seq: parseInt(match[1], 10) } },
    { upsert: true }
  );
};

const itemKey = (productId, variantId) => `${productId}_${variantId || ''}`;

const isTransientTxnError = (error) => {
  if (!error) return false;
  const labels = error.errorLabels || [];
  if (labels.includes('TransientTransactionError')) return true;
  return /Write conflict|Please retry your operation/i.test(error.message || '');
};

const createReturnOnce = async (saleId, payload, req) => {
  const session = await mongoose.startSession();
  session.startTransaction();

  try {
    const sale = await Sale.findById(saleId).session(session);
    if (!sale) throw Object.assign(new Error('Sale not found'), { statusCode: 404 });

    const requestedMap = new Map();
    for (const item of payload.items) {
      const key = itemKey(item.product, item.variantId);
      requestedMap.set(key, (requestedMap.get(key) || 0) + Number(item.quantity));
    }

    const existingCounter = await Counter.findById('saleReturn');
    if (!existingCounter) await seedReturnCounter();

    const returnedMap = await returnRepo.aggregateReturnedByVariant(sale._id, session);

    const returnItems = [];
    let totalRefund = 0;

    for (const [key, qty] of requestedMap.entries()) {
      const [productId, variantId] = key.split(/_(.+)/);

      const soldItem = sale.items.find(
        (i) =>
          String(i.product) === productId &&
          ((i.variant || '') === (variantId || ''))
      );

      if (!soldItem) {
        throw Object.assign(
          new Error('Returned item does not belong to this sale'),
          { statusCode: 400 }
        );
      }

      const soldQty = Number(soldItem.quantity);
      const alreadyReturned = returnedMap[key] || 0;
      const remaining = soldQty - alreadyReturned;

      if (qty > remaining) {
        throw Object.assign(
          new Error(
            `Cannot return ${qty} of ${soldItem.productName}${
              soldItem.variantLabel ? ` (${soldItem.variantLabel})` : ''
            }. Sold: ${soldQty}, already returned: ${alreadyReturned}, remaining returnable: ${remaining}`
          ),
          { statusCode: 400 }
        );
      }

      const amount = Math.round(Number(soldItem.price) * qty * 100) / 100;
      totalRefund += amount;

      returnItems.push({
        product: productId,
        productName: soldItem.productName,
        variantId: variantId || '',
        variantLabel: soldItem.variantLabel || '',
        sku: soldItem.sku || '',
        quantity: qty,
        price: Number(soldItem.price),
        amount,
        costPrice: soldItem.costPrice != null ? Number(soldItem.costPrice) : null,
      });
    }

    totalRefund = Math.round(totalRefund * 100) / 100;

    // High-value refunds require manager-level approval (limit from store settings)
    const Settings = (await import('../models/StoreSettings.js')).default;
    const settingsDoc = await Settings.findOne().session(session).lean();
    const refundLimit = Number(settingsDoc?.refundApprovalLimit) || 0;
    if (refundLimit > 0 && totalRefund > refundLimit) {
      const allowed = req.user && ['SUPER_ADMIN', 'ADMIN', 'MANAGER'].includes(req.user.role);
      if (!allowed) {
        throw Object.assign(
          new Error(
            `Refund of ₹${totalRefund} exceeds the approval limit (₹${refundLimit}). Manager approval required.`
          ),
          { statusCode: 403 }
        );
      }
    }

    for (const item of returnItems) {
      try {
        const product = await Product.findById(item.product).session(session);
        if (!product) continue;

        const previousStock = product.currentStock;

        if (product.variants && product.variants.length > 0 && item.variantId) {
          const variant = product.variants.find((v) => v.variantId === item.variantId);
          if (!variant) continue;
          variant.currentStock = (Number(variant.currentStock) || 0) + item.quantity;
          syncProductFromVariants(product);
        } else if (product.trackingType === 'SIZE' && soldSizeOf(sale, item)) {
          const size = soldSizeOf(sale, item);
          const sizeVariant = product.sizeVariants.find((v) => v.size === size);
          if (sizeVariant) sizeVariant.stock += item.quantity;
          product.currentStock = product.sizeVariants.reduce((sum, v) => sum + (v.stock || 0), 0);
        } else {
          product.currentStock = (Number(product.currentStock) || 0) + item.quantity;
        }

        await product.save({ session });

        await stockHistoryRepo.create(
          {
            product: product._id,
            productName: product.name,
            type: 'RETURN',
            quantity: item.quantity,
            previousStock,
            newStock: product.currentStock,
            variant: item.variantId,
            variantLabel: item.variantLabel,
            sellingUnit: 'pcs',
            reference: sale.billNumber,
          },
          session
        );
      } catch (stockError) {
        // Product deleted or stock restore impossible: refund still recorded.
        continue;
      }
    }

    const returnNumber = await generateReturnNumber(session);

    const returnDoc = await returnRepo.create(
      {
        returnNumber,
        sale: sale._id,
        billNumber: sale.billNumber,
        items: returnItems,
        totalRefund,
        reason: payload.reason,
        refundMethod: REFUND_METHODS.includes(payload.refundMethod)
          ? payload.refundMethod
          : 'CASH',
        refundReference: (payload.refundReference || '').trim().slice(0, 60),
        notes: (payload.notes || '').trim().slice(0, 500),
        processedBy: req.user
          ? { id: req.user._id, name: req.user.name }
          : { id: null, name: 'system' },
      },
      session
    );

    sale.returnedAmount = Math.round(((sale.returnedAmount || 0) + totalRefund) * 100) / 100;
    await sale.save({ session });

    await session.commitTransaction();

    logAudit(req, 'RETURN_CREATED', 'return', returnDoc._id, {
      returnNumber,
      billNumber: sale.billNumber,
      totalRefund,
      reason: payload.reason,
      items: returnItems.map((i) => ({ name: i.productName, variant: i.variantLabel, qty: i.quantity })),
    });
    logAudit(req, 'REFUND_CREATED', 'refund', returnDoc._id, {
      returnNumber,
      billNumber: sale.billNumber,
      amount: totalRefund,
      method: returnDoc.refundMethod,
      reference: returnDoc.refundReference || '',
    });

    return returnDoc;
  } catch (error) {
    await session.abortTransaction();
    throw error;
  } finally {
    session.endSession();
  }
};

const createReturn = async (saleId, payload, req) => {
  const MAX_ATTEMPTS = 8;
  let lastError;
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
    try {
      return await createReturnOnce(saleId, payload, req);
    } catch (error) {
      lastError = error;
      if (!isTransientTxnError(error) || attempt === MAX_ATTEMPTS) throw error;
      const jitter = Math.floor(Math.random() * 80);
      await new Promise((r) => setTimeout(r, 60 * attempt + jitter));
    }
  }
  throw lastError;
};

const soldSizeOf = (sale, item) => {
  const soldItem = sale.items.find(
    (i) => String(i.product) === String(item.product) && (i.variant || '') === (item.variantId || '')
  );
  return soldItem?.size || '';
};

const getReturnsForSale = async (saleId) => {
  const sale = await Sale.findById(saleId);
  if (!sale) throw Object.assign(new Error('Sale not found'), { statusCode: 404 });

  const returns = await returnRepo.findBySale(saleId);
  const returnedMap = await returnRepo.aggregateReturnedByVariant(sale._id);

  const returnable = {};
  for (const item of sale.items) {
    const key = itemKey(String(item.product), item.variant);
    const soldQty = Number(item.quantity);
    const returned = returnedMap[key] || 0;
    returnable[key] = {
      soldQuantity: soldQty,
      returnedQuantity: returned,
      remaining: Math.max(0, soldQty - returned),
    };
  }

  return {
    returns,
    returnable,
    totalRefunded: returns.reduce((sum, r) => sum + (r.totalRefund || 0), 0),
  };
};

const escapeRx = (s) => String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

const listReturns = async (query = {}) => {
  const page = Math.max(Number(query.page) || 1, 1);
  const limit = Math.min(Number(query.limit) || 20, 100);
  const filter = {};

  if (query.billNumber) filter.billNumber = { $regex: escapeRx(query.billNumber).trim(), $options: 'i' };
  if (query.returnNumber) filter.returnNumber = { $regex: escapeRx(query.returnNumber).trim(), $options: 'i' };
  if (query.customer) {
    const rx = new RegExp(escapeRx(query.customer).trim(), 'i');
    const sales = await Sale.find({ $or: [{ customerName: rx }, { customerPhone: rx }] }).select('_id');
    filter.sale = { $in: sales.map((s) => s._id) };
  }
  if (query.product) filter['items.productName'] = { $regex: escapeRx(query.product).trim(), $options: 'i' };
  if (query.barcode || query.sku) {
    filter['items.sku'] = { $regex: escapeRx(query.barcode || query.sku).trim(), $options: 'i' };
  }
  if (query.reason) filter.reason = query.reason;
  if (query.refundMethod) filter.refundMethod = query.refundMethod;
  if (query.from || query.to) {
    filter.createdAt = {};
    if (query.from) filter.createdAt.$gte = new Date(query.from);
    if (query.to) {
      const to = new Date(query.to);
      to.setHours(23, 59, 59, 999);
      filter.createdAt.$lte = to;
    }
  }

  const [returns, total, totals] = await Promise.all([
    SaleReturn.find(filter).sort('-createdAt').skip((page - 1) * limit).limit(limit),
    SaleReturn.countDocuments(filter),
    SaleReturn.aggregate([
      { $match: filter },
      { $group: { _id: null, totalRefund: { $sum: '$totalRefund' }, count: { $sum: 1 } } },
    ]),
  ]);

  return {
    returns,
    total,
    totalRefund: Math.round((totals[0]?.totalRefund || 0) * 100) / 100,
    count: totals[0]?.count || 0,
    page,
    pages: Math.ceil(total / limit),
  };
};

export default { createReturn, getReturnsForSale, listReturns };
