import {
  validCoupon,
  validPromotionCode,
  calculateDiscountAmount,
  checkPromotionCodeEligibility,
  getValidDiscountsForSubscriptionBilling,
  expandLineItemsWithCouponInfo,
  expandDiscountsWithDetails,
  createDiscountRecordsForCheckout,
  updateSubscriptionDiscountReferences,
} from '../../src/libs/discount/coupon';
import { Coupon, Customer, Discount, PromotionCode, Subscription, PaymentCurrency } from '../../src/store/models';

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() }));

describe('libs/discount/coupon.ts', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    jest.restoreAllMocks();
  });

  describe('validCoupon', () => {
    it('rejects invalid or expired or fully redeemed coupon', () => {
      expect(validCoupon({ valid: false } as any).valid).toBe(false);
      const expired = Math.floor(Date.now() / 1000) - 10;
      expect(validCoupon({ valid: true, redeem_by: expired } as any).valid).toBe(false);
      expect(validCoupon({ valid: true, max_redemptions: 1, times_redeemed: 1 } as any).valid).toBe(false);
    });

    it('enforces applies_to products if provided', () => {
      const coupon = { valid: true, applies_to: { products: ['p1'] } } as any;
      const lineItems = [{ price: { product_id: 'p2' } }] as any;
      const res = validCoupon(coupon, lineItems);
      expect(res.valid).toBe(false);
    });

    it('passes when valid and applicable', () => {
      const coupon = { valid: true } as any;
      expect(validCoupon(coupon).valid).toBe(true);
    });
  });

  describe('validPromotionCode', () => {
    it('rejects inactive/expired/max redeemed', async () => {
      const now = Math.floor(Date.now() / 1000);
      await expect(validPromotionCode({ active: false } as any, {})).resolves.toEqual({
        valid: false,
        reason: expect.any(String),
      });
      await expect(validPromotionCode({ active: true, expires_at: now - 30 } as any, {})).resolves.toEqual({
        valid: false,
        reason: expect.any(String),
      });
      await expect(
        validPromotionCode({ active: true, max_redemptions: 1, times_redeemed: 1 } as any, {})
      ).resolves.toEqual({ valid: false, reason: expect.any(String) });
    });

    it('checks user restriction and first time transaction', async () => {
      jest.spyOn(Customer, 'findByPkOrDid').mockResolvedValue({ did: 'did:user:1' } as any);
      const pc: any = { active: true, verification_type: 'user_restricted', customer_dids: ['did:user:1'] };
      await expect(validPromotionCode(pc, { customerId: 'c1' })).resolves.toEqual({ valid: true });

      jest.spyOn(Discount, 'count').mockResolvedValue(1 as any);
      const pc2: any = {
        active: true,
        restrictions: { first_time_transaction: true },
      };
      await expect(validPromotionCode(pc2, { customerId: 'c1' })).resolves.toEqual({
        valid: false,
        reason: expect.any(String),
      });
    });

    it('allows first_time_transaction re-submit by excluding current session', async () => {
      jest.spyOn(Customer, 'findByPkOrDid').mockResolvedValue({ did: 'did:user:1' } as any);
      // When checkoutSessionId is provided, the Discount.count query excludes current session
      jest.spyOn(Discount, 'count').mockResolvedValue(0 as any);
      const pc: any = {
        active: true,
        restrictions: { first_time_transaction: true },
        coupon_id: 'c1',
      };
      await expect(validPromotionCode(pc, { customerId: 'c1', checkoutSessionId: 'cs_123' })).resolves.toEqual({
        valid: true,
      });
      // Verify the count query was called with Op.ne exclusion
      expect(Discount.count).toHaveBeenCalledWith({
        where: expect.objectContaining({
          checkout_session_id: expect.any(Object),
        }),
      });
    });

    it('checks minimum amount by currency', async () => {
      jest.spyOn(PaymentCurrency, 'findByPk').mockResolvedValue({ id: 'usd', decimal: 2, symbol: 'USD' } as any);
      const pc: any = {
        active: true,
        restrictions: { minimum_amount: '200', minimum_amount_currency: 'usd' },
      };
      await expect(validPromotionCode(pc, { amount: '100', currencyId: 'usd' })).resolves.toEqual({
        valid: false,
        reason: expect.stringContaining('minimum purchase'),
      });
    });
  });

  describe('calculateDiscountAmount', () => {
    it('handles percent_off and amount_off with cap', () => {
      expect(
        calculateDiscountAmount({ percent_off: 25 } as any, '400', { id: 'usd', decimal: 2, symbol: 'USD' } as any)
      ).toBe('100');

      expect(
        calculateDiscountAmount({ amount_off: '150', currency_id: 'usd' } as any, '100', {
          id: 'usd',
          decimal: 2,
          symbol: 'USD',
        } as any)
      ).toBe('100');
    });
  });

  describe('checkPromotionCodeEligibility', () => {
    it('validates promotion and coupon together', async () => {
      jest.spyOn(Coupon, 'findByPk').mockResolvedValue({ valid: true } as any);
      jest.spyOn(Customer, 'findByPkOrDid').mockResolvedValue({ did: 'did:user:1' } as any);
      jest.spyOn(PaymentCurrency, 'findByPk').mockResolvedValue({ id: 'usd', decimal: 2, symbol: 'USD' } as any);
      jest.spyOn(Discount, 'count').mockResolvedValue(0 as any);
      const couponModule = await import('../../src/libs/discount/coupon');
      jest.spyOn(couponModule, 'validCoupon').mockReturnValue({ valid: true } as any);
      const result = await checkPromotionCodeEligibility({
        promotionCode: { active: true } as any,
        couponId: 'c1',
        customerId: 'u1',
        amount: '100',
        currencyId: 'usd',
      });
      expect(result.eligible).toBe(true);
    });

    it('allows re-submit when max_redemptions reached by current session', async () => {
      // Scenario: promo has max_redemptions=1, times_redeemed=1 (from this session's first submit)
      // User closed Stripe modal and re-submits — should NOT be rejected
      jest.spyOn(Discount, 'count').mockResolvedValue(1 as any); // current session has 1 confirmed discount
      jest.spyOn(Coupon, 'findByPk').mockResolvedValue({ valid: true, times_redeemed: 1, max_redemptions: 1 } as any);
      jest.spyOn(Customer, 'findByPkOrDid').mockResolvedValue({ did: 'did:user:1' } as any);
      const couponModule = await import('../../src/libs/discount/coupon');
      jest.spyOn(couponModule, 'validCoupon').mockReturnValue({ valid: true } as any);

      const result = await checkPromotionCodeEligibility({
        promotionCode: { id: 'pc1', active: true, max_redemptions: 1, times_redeemed: 1 } as any,
        couponId: 'c1',
        customerId: 'u1',
        amount: '100',
        currencyId: 'usd',
        checkoutSessionId: 'cs_123',
      });
      expect(result.eligible).toBe(true);
    });

    it('rejects when max_redemptions reached by other sessions', async () => {
      // Scenario: promo has max_redemptions=1, times_redeemed=1, but NOT from current session
      jest.spyOn(Discount, 'count').mockResolvedValue(0 as any); // current session has no confirmed discount
      jest.spyOn(Coupon, 'findByPk').mockResolvedValue({ valid: true } as any);

      const result = await checkPromotionCodeEligibility({
        promotionCode: { id: 'pc1', active: true, max_redemptions: 1, times_redeemed: 1 } as any,
        couponId: 'c1',
        customerId: 'u1',
        amount: '100',
        currencyId: 'usd',
        checkoutSessionId: 'cs_456',
      });
      expect(result.eligible).toBe(false);
      expect(result.reason).toContain('fully redeemed');
    });
  });

  describe('validateDiscountForBilling & getValidDiscountsForSubscriptionBilling', () => {
    it('filters discounts by duration logic', async () => {
      const now = Math.floor(Date.now() / 1000);
      jest.spyOn(Discount, 'findAll').mockResolvedValue([
        { id: 'd_once', coupon_id: 'c_once' },
        { id: 'd_repired', coupon_id: 'c_repired', end: now - 1 },
        { id: 'd_forever', coupon_id: 'c_forever' },
      ] as any);

      jest.spyOn(Coupon, 'findByPk').mockImplementation((id) => {
        if (id === 'c_once') return { duration: 'once' } as any;
        if (id === 'c_repired') return { duration: 'repeating' } as any;
        return { duration: 'forever' } as any;
      });

      const { validDiscounts, expiredDiscounts } = await getValidDiscountsForSubscriptionBilling({
        subscriptionId: 'sub_1',
        customerId: 'u1',
      });

      expect(validDiscounts.map((d) => d.id)).toEqual(['d_forever']);
      expect(expiredDiscounts.map((d) => d.id).sort()).toEqual(['d_once', 'd_repired'].sort());
    });
  });

  describe('expandLineItemsWithCouponInfo & expandDiscountsWithDetails', () => {
    it('expands discount info on line items', async () => {
      jest.spyOn(Coupon, 'findByPk').mockResolvedValue({
        id: 'c1',
        name: 'c',
        amount_off: '10',
        percent_off: 0,
        currency_id: 'usd',
        duration: 'once',
      } as any);
      jest.spyOn(PromotionCode, 'findByPk').mockResolvedValue({ id: 'pc1', code: 'CODE1' } as any);

      const items = [{ id: 'i1', discount_amounts: [{ amount: '10', coupon: 'c1' }] }] as any;

      const res = await expandLineItemsWithCouponInfo(items, [{ coupon: 'c1', promotion_code: 'pc1' }], 'usd');
      expect(res[0]?.discount_amounts[0]?.coupon.id).toBe('c1');
      expect(res[0]?.discount_amounts[0]?.promotion_code.id).toBe('pc1');
    });

    it('expands discounts array with details', async () => {
      jest.spyOn(Coupon, 'findByPk').mockResolvedValue({ id: 'c1', name: 'c' } as any);
      jest.spyOn(PromotionCode, 'findByPk').mockResolvedValue({ id: 'pc1', code: 'CODE1' } as any);
      const res = await expandDiscountsWithDetails([{ coupon: 'c1', promotion_code: 'pc1' }]);
      expect(res[0].coupon_details.id).toBe('c1');
      expect(res[0].promotion_code_details.id).toBe('pc1');
    });
  });

  describe('createDiscountRecordsForCheckout & updateSubscriptionDiscountReferences', () => {
    it('creates/updates records and usage counts per discount config', async () => {
      const checkoutSession: any = {
        id: 'cs_1',
        discounts: [{ coupon: 'c1', promotion_code: 'pc1', discount_amount: '50' }],
      };

      jest.spyOn(Discount, 'findAll').mockResolvedValue([] as any);
      jest.spyOn(Coupon, 'findByPk').mockResolvedValue({ id: 'c1', livemode: false, duration: 'once' } as any);
      jest.spyOn(PromotionCode, 'findByPk').mockResolvedValue({ id: 'pc1', verification_type: 'none' } as any);
      jest
        .spyOn(Discount, 'create')
        .mockImplementation(
          (data: any) => Promise.resolve({ id: `d_${Date.now()}`, update: jest.fn(), ...data }) as any
        );

      // Stubs for usage update path
      jest.spyOn(Coupon.prototype as any, 'update').mockResolvedValue(undefined);
      jest.spyOn(PromotionCode.prototype as any, 'update').mockResolvedValue(undefined);

      const { discountRecords, subscriptionsUpdated } = await createDiscountRecordsForCheckout({
        checkoutSession,
        customerId: 'u1',
        subscriptionIds: ['sub_1'],
      });

      expect(discountRecords.length).toBeGreaterThan(0);
      expect(subscriptionsUpdated).toEqual(['sub_1']);

      // updateSubscriptionDiscountReferences
      jest.spyOn(Subscription, 'update').mockResolvedValue([1] as any);
      const { updatedSubscriptions } = await updateSubscriptionDiscountReferences({
        discountRecords,
        subscriptionsUpdated,
      });
      expect(updatedSubscriptions).toEqual(['sub_1']);
    });
  });
});
