import {
  applyDiscountsToLineItems,
  applySubscriptionDiscount,
  rollbackDiscountUsageForCheckoutSession,
} from '../../src/libs/discount/discount';
import { Coupon, Discount, CheckoutSession } from '../../src/store/models';

// Mock logger and event emitter to keep test output clean
jest.mock('../../src/libs/logger', () => ({
  __esModule: true,
  default: {
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
    debug: jest.fn(),
  },
}));

jest.mock('../../src/libs/event', () => ({
  emitAsync: jest.fn(),
}));

// Mock price util so we can control unit amounts per item
jest.mock('../../src/libs/price', () => ({
  getPriceUintAmountByCurrency: (price: any, currencyId: string) => {
    if (!price) return '0';
    if (price.unit_amount_map && price.unit_amount_map[currencyId]) return String(price.unit_amount_map[currencyId]);
    if (price.unit_amount) return String(price.unit_amount);
    if (Array.isArray(price.currency_options)) {
      const opt = price.currency_options.find((c: any) => c.currency_id === currencyId);
      return String(opt?.unit_amount || '0');
    }
    return '0';
  },
}));

describe('libs/discount/discount.ts', () => {
  const currency = { id: 'usd', decimal: 2, symbol: 'USD' } as any;

  beforeEach(() => {
    jest.clearAllMocks();
    jest.restoreAllMocks();
  });

  describe('applyDiscountsToLineItems - percent_off over eligible items', () => {
    it('applies percentage discount only to eligible products and returns per-item breakdown', async () => {
      const lineItems: any[] = [
        {
          id: 'li_1',
          quantity: 1,
          price: { product_id: 'p1', unit_amount_map: { usd: '100' } },
        },
        {
          id: 'li_2',
          quantity: 3,
          price: { product_id: 'p2', unit_amount_map: { usd: '100' } }, // amount 300
        },
        {
          id: 'li_3',
          quantity: 2,
          price: { product_id: 'p3', unit_amount_map: { usd: '50' } }, // ineligible
        },
      ];

      jest.spyOn(Coupon, 'findByPk').mockResolvedValue({
        id: 'c1',
        percent_off: 50,
        amount_off: null,
        applies_to: { products: ['p1', 'p2'] },
        valid: true,
      } as any);

      // Force coupon validation to pass
      const couponModule = await import('../../src/libs/discount/coupon');
      jest.spyOn(couponModule, 'validCoupon').mockReturnValue({ valid: true } as any);

      const result = await applyDiscountsToLineItems({
        lineItems,
        couponId: 'c1',
        customerId: 'cust_1',
        currency,
      });

      const { enhancedLineItems, discountSummary } = result;
      // li_1 amount=100 -> 50% => 50; li_2 amount=300 -> 150; li_3 ineligible
      const li1 = enhancedLineItems.find((x: any) => x.id === 'li_1');
      const li2 = enhancedLineItems.find((x: any) => x.id === 'li_2');
      const li3 = enhancedLineItems.find((x: any) => x.id === 'li_3');

      expect(li1).toBeDefined();
      expect(li2).toBeDefined();
      expect(li3).toBeDefined();
      expect((li1 as any).discountable).toBe(true);
      expect((li1 as any).discount_amounts[0].amount).toBe('50');
      expect((li2 as any).discountable).toBe(true);
      expect((li2 as any).discount_amounts[0].amount).toBe('150');
      expect((li3 as any).discountable).toBe(false);

      expect(discountSummary.appliedCoupon).toBe('c1');
      expect(discountSummary.totalDiscountAmount).toBe('200');
      // base total = 100 + 300 + 100 = 500; final = 300
      expect(discountSummary.finalTotal).toBe('300');
      expect(result.notValidReason).toBeUndefined();
    });
  });

  describe('applyDiscountsToLineItems - amount_off distributed proportionally', () => {
    it('distributes fixed amount discount proportionally among eligible items', async () => {
      const lineItems: any[] = [
        { id: 'a', quantity: 1, price: { product_id: 'pa', unit_amount_map: { usd: '100' } } },
        { id: 'b', quantity: 3, price: { product_id: 'pb', unit_amount_map: { usd: '100' } } }, // 300
      ];

      jest.spyOn(Coupon, 'findByPk').mockResolvedValue({
        id: 'c2',
        percent_off: 0,
        amount_off: '150',
        currency_id: 'usd',
        applies_to: { products: [] },
        valid: true,
      } as any);

      const couponModule = await import('../../src/libs/discount/coupon');
      jest.spyOn(couponModule, 'validCoupon').mockReturnValue({ valid: true } as any);

      const { enhancedLineItems, discountSummary } = await applyDiscountsToLineItems({
        lineItems,
        couponId: 'c2',
        customerId: 'cust_1',
        currency,
      });

      // Total eligible = 400; adjustedDiscount=150
      // a: 100/400 * 150 = 37; b: 300/400 * 150 = 112 (integer division)
      const a = enhancedLineItems.find((x: any) => x.id === 'a');
      const b = enhancedLineItems.find((x: any) => x.id === 'b');
      expect(a).toBeDefined();
      expect(b).toBeDefined();
      expect((a as any).discount_amounts[0].amount).toBe('37');
      expect((b as any).discount_amounts[0].amount).toBe('112');
      expect(discountSummary.totalDiscountAmount).toBe('150');
      expect(discountSummary.finalTotal).toBe('250');
    });
  });

  describe('applyDiscountsToLineItems - invalid or missing coupon', () => {
    it('returns not valid when coupon not found', async () => {
      jest.spyOn(Coupon, 'findByPk').mockResolvedValue(null as any);
      const { discountSummary, notValidReason } = await applyDiscountsToLineItems({
        lineItems: [{ id: 'x', quantity: 1, price: { product_id: 'p1', unit_amount_map: { usd: '100' } } }] as any,
        couponId: 'missing',
        customerId: 'cust_1',
        currency,
      });
      expect(discountSummary.totalDiscountAmount).toBe('0');
      expect(notValidReason).toBe('Coupon not found');
    });

    it('returns not valid when validation fails', async () => {
      jest.spyOn(Coupon, 'findByPk').mockResolvedValue({ id: 'c3', valid: false } as any);
      const couponModule = await import('../../src/libs/discount/coupon');
      jest.spyOn(couponModule, 'validCoupon').mockReturnValue({ valid: false, reason: 'invalid' } as any);

      const res = await applyDiscountsToLineItems({
        lineItems: [{ id: 'x', quantity: 1, price: { product_id: 'p1', unit_amount_map: { usd: '100' } } }] as any,
        couponId: 'c3',
        customerId: 'cust_1',
        currency,
      });
      expect(res.discountSummary.totalDiscountAmount).toBe('0');
      expect(res.notValidReason).toBe('invalid');
    });
  });

  describe('applySubscriptionDiscount', () => {
    it('returns computed discount when coupon exists', async () => {
      const items: any[] = [{ id: 'a', quantity: 2, price: { product_id: 'pa', unit_amount_map: { usd: '50' } } }];

      jest.spyOn(Coupon, 'findByPk').mockResolvedValue({
        id: 'c4',
        percent_off: 20,
        applies_to: { products: [] },
        valid: true,
      } as any);

      const result = await applySubscriptionDiscount({
        lineItems: items as any,
        discount: { coupon_id: 'c4', id: 'd1' } as any,
        currency,
      });

      expect(result.discountSummary.appliedCoupon).toBe('c4');
      // amount = 2*50=100; 20% => 20; final 80
      expect(result.discountSummary.totalDiscountAmount).toBe('20');
      expect(result.discountSummary.finalTotal).toBe('80');
    });

    it('returns original items when coupon missing', async () => {
      jest.spyOn(Coupon, 'findByPk').mockResolvedValue(null as any);
      const items: any[] = [{ id: 'a', quantity: 1, price: { unit_amount_map: { usd: '100' } } }];
      const res = await applySubscriptionDiscount({
        lineItems: items as any,
        discount: { coupon_id: 'missing', id: 'd2' } as any,
        currency,
      });
      expect(res.discountSummary.totalDiscountAmount).toBe('0');
      expect((res.enhancedLineItems[0] as any).discountable).toBe(false);
      expect(res.discountSummary.finalTotal).toBe('100');
    });
  });

  describe('rollbackDiscountUsageForCheckoutSession', () => {
    it('rolls back confirmed discounts and destroys records', async () => {
      jest.spyOn(CheckoutSession, 'findByPk').mockResolvedValue({ id: 'cs_1', status: 'open' } as any);

      const coupon = { id: 'c1', times_redeemed: 2, update: jest.fn().mockResolvedValue(undefined) } as any;
      const pc = { id: 'pc1', times_redeemed: 3, update: jest.fn().mockResolvedValue(undefined) } as any;

      const discounts = [
        { id: 'd1', confirmed: true, coupon, promotionCode: pc, destroy: jest.fn().mockResolvedValue(undefined) },
        { id: 'd2', confirmed: true, coupon, promotionCode: pc, destroy: jest.fn().mockResolvedValue(undefined) },
        { id: 'd3', confirmed: false, coupon, promotionCode: pc, destroy: jest.fn().mockResolvedValue(undefined) },
      ] as any;

      jest.spyOn(Discount, 'findAll').mockResolvedValue(discounts);

      await rollbackDiscountUsageForCheckoutSession('cs_1');

      // For unique coupon and promotion code, update should be called once each
      expect(coupon.update).toHaveBeenCalledWith({ times_redeemed: 1, valid: true });
      expect(pc.update).toHaveBeenCalledWith({ times_redeemed: 2, active: true });
      // All discounts should be destroyed
      discounts.forEach((d: any) => expect(d.destroy).toHaveBeenCalled());
    });

    it('early returns when session not found or completed', async () => {
      const findByPkSpy = jest.spyOn(CheckoutSession, 'findByPk');
      const discountFindAllSpy = jest.spyOn(Discount, 'findAll');

      findByPkSpy.mockResolvedValueOnce(null as any);
      await rollbackDiscountUsageForCheckoutSession('missing');

      findByPkSpy.mockResolvedValueOnce({ id: 'cs_2', status: 'complete' } as any);
      await rollbackDiscountUsageForCheckoutSession('cs_2');

      // Should not query discounts in either case
      expect(discountFindAllSpy).not.toHaveBeenCalled();
    });
  });
});
