/**
 * Payment Order Currency Filter Property Tests
 * **Feature: payment-order-currency-filter**
 */

const fc = require('fast-check');

// Mock database first
jest.mock('../config/database', () => ({
  sequelize: {
    define: jest.fn(() => ({})),
    transaction: jest.fn(() => Promise.resolve({
      commit: jest.fn(),
      rollback: jest.fn(),
    })),
  },
}));

// Mock models
jest.mock('../models/User', () => ({
  findByPk: jest.fn(),
  findOne: jest.fn(),
}));

jest.mock('../models/Appointment', () => ({
  findByPk: jest.fn(),
  findOne: jest.fn(),
}));

jest.mock('../models/PaymentOrder', () => ({
  findOne: jest.fn(),
  create: jest.fn(),
  findByPk: jest.fn(),
  findAndCountAll: jest.fn(),
}));

jest.mock('../models/Admin', () => ({
  findByPk: jest.fn(),
}));

jest.mock('../models/Commission', () => ({
  findAll: jest.fn(),
  findOne: jest.fn(),
}));

jest.mock('../services/commissionService', () => ({
  calculateCommission: jest.fn(),
}));

const PaymentOrder = require('../models/PaymentOrder');
const Commission = require('../models/Commission');
const paymentOrderService = require('../services/paymentOrderService');


// Generator for payment order with various currency amounts
const paymentOrderArbitrary = () => fc.record({
  id: fc.uuid(),
  orderNo: fc.string({ minLength: 10, maxLength: 20 }).map(s => `PO${s}`),
  userId: fc.uuid(),
  amountRmb: fc.oneof(
    fc.constant(null),
    fc.constant(0),
    fc.double({ min: 0.01, max: 100000, noNaN: true })
  ),
  amountPeso: fc.oneof(
    fc.constant(null),
    fc.constant(0),
    fc.double({ min: 0.01, max: 100000, noNaN: true })
  ),
  amount: fc.option(fc.double({ min: 0.01, max: 100000, noNaN: true }), { nil: null }),
  serviceContent: fc.string({ minLength: 1, maxLength: 200 }),
  paymentTime: fc.date({ min: new Date('2020-01-01'), max: new Date('2030-12-31') }),
  status: fc.constantFrom('active', 'cancelled'),
  createdAt: fc.date({ min: new Date('2020-01-01'), max: new Date('2030-12-31') }),
});

// Helper to create mock order with associations
const createMockOrder = (order) => ({
  ...order,
  user: { id: order.userId, uid: '123456', nickname: 'Test User', phone: '1234567890' },
  appointment: null,
  creator: { id: 'admin-uuid', username: 'admin', email: 'admin@test.com' },
});

