import { BN } from '@ocap/util';
import { Coupon, Discount, PaymentCurrency, PromotionCode, CheckoutSession } from '../../store/models';
import type { TLineItemExpanded } from '../../store/models';
import { getPriceUintAmountByCurrency } from '../price';
import { validCoupon, checkPromotionCodeEligibility, calculateDiscountAmount } from './coupon';
import { emitAsync } from '../event';
import logger from '../logger';

const getItemsTotalAmount = (lineItems: TLineItemExpanded[], currencyId: string, options?: { trialing?: boolean }) => {
  let totalUnitPrice = new BN(0);

  lineItems.forEach((item) => {
    const price = item.upsell_price || item.price;

    const dynamicAmount =
      price?.pricing_type === 'dynamic' ? (item as any).custom_amount || (item as any).quoted_amount : undefined;
    const unitPrice = getPriceUintAmountByCurrency(price, currencyId);

    if (price?.type === 'recurring') {
      if (options?.trialing) {
        return;
      }
      if (price?.recurring?.usage_type === 'metered') {
        return;
      }
    }

    if (dynamicAmount) {
      totalUnitPrice = totalUnitPrice.add(new BN(dynamicAmount));
      return;
    }

    totalUnitPrice = totalUnitPrice.add(new BN(unitPrice).mul(new BN(item.quantity)));
  });
  return totalUnitPrice.toString();
};

const getItemAmount = (item: TLineItemExpanded, currencyId: string) => {
  const price = item.upsell_price || item.price;
  const dynamicAmount =
    price?.pricing_type === 'dynamic' ? (item as any).custom_amount || (item as any).quoted_amount : undefined;
  if (dynamicAmount) {
    return new BN(dynamicAmount).toString();
  }
  const unitPrice = getPriceUintAmountByCurrency(price, currencyId);
  return new BN(unitPrice).mul(new BN(item.quantity)).toString();
};

/**
 * Apply discounts to line items and calculate discount summary
 */
export async function applyDiscountsToLineItems({
  lineItems,
  promotionCodeId,
  couponId,
  discountId,
  customerId,
  currency,
  totalAmount,
  billingContext,
}: {
  lineItems: TLineItemExpanded[];
  promotionCodeId?: string;
  discountId?: string;
  couponId: string;
  customerId: string;
  currency: PaymentCurrency;
  totalAmount?: string; // Optional, will calculate from lineItems if not provided
  billingContext?: { trialing?: boolean };
}): Promise<{
  enhancedLineItems: (TLineItemExpanded & {
    discountable: boolean;
    discount_amounts: Array<{ amount: string; coupon: string }>;
  })[];
  discountSummary: {
    appliedCoupon: string | null;
    discountAmount: string;
    totalDiscountAmount: string;
    finalTotal: string;
  };
  notValidReason?: string;
}> {
  let discount = null;
  if (discountId) {
    discount = await Discount.findByPk(discountId);
  }

  // 1. Calculate total amount from lineItems if not provided, considering billing context
  const baseTotal = totalAmount ?? getItemsTotalAmount(lineItems, currency.id, billingContext);

  // 2. Get coupon (required)
  const coupon = await Coupon.findByPk(discount?.coupon_id || couponId);
  if (!coupon) {
    // 3. If couponId doesn't exist, return original items
    const enhancedLineItems = lineItems.map((item) => ({
      ...item,
      discountable: false,
      discount_amounts: [],
    }));
    return {
      enhancedLineItems,
      discountSummary: {
        appliedCoupon: null,
        discountAmount: '0',
        totalDiscountAmount: '0',
        finalTotal: baseTotal,
      },
      notValidReason: 'Coupon not found',
    };
  }

  let isValid = false;
  let notValidReason = '';

  // 4. Validate coupon eligibility
  if (promotionCodeId || discount?.promotion_code_id) {
    // Get promotion code and use comprehensive validation
    const promotionCode = await PromotionCode.findByPk(promotionCodeId || discount?.promotion_code_id);
    if (!promotionCode) {
      isValid = false;
    } else {
      const eligibility = await checkPromotionCodeEligibility({
        promotionCode,
        couponId,
        customerId,
        amount: baseTotal,
        currencyId: currency.id,
        lineItems,
      });
      isValid = eligibility.eligible;
      notValidReason = eligibility.reason || '';
    }
  } else {
    // Direct coupon validation
    const couponValidation = await validCoupon(coupon, lineItems);
    isValid = couponValidation.valid;
    notValidReason = couponValidation.reason || '';
  }

  if (!isValid) {
    // Return original items if validation fails
    const enhancedLineItems = lineItems.map((item) => ({
      ...item,
      discountable: false,
      discount_amounts: [],
    }));
    return {
      enhancedLineItems,
      discountSummary: {
        appliedCoupon: null,
        discountAmount: '0',
        totalDiscountAmount: '0',
        finalTotal: baseTotal,
      },
      notValidReason,
    };
  }

  // Use common calculation logic
  const result = calculateDiscountForLineItems({
    lineItems,
    coupon,
    discount,
    currency,
    totalAmount,
    billingContext,
  });

  return {
    ...result,
    notValidReason: undefined, // No validation error if we reach here
  };
}

