const {
  parseStripeError,
  PaymentError,
  DECLINE_MESSAGES,
} = require('../../../utils/stripeErrors');

describe('Stripe Errors Utility', () => {
  describe('DECLINE_MESSAGES', () => {
    const requiredProperties = [
      'ownerMessage',
      'renterMessage',
      'canOwnerRetry',
      'requiresNewPaymentMethod',
    ];

    test('each decline code has all required properties', () => {
      for (const [code, messages] of Object.entries(DECLINE_MESSAGES)) {
        for (const prop of requiredProperties) {
          expect(messages).toHaveProperty(prop);
        }
      }
    });

    describe('Additional card decline codes', () => {
      const additionalCardCodes = [
        'call_issuer',
        'do_not_try_again',
        'duplicate_transaction',
        'issuer_not_available',
        'restricted_card',
        'withdrawal_count_limit_exceeded',
        'not_permitted',
        'invalid_amount',
        'security_violation',
        'stop_payment_order',
        'transaction_not_allowed',
      ];

      test.each(additionalCardCodes)('%s exists in DECLINE_MESSAGES', (code) => {
        expect(DECLINE_MESSAGES).toHaveProperty(code);
      });

      test.each(additionalCardCodes)('%s has all required properties', (code) => {
        const messages = DECLINE_MESSAGES[code];
        expect(messages).toHaveProperty('ownerMessage');
        expect(messages).toHaveProperty('renterMessage');
        expect(messages).toHaveProperty('canOwnerRetry');
        expect(messages).toHaveProperty('requiresNewPaymentMethod');
      });

      test.each(additionalCardCodes)('%s has non-empty messages', (code) => {
        expect(DECLINE_MESSAGES[code].ownerMessage.length).toBeGreaterThan(0);
        expect(DECLINE_MESSAGES[code].renterMessage.length).toBeGreaterThan(0);
      });

      // Codes that allow retry (temporary issues)
      const retryableCodes = [
        'call_issuer',
        'duplicate_transaction',
        'issuer_not_available',
        'withdrawal_count_limit_exceeded',
      ];

      test.each(retryableCodes)('%s allows owner retry', (code) => {
        expect(DECLINE_MESSAGES[code].canOwnerRetry).toBe(true);
      });

      // Codes that require new payment method (permanent issues)
      const permanentCodes = [
        'do_not_try_again',
        'restricted_card',
        'not_permitted',
        'security_violation',
        'stop_payment_order',
        'transaction_not_allowed',
      ];

      test.each(permanentCodes)('%s requires new payment method', (code) => {
        expect(DECLINE_MESSAGES[code].requiresNewPaymentMethod).toBe(true);
      });

      test('do_not_try_again has isPermanent flag', () => {
        expect(DECLINE_MESSAGES.do_not_try_again.isPermanent).toBe(true);
      });

      test('invalid_amount does not require new payment method', () => {
        // This is a platform/amount issue, not a card issue
        expect(DECLINE_MESSAGES.invalid_amount.requiresNewPaymentMethod).toBe(false);
      });
    });
  });

  describe('parseStripeError', () => {
    test('parses card decline error with known code', () => {
      const error = {
        type: 'StripeCardError',
        code: 'card_declined',
        decline_code: 'insufficient_funds',
        message: 'Your card has insufficient funds.',
      };

      const result = parseStripeError(error);

      expect(result.code).toBe('insufficient_funds');
      expect(result.ownerMessage).toBe("The renter's card has insufficient funds.");
      expect(result.renterMessage).toBe('Your card has insufficient funds.');
      expect(result.canOwnerRetry).toBe(false);
      expect(result.requiresNewPaymentMethod).toBe(false);
    });

    test('parses card decline error - do_not_try_again (permanent)', () => {
      const error = {
        type: 'StripeCardError',
        code: 'card_declined',
        decline_code: 'do_not_try_again',
        message: 'Do not try again.',
      };

      const result = parseStripeError(error);

      expect(result.code).toBe('do_not_try_again');
      expect(result.ownerMessage).toBe("The renter's card has been permanently declined.");
      expect(result.requiresNewPaymentMethod).toBe(true);
    });

    test('parses card decline error - call_issuer (retryable)', () => {
      const error = {
        type: 'StripeCardError',
        code: 'card_declined',
        decline_code: 'call_issuer',
        message: 'Call issuer.',
      };

      const result = parseStripeError(error);

      expect(result.code).toBe('call_issuer');
      expect(result.canOwnerRetry).toBe(true);
      expect(result.requiresNewPaymentMethod).toBe(false);
    });

    test('parses card decline error - withdrawal_count_limit_exceeded', () => {
      const error = {
        type: 'StripeCardError',
        code: 'card_declined',
        decline_code: 'withdrawal_count_limit_exceeded',
        message: 'Withdrawal count limit exceeded.',
      };

      const result = parseStripeError(error);

      expect(result.code).toBe('withdrawal_count_limit_exceeded');
      expect(result.renterMessage).toContain('daily limit');
      expect(result.canOwnerRetry).toBe(true);
    });

    test('returns default error for unknown decline code', () => {
      const error = {
        type: 'StripeCardError',
        code: 'card_declined',
        decline_code: 'unknown_code_xyz',
        message: 'Unknown error',
      };

      const result = parseStripeError(error);

      expect(result.code).toBe('unknown_code_xyz');
      expect(result.ownerMessage).toBe('The payment could not be processed.');
      expect(result.requiresNewPaymentMethod).toBe(true);
    });

    test('handles StripeAPIError', () => {
      const error = {
        type: 'StripeAPIError',
        message: 'API error',
        code: 'api_error',
      };

      const result = parseStripeError(error);

      expect(result.code).toBe('api_error');
      expect(result.canOwnerRetry).toBe(true);
    });

    test('handles StripeRateLimitError', () => {
      const error = {
        type: 'StripeRateLimitError',
        message: 'Rate limit',
        code: 'rate_limit_error',
      };

      const result = parseStripeError(error);

      expect(result.code).toBe('rate_limit');
      expect(result.canOwnerRetry).toBe(true);
    });

    test('includes original error info for logging', () => {
      const error = {
        type: 'StripeCardError',
        code: 'card_declined',
        decline_code: 'insufficient_funds',
        message: 'Original Stripe message',
      };

      const result = parseStripeError(error);

      expect(result._originalMessage).toBe('Original Stripe message');
      expect(result._stripeCode).toBe('card_declined');
    });
  });

  describe('PaymentError', () => {
    test('creates PaymentError with all properties', () => {
      const parsedError = {
        code: 'insufficient_funds',
        ownerMessage: "The renter's card has insufficient funds.",
        renterMessage: 'Your card has insufficient funds.',
        canOwnerRetry: false,
        requiresNewPaymentMethod: false,
        _originalMessage: 'Original message',
        _stripeCode: 'card_declined',
      };

      const error = new PaymentError(parsedError);

      expect(error).toBeInstanceOf(Error);
      expect(error.name).toBe('PaymentError');
      expect(error.code).toBe('insufficient_funds');
      expect(error.ownerMessage).toBe("The renter's card has insufficient funds.");
      expect(error.renterMessage).toBe('Your card has insufficient funds.');
      expect(error.requiresNewPaymentMethod).toBe(false);
    });

    test('toJSON returns serializable object', () => {
      const parsedError = {
        code: 'expired_card',
        ownerMessage: "The renter's card has expired.",
        renterMessage: 'Your card has expired.',
        canOwnerRetry: false,
        requiresNewPaymentMethod: true,
        _originalMessage: 'Should not appear',
        _stripeCode: 'card_declined',
      };

      const error = new PaymentError(parsedError);
      const json = error.toJSON();

      expect(json).toHaveProperty('code', 'expired_card');
      expect(json).toHaveProperty('ownerMessage');
      expect(json).toHaveProperty('renterMessage');
      expect(json).not.toHaveProperty('_originalMessage');
      expect(json).not.toHaveProperty('_stripeCode');
    });
  });
});