describe('Payment Order Currency Filter - Property Tests', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  /**
   * **Feature: payment-order-currency-filter, Property 1: RMB Currency Filter Returns Only RMB Orders**
   * *For any* set of payment orders in the database, when the currency filter is set to "rmb",
   * all returned orders should have amountRmb greater than zero.
   * **Validates: Requirements 1.1, 3.1**
   */
  describe('Property 1: RMB Currency Filter Returns Only RMB Orders', () => {
    it('should return only orders with amountRmb > 0 when currency filter is "rmb"', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.array(paymentOrderArbitrary(), { minLength: 1, maxLength: 20 }),
          async (orders) => {
            // Clear mocks before each property test iteration
            jest.clearAllMocks();
            
            // Filter orders that should be returned (amountRmb > 0)
            const expectedOrders = orders.filter(o => o.amountRmb !== null && o.amountRmb > 0);
            const mockRows = expectedOrders.map(createMockOrder);

            // Setup mock
            PaymentOrder.findAndCountAll.mockResolvedValue({
              count: mockRows.length,
              rows: mockRows,
            });
            Commission.findAll.mockResolvedValue([]);

            // Act
            const result = await paymentOrderService.getPaymentOrders({ currency: 'rmb' });

            // Assert: All returned orders should have amountRmb > 0
            for (const record of result.records) {
              const amountRmb = parseFloat(record.amountRmb);
              expect(amountRmb).toBeGreaterThan(0);
            }

            // Verify the WHERE clause was called with correct filter
            expect(PaymentOrder.findAndCountAll).toHaveBeenCalledTimes(1);
            const callArgs = PaymentOrder.findAndCountAll.mock.calls[0][0];
            expect(callArgs.where.amountRmb).toBeDefined();
          }
        ),
        { numRuns: 100 }
      );
    });
  });


  /**
   * **Feature: payment-order-currency-filter, Property 2: Peso Currency Filter Returns Only Peso Orders**
   * *For any* set of payment orders in the database, when the currency filter is set to "peso",
   * all returned orders should have amountPeso greater than zero.
   * **Validates: Requirements 1.2, 3.2**
   */
  describe('Property 2: Peso Currency Filter Returns Only Peso Orders', () => {
    it('should return only orders with amountPeso > 0 when currency filter is "peso"', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.array(paymentOrderArbitrary(), { minLength: 1, maxLength: 20 }),
          async (orders) => {
            // Clear mocks before each property test iteration
            jest.clearAllMocks();
            
            // Filter orders that should be returned (amountPeso > 0)
            const expectedOrders = orders.filter(o => o.amountPeso !== null && o.amountPeso > 0);
            const mockRows = expectedOrders.map(createMockOrder);

            // Setup mock
            PaymentOrder.findAndCountAll.mockResolvedValue({
              count: mockRows.length,
              rows: mockRows,
            });
            Commission.findAll.mockResolvedValue([]);

            // Act
            const result = await paymentOrderService.getPaymentOrders({ currency: 'peso' });

            // Assert: All returned orders should have amountPeso > 0
            for (const record of result.records) {
              const amountPeso = parseFloat(record.amountPeso);
              expect(amountPeso).toBeGreaterThan(0);
            }

            // Verify the WHERE clause was called with correct filter
            expect(PaymentOrder.findAndCountAll).toHaveBeenCalledTimes(1);
            const callArgs = PaymentOrder.findAndCountAll.mock.calls[0][0];
            expect(callArgs.where.amountPeso).toBeDefined();
          }
        ),
        { numRuns: 100 }
      );
    });
  });

  /**
   * **Feature: payment-order-currency-filter, Property 3: No Currency Filter Returns All Orders**
   * *For any* set of payment orders in the database, when no currency filter is applied (empty or undefined),
   * the returned orders should include all orders regardless of their currency amounts.
   * **Validates: Requirements 1.3, 3.3**
   */
  describe('Property 3: No Currency Filter Returns All Orders', () => {
    it('should return all orders when no currency filter is applied', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.array(paymentOrderArbitrary(), { minLength: 1, maxLength: 20 }),
          fc.constantFrom(undefined, '', null),
          async (orders, currencyValue) => {
            // Clear mocks before each property test iteration
            jest.clearAllMocks();
            
            const mockRows = orders.map(createMockOrder);

            // Setup mock
            PaymentOrder.findAndCountAll.mockResolvedValue({
              count: mockRows.length,
              rows: mockRows,
            });
            Commission.findAll.mockResolvedValue([]);

            // Act
            const result = await paymentOrderService.getPaymentOrders({ currency: currencyValue });

            // Assert: Should return all orders
            expect(result.records.length).toBe(orders.length);

            // Verify the WHERE clause does NOT have currency filter
            expect(PaymentOrder.findAndCountAll).toHaveBeenCalledTimes(1);
            const callArgs = PaymentOrder.findAndCountAll.mock.calls[0][0];
            expect(callArgs.where.amountRmb).toBeUndefined();
            expect(callArgs.where.amountPeso).toBeUndefined();
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should ignore invalid currency values and return all orders', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.array(paymentOrderArbitrary(), { minLength: 1, maxLength: 20 }),
          fc.string({ minLength: 1, maxLength: 20 }).filter(s => s !== 'rmb' && s !== 'peso'),
          async (orders, invalidCurrency) => {
            // Clear mocks before each property test iteration
            jest.clearAllMocks();
            
            const mockRows = orders.map(createMockOrder);

            // Setup mock
            PaymentOrder.findAndCountAll.mockResolvedValue({
              count: mockRows.length,
              rows: mockRows,
            });
            Commission.findAll.mockResolvedValue([]);

            // Act
            const result = await paymentOrderService.getPaymentOrders({ currency: invalidCurrency });

            // Assert: Should return all orders (invalid currency is ignored)
            expect(result.records.length).toBe(orders.length);

            // Verify the WHERE clause does NOT have currency filter
            expect(PaymentOrder.findAndCountAll).toHaveBeenCalledTimes(1);
            const callArgs = PaymentOrder.findAndCountAll.mock.calls[0][0];
            expect(callArgs.where.amountRmb).toBeUndefined();
            expect(callArgs.where.amountPeso).toBeUndefined();
          }
        ),
        { numRuns: 100 }
      );
    });
  });


  /**
   * **Feature: payment-order-currency-filter, Property 4: Currency Filter Combines with Other Filters Using AND Logic**
   * *For any* combination of filters (currency, userId, status, dateRange),
   * all returned orders should satisfy ALL applied filter conditions simultaneously.
   * **Validates: Requirements 1.4**
   */
  describe('Property 4: Currency Filter Combines with Other Filters Using AND Logic', () => {
    it('should combine currency filter with userId filter using AND logic', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.array(paymentOrderArbitrary(), { minLength: 1, maxLength: 20 }),
          fc.uuid(),
          fc.constantFrom('rmb', 'peso'),
          async (orders, filterUserId, currency) => {
            // Clear mocks before each property test iteration
            jest.clearAllMocks();
            
            // Filter orders that match both conditions
            const expectedOrders = orders.filter(o => {
              const matchesUser = o.userId === filterUserId;
              const matchesCurrency = currency === 'rmb' 
                ? (o.amountRmb !== null && o.amountRmb > 0)
                : (o.amountPeso !== null && o.amountPeso > 0);
              return matchesUser && matchesCurrency;
            });
            const mockRows = expectedOrders.map(createMockOrder);

            // Setup mock
            PaymentOrder.findAndCountAll.mockResolvedValue({
              count: mockRows.length,
              rows: mockRows,
            });
            Commission.findAll.mockResolvedValue([]);

            // Act
            const result = await paymentOrderService.getPaymentOrders({ 
              currency, 
              userId: filterUserId 
            });

            // Assert: Verify WHERE clause has both filters
            expect(PaymentOrder.findAndCountAll).toHaveBeenCalledTimes(1);
            const callArgs = PaymentOrder.findAndCountAll.mock.calls[0][0];
            expect(callArgs.where.userId).toBe(filterUserId);
            if (currency === 'rmb') {
              expect(callArgs.where.amountRmb).toBeDefined();
            } else {
              expect(callArgs.where.amountPeso).toBeDefined();
            }
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should combine currency filter with status filter using AND logic', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.array(paymentOrderArbitrary(), { minLength: 1, maxLength: 20 }),
          fc.constantFrom('active', 'cancelled'),
          fc.constantFrom('rmb', 'peso'),
          async (orders, filterStatus, currency) => {
            // Clear mocks before each property test iteration
            jest.clearAllMocks();
            
            // Filter orders that match both conditions
            const expectedOrders = orders.filter(o => {
              const matchesStatus = o.status === filterStatus;
              const matchesCurrency = currency === 'rmb' 
                ? (o.amountRmb !== null && o.amountRmb > 0)
                : (o.amountPeso !== null && o.amountPeso > 0);
              return matchesStatus && matchesCurrency;
            });
            const mockRows = expectedOrders.map(createMockOrder);

            // Setup mock
            PaymentOrder.findAndCountAll.mockResolvedValue({
              count: mockRows.length,
              rows: mockRows,
            });
            Commission.findAll.mockResolvedValue([]);

            // Act
            const result = await paymentOrderService.getPaymentOrders({ 
              currency, 
              status: filterStatus 
            });

            // Assert: Verify WHERE clause has both filters
            expect(PaymentOrder.findAndCountAll).toHaveBeenCalledTimes(1);
            const callArgs = PaymentOrder.findAndCountAll.mock.calls[0][0];
            expect(callArgs.where.status).toBe(filterStatus);
            if (currency === 'rmb') {
              expect(callArgs.where.amountRmb).toBeDefined();
            } else {
              expect(callArgs.where.amountPeso).toBeDefined();
            }
          }
        ),
        { numRuns: 100 }
      );
    });
  });
});