/**
 * Common discount calculation logic
 */
function calculateDiscountForLineItems({
  lineItems,
  coupon,
  discount,
  currency,
  totalAmount,
  billingContext,
}: {
  lineItems: TLineItemExpanded[];
  coupon: Coupon;
  discount?: Discount | null;
  currency: PaymentCurrency;
  totalAmount?: string;
  billingContext?: { trialing?: boolean };
}): {
  enhancedLineItems: (TLineItemExpanded & {
    discountable: boolean;
    discount_amounts: Array<{ amount: string; coupon: string }>;
  })[];
  discountSummary: {
    appliedCoupon: string | null;
    discountAmount: string;
    totalDiscountAmount: string;
    finalTotal: string;
  };
} {
  // 1. Calculate total amount from lineItems if not provided, considering billing context
  const baseTotal = totalAmount ?? getItemsTotalAmount(lineItems, currency.id, billingContext);

  // 2. Get all eligible items based on coupon product restrictions
  const eligibleLineItems = lineItems.filter((item) => {
    if (!coupon.applies_to?.products?.length) {
      return true; // No product restriction, all items eligible
    }
    return coupon.applies_to.products.some((productId) => item.price?.product_id === productId);
  });

  if (eligibleLineItems.length === 0) {
    // No eligible items, return original
    const enhancedLineItems = lineItems.map((item) => ({
      ...item,
      discountable: false,
      discount_amounts: [],
    }));
    return {
      enhancedLineItems,
      discountSummary: {
        appliedCoupon: null,
        discountAmount: '0',
        totalDiscountAmount: '0',
        finalTotal: baseTotal,
      },
    };
  }

  // 3. Calculate total eligible amount (only products that qualify for the discount)
  const totalEligibleAmount = eligibleLineItems.reduce((sum, item) => {
    const itemAmount = getItemAmount(item, currency.id);
    return new BN(sum).add(new BN(itemAmount)).toString();
  }, '0');

  // 4. Calculate discount amount based ONLY on eligible products, not the entire cart
  const totalDiscountAmount = calculateDiscountAmount(coupon, totalEligibleAmount, currency);

  // Fixed amount coupon with no matching currency — discount is 0, treat as not applicable
  if (coupon.amount_off && (!coupon.percent_off || coupon.percent_off <= 0) && totalDiscountAmount === '0') {
    const enhancedLineItems = lineItems.map((item) => ({
      ...item,
      discountable: false,
      discount_amounts: [],
    }));
    return {
      enhancedLineItems,
      discountSummary: {
        appliedCoupon: null,
        discountAmount: '0',
        totalDiscountAmount: '0',
        finalTotal: baseTotal,
      },
    };
  }

  // Ensure discount doesn't exceed base total
  const adjustedDiscountAmount = new BN(totalDiscountAmount).gt(new BN(baseTotal)) ? baseTotal : totalDiscountAmount;

  const finalTotal = new BN(baseTotal).sub(new BN(adjustedDiscountAmount));
  const adjustedFinalTotal = finalTotal.lt(new BN('0')) ? '0' : finalTotal.toString();

  // 5. Apply discount to line items
  const enhancedLineItems = lineItems.map((item) => {
    const isEligible = eligibleLineItems.includes(item);

    if (!isEligible) {
      return {
        ...item,
        discountable: false,
        discount_amounts: [],
      };
    }

    const itemAmount = getItemAmount(item, currency.id);
    let itemDiscountAmount = '0';

    if (coupon.percent_off > 0) {
      // For percentage discounts, apply directly to each eligible item
      itemDiscountAmount = new BN(itemAmount).mul(new BN(coupon.percent_off)).div(new BN(100)).toString();
    } else if (coupon.amount_off && totalEligibleAmount !== '0') {
      // For fixed amount discounts, distribute proportionally among eligible items
      itemDiscountAmount = new BN(itemAmount)
        .mul(new BN(adjustedDiscountAmount))
        .div(new BN(totalEligibleAmount))
        .toString();
    }

    return {
      ...item,
      discountable: true,
      discount_amounts:
        itemDiscountAmount !== '0' ? [{ amount: itemDiscountAmount, coupon: coupon.id, discount: discount?.id }] : [],
    };
  });

  return {
    enhancedLineItems,
    discountSummary: {
      appliedCoupon: coupon.id,
      discountAmount: adjustedDiscountAmount,
      totalDiscountAmount: adjustedDiscountAmount,
      finalTotal: adjustedFinalTotal,
    },
  };
}

