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

/**
 * **Feature: overseas-appointment-backend, Property 44: Dashboard metrics accuracy**
 * **Feature: overseas-appointment-backend, Property 47: Conversion rate calculation**
 * Property-Based Tests for Admin Statistics
 */

class MockStatisticsDatabase {
  constructor() {
    this.users = [];
    this.appointments = [];
    this.withdrawals = [];
    this.invitations = [];
  }

  clear() {
    this.users = [];
    this.appointments = [];
    this.withdrawals = [];
    this.invitations = [];
  }

  addUser(userData) {
    const user = { id: Math.random().toString(36).substring(7), status: 'active', ...userData };
    this.users.push(user);
    return user;
  }

  addAppointment(data) {
    const apt = { id: Math.random().toString(36).substring(7), status: 'pending', ...data };
    this.appointments.push(apt);
    return apt;
  }

  addWithdrawal(data) {
    const wd = { id: Math.random().toString(36).substring(7), status: 'waiting', amount: 0, ...data };
    this.withdrawals.push(wd);
    return wd;
  }

  addInvitation(data) {
    const inv = { id: Math.random().toString(36).substring(7), rewardStatus: 'pending', firstPaymentAt: null, rewardAmount: 0, ...data };
    this.invitations.push(inv);
    return inv;
  }

  getDashboardMetrics() {
    const totalUsers = this.users.length;
    const activeAppointments = this.appointments.filter(a => ['pending', 'confirmed', 'in-progress'].includes(a.status)).length;
    const pendingWithdrawals = this.withdrawals.filter(w => w.status === 'waiting').length;
    const pendingWithdrawalAmount = this.withdrawals.filter(w => w.status === 'waiting').reduce((sum, w) => sum + parseFloat(w.amount || 0), 0);
    return { totalUsers, activeAppointments, pendingWithdrawals, pendingWithdrawalAmount };
  }

  getInvitationConversionRate() {
    const totalInvitations = this.invitations.length;
    const convertedInvitations = this.invitations.filter(inv => inv.firstPaymentAt !== null).length;
    const conversionRate = totalInvitations > 0 ? (convertedInvitations / totalInvitations) * 100 : 0;
    const totalRewards = this.invitations.filter(inv => inv.rewardStatus === 'credited').reduce((sum, inv) => sum + parseFloat(inv.rewardAmount || 0), 0);
    return { totalInvitations, convertedInvitations, conversionRate: parseFloat(conversionRate.toFixed(2)), totalRewards };
  }
}

const db = new MockStatisticsDatabase();

describe('Admin Statistics Property Tests', () => {
  beforeEach(() => { db.clear(); });

  /**
   * **Feature: overseas-appointment-backend, Property 44: Dashboard metrics accuracy**
   * **Validates: Requirements 10.1**
   */
  describe('Property 44: Dashboard metrics accuracy', () => {
    test('dashboard metrics should match actual database counts', () => {
      fc.assert(
        fc.property(
          fc.record({
            userCount: fc.integer({ min: 0, max: 50 }),
            activeAppointmentCount: fc.integer({ min: 0, max: 30 }),
            completedAppointmentCount: fc.integer({ min: 0, max: 20 }),
            pendingWithdrawalCount: fc.integer({ min: 0, max: 20 }),
            withdrawalAmount: fc.float({ min: 10, max: 500, noNaN: true }),
          }),
          ({ userCount, activeAppointmentCount, completedAppointmentCount, pendingWithdrawalCount, withdrawalAmount }) => {
            db.clear();
            for (let i = 0; i < userCount; i++) db.addUser({ nickname: `User ${i}` });
            const activeStatuses = ['pending', 'confirmed', 'in-progress'];
            for (let i = 0; i < activeAppointmentCount; i++) db.addAppointment({ status: activeStatuses[i % 3], amount: 100 });
            for (let i = 0; i < completedAppointmentCount; i++) db.addAppointment({ status: 'completed', amount: 100 });
            for (let i = 0; i < pendingWithdrawalCount; i++) db.addWithdrawal({ status: 'waiting', amount: withdrawalAmount });
            const metrics = db.getDashboardMetrics();
            expect(metrics.totalUsers).toBe(userCount);
            expect(metrics.activeAppointments).toBe(activeAppointmentCount);
            expect(metrics.pendingWithdrawals).toBe(pendingWithdrawalCount);
            expect(metrics.pendingWithdrawalAmount).toBeCloseTo(pendingWithdrawalCount * withdrawalAmount, 2);
          }
        ),
        { numRuns: 100 }
      );
    });
  });


  /**
   * **Feature: overseas-appointment-backend, Property 47: Conversion rate calculation**
   * **Validates: Requirements 10.4**
   */
  describe('Property 47: Conversion rate calculation', () => {
    test('conversion rate equals (converted / total) * 100', () => {
      fc.assert(
        fc.property(
          fc.record({
            totalInvitations: fc.integer({ min: 1, max: 50 }),
            convertedCount: fc.integer({ min: 0, max: 50 }),
            rewardAmount: fc.float({ min: 5, max: 100, noNaN: true }),
          }).filter(({ totalInvitations, convertedCount }) => convertedCount <= totalInvitations),
          ({ totalInvitations, convertedCount, rewardAmount }) => {
            db.clear();
            for (let i = 0; i < totalInvitations; i++) {
              const hasPayment = i < convertedCount;
              db.addInvitation({
                inviterId: 'inviter-1',
                inviteeId: `invitee-${i}`,
                firstPaymentAt: hasPayment ? new Date() : null,
                rewardAmount: hasPayment ? rewardAmount : 0,
                rewardStatus: hasPayment ? 'credited' : 'pending',
              });
            }
            const data = db.getInvitationConversionRate();
            const expectedRate = totalInvitations > 0 ? (convertedCount / totalInvitations) * 100 : 0;
            expect(data.totalInvitations).toBe(totalInvitations);
            expect(data.convertedInvitations).toBe(convertedCount);
            // Use precision 1 to avoid floating-point boundary issues (e.g., 40.625 vs 40.63)
            expect(data.conversionRate).toBeCloseTo(expectedRate, 1);
            expect(data.totalRewards).toBeCloseTo(convertedCount * rewardAmount, 1);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('conversion rate is 0 when no invitations exist', () => {
      db.clear();
      const data = db.getInvitationConversionRate();
      expect(data.totalInvitations).toBe(0);
      expect(data.convertedInvitations).toBe(0);
      expect(data.conversionRate).toBe(0);
      expect(data.totalRewards).toBe(0);
    });

    test('conversion rate is 100 when all invitations are converted', () => {
      fc.assert(
        fc.property(
          fc.record({ totalInvitations: fc.integer({ min: 1, max: 30 }), rewardAmount: fc.float({ min: 5, max: 100, noNaN: true }) }),
          ({ totalInvitations, rewardAmount }) => {
            db.clear();
            for (let i = 0; i < totalInvitations; i++) {
              db.addInvitation({ inviterId: 'inviter-1', inviteeId: `invitee-${i}`, firstPaymentAt: new Date(), rewardAmount, rewardStatus: 'credited' });
            }
            const data = db.getInvitationConversionRate();
            expect(data.conversionRate).toBe(100);
            expect(data.convertedInvitations).toBe(totalInvitations);
          }
        ),
        { numRuns: 100 }
      );
    });
  });
});