/**
 * Payment Order Service Property Tests
 * **Feature: invitation-commission-system, Property 1: Payment order validation**
 */

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

jest.mock('../models/Appointment', () => ({
  findByPk: 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 User = require('../models/User');
const Appointment = require('../models/Appointment');
const PaymentOrder = require('../models/PaymentOrder');
const commissionService = require('../services/commissionService');
const paymentOrderService = require('../services/paymentOrderService');

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

  /**
   * **Feature: invitation-commission-system, Property 1: Payment order validation**
   * *For any* payment order creation request, the system should accept the request
   * if and only if it contains valid userId, amount > 0, paymentTime, and serviceContent fields.
   * **Validates: Requirements 1.1, 1.2, 1.3**
   */
  describe('Property 1: Payment order validation', () => {
    it('should reject orders with missing userId', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.double({ min: 0.01, max: 100000, noNaN: true }),
          fc.string({ minLength: 1, maxLength: 500 }),
          fc.date(),
          async (amount, serviceContent, paymentTime) => {
            // Act & Assert
            await expect(
              paymentOrderService.createPaymentOrder({
                userId: null, // Missing userId
                amount,
                serviceContent,
                paymentTime,
              }, 'admin-uuid')
            ).rejects.toThrow('User ID is required');
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should reject orders with invalid amount', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.oneof(
            fc.constant(0),
            fc.constant(-1),
            fc.double({ min: -1000, max: 0, noNaN: true }),
            fc.constant(null),
            fc.constant(undefined),
          ),
          fc.string({ minLength: 1, maxLength: 500 }),
          fc.date(),
          async (invalidAmount, serviceContent, paymentTime) => {
            // Act & Assert
            await expect(
              paymentOrderService.createPaymentOrder({
                userId: 'user-uuid',
                amount: invalidAmount,
                serviceContent,
                paymentTime,
              }, 'admin-uuid')
            ).rejects.toThrow('Valid amount is required');
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should reject orders with missing serviceContent', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.double({ min: 0.01, max: 100000, noNaN: true }),
          fc.date(),
          async (amount, paymentTime) => {
            User.findByPk.mockResolvedValue({ id: 'user-uuid' });

            // Act & Assert
            await expect(
              paymentOrderService.createPaymentOrder({
                userId: 'user-uuid',
                amount,
                serviceContent: '', // Empty serviceContent
                paymentTime,
              }, 'admin-uuid')
            ).rejects.toThrow('Service content is required');
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should reject orders with missing paymentTime', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.double({ min: 0.01, max: 100000, noNaN: true }),
          fc.string({ minLength: 1, maxLength: 500 }),
          async (amount, serviceContent) => {
            User.findByPk.mockResolvedValue({ id: 'user-uuid' });

            // Act & Assert
            await expect(
              paymentOrderService.createPaymentOrder({
                userId: 'user-uuid',
                amount,
                serviceContent,
                paymentTime: null, // Missing paymentTime
              }, 'admin-uuid')
            ).rejects.toThrow('Payment time is required');
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should accept orders with all required fields', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.uuid(),
          fc.double({ min: 0.01, max: 100000, noNaN: true }),
          fc.string({ minLength: 1, maxLength: 500 }),
          fc.date({ min: new Date('2020-01-01'), max: new Date('2030-12-31') }),
          async (userId, amount, serviceContent, paymentTime) => {
            // Arrange
            User.findByPk.mockResolvedValue({ id: userId });
            PaymentOrder.findOne.mockResolvedValue(null);
            PaymentOrder.create.mockResolvedValue({
              id: 'order-uuid',
              orderNo: 'PO123456789',
              userId,
              amount,
              serviceContent,
              paymentTime,
              status: 'active',
              createdAt: new Date(),
            });
            commissionService.calculateCommission.mockResolvedValue(null);

            // Act
            const result = await paymentOrderService.createPaymentOrder({
              userId,
              amount,
              serviceContent,
              paymentTime,
            }, 'admin-uuid');

            // Assert
            expect(result.paymentOrder).toBeDefined();
            expect(result.paymentOrder.userId).toBe(userId);
            expect(parseFloat(result.paymentOrder.amount)).toBeCloseTo(amount, 2);
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should accept orders with optional appointmentId', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.uuid(),
          fc.option(fc.uuid(), { nil: undefined }),
          fc.double({ min: 0.01, max: 100000, noNaN: true }),
          fc.string({ minLength: 1, maxLength: 500 }),
          fc.date({ min: new Date('2020-01-01'), max: new Date('2030-12-31') }),
          async (userId, appointmentId, amount, serviceContent, paymentTime) => {
            // Arrange
            User.findByPk.mockResolvedValue({ id: userId });
            if (appointmentId) {
              Appointment.findByPk.mockResolvedValue({ id: appointmentId });
            }
            PaymentOrder.findOne.mockResolvedValue(null);
            PaymentOrder.create.mockResolvedValue({
              id: 'order-uuid',
              orderNo: 'PO123456789',
              userId,
              appointmentId: appointmentId || null,
              amount,
              serviceContent,
              paymentTime,
              status: 'active',
              createdAt: new Date(),
            });
            commissionService.calculateCommission.mockResolvedValue(null);

            // Act
            const result = await paymentOrderService.createPaymentOrder({
              userId,
              appointmentId,
              amount,
              serviceContent,
              paymentTime,
            }, 'admin-uuid');

            // Assert: Should succeed regardless of appointmentId presence
            expect(result.paymentOrder).toBeDefined();
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should accept orders with optional notes', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.uuid(),
          fc.double({ min: 0.01, max: 100000, noNaN: true }),
          fc.string({ minLength: 1, maxLength: 500 }),
          fc.date({ min: new Date('2020-01-01'), max: new Date('2030-12-31') }),
          fc.option(fc.string({ maxLength: 1000 }), { nil: undefined }),
          async (userId, amount, serviceContent, paymentTime, notes) => {
            // Arrange
            User.findByPk.mockResolvedValue({ id: userId });
            PaymentOrder.findOne.mockResolvedValue(null);
            PaymentOrder.create.mockResolvedValue({
              id: 'order-uuid',
              orderNo: 'PO123456789',
              userId,
              amount,
              serviceContent,
              paymentTime,
              notes: notes || null,
              status: 'active',
              createdAt: new Date(),
            });
            commissionService.calculateCommission.mockResolvedValue(null);

            // Act
            const result = await paymentOrderService.createPaymentOrder({
              userId,
              amount,
              serviceContent,
              paymentTime,
              notes,
            }, 'admin-uuid');

            // Assert: Should succeed regardless of notes presence
            expect(result.paymentOrder).toBeDefined();
          }
        ),
        { numRuns: 100 }
      );
    });
  });
});