/**
 * Apply discount to subscription billing cycles (skips validation for stored discounts)
 */
export async function applySubscriptionDiscount({
  lineItems,
  discount,
  currency,
  totalAmount,
  billingContext,
}: {
  lineItems: TLineItemExpanded[];
  discount: Discount;
  currency: PaymentCurrency;
  totalAmount?: string;
  billingContext?: { trialing?: boolean };
}): Promise<{
  enhancedLineItems: (TLineItemExpanded & {
    discountable: boolean;
    discount_amounts: Array<{ amount: string; coupon: string }>;
  })[];
  discountSummary: {
    appliedCoupon: string | null;
    discountAmount: string;
    totalDiscountAmount: string;
    finalTotal: string;
  };
}> {
  // Get coupon from stored discount
  const coupon = await Coupon.findByPk(discount.coupon_id);
  if (!coupon) {
    logger.error('Coupon not found for stored discount', {
      discountId: discount.id,
      couponId: discount.coupon_id,
    });

    // Return original items if coupon is missing
    const enhancedLineItems = lineItems.map((item) => ({
      ...item,
      discountable: false,
      discount_amounts: [],
    }));
    return {
      enhancedLineItems,
      discountSummary: {
        appliedCoupon: null,
        discountAmount: '0',
        totalDiscountAmount: '0',
        finalTotal: totalAmount ?? getItemsTotalAmount(lineItems, currency.id, billingContext),
      },
    };
  }

  // Use common calculation logic
  const result = calculateDiscountForLineItems({
    lineItems,
    coupon,
    discount,
    currency,
    totalAmount,
    billingContext,
  });

  logger.info('Applied subscription discount', {
    discountId: discount.id,
    originalTotal: totalAmount,
    discountAmount: result.discountSummary.discountAmount,
    finalTotal: result.discountSummary.finalTotal,
  });

  return result;
}

async function rollbackUsageForResource<T extends Coupon | PromotionCode>(
  resource: T,
  resourceType: 'coupon' | 'promotion-code'
): Promise<void> {
  const currentUsage = resource.times_redeemed || 0;
  if (currentUsage <= 0) return;

  if (resourceType === 'coupon') {
    await (resource as Coupon).update({
      times_redeemed: currentUsage - 1,
      valid: true,
    });
  } else {
    await (resource as PromotionCode).update({
      times_redeemed: currentUsage - 1,
      active: true,
    });
  }

  logger.info('Updated usage for resource', {
    resourceId: resource.id,
    resourceType,
    currentUsage: currentUsage - 1,
  });

  emitAsync('discount-status.queued', resource, resourceType, true);
}

export async function rollbackDiscountUsageForCheckoutSession(checkoutSessionId: string): Promise<void> {
  try {
    const checkoutSession = await CheckoutSession.findByPk(checkoutSessionId);
    if (!checkoutSession) {
      logger.error('Checkout session not found', { checkoutSessionId });
      return;
    }

    if (checkoutSession.status === 'complete') {
      logger.error('Cannot rollback completed session', { checkoutSessionId });
      return;
    }

    const discounts = (await Discount.findAll({
      where: { checkout_session_id: checkoutSessionId },
      include: [
        { model: Coupon, as: 'coupon' },
        { model: PromotionCode, as: 'promotionCode', required: false },
      ],
    })) as (Discount & { coupon: Coupon; promotionCode?: PromotionCode })[];

    if (discounts.length === 0) {
      logger.info('No discounts found for checkout session', { checkoutSessionId });
      return;
    }

    const uniqueCoupons = new Map<string, Coupon>();
    const uniquePromotionCodes = new Map<string, PromotionCode>();

    discounts.forEach((discount) => {
      if (discount.confirmed) {
        if (discount.coupon?.id && !uniqueCoupons.has(discount.coupon.id)) {
          uniqueCoupons.set(discount.coupon.id, discount.coupon);
        }
        if (discount.promotionCode?.id && !uniquePromotionCodes.has(discount.promotionCode.id)) {
          uniquePromotionCodes.set(discount.promotionCode.id, discount.promotionCode);
        }
      }
    });

    const rollbackPromises = [
      ...Array.from(uniquePromotionCodes.values()).map((pc) => rollbackUsageForResource(pc, 'promotion-code')),
      ...Array.from(uniqueCoupons.values()).map((c) => rollbackUsageForResource(c, 'coupon')),
    ];

    await Promise.all(rollbackPromises);

    await Promise.all(discounts.map((discount) => discount.destroy()));
  } catch (error) {
    logger.error('Discount rollback failed', { checkoutSessionId, error: error.message });
  }
}
