// Feature: client-portal, Property 4: Top songs ordering
// **Validates: Requirements 9.1**

import { describe, it, expect } from 'vitest';
import * as fc from 'fast-check';
import type { PortalTopSong } from '@/lib/mock-data/types';

/**
 * Property: For any set of PortalTopSong entries, the displayed list should
 * be sorted by income in descending order (highest earning songs first).
 *
 * The TopSongsCard component sorts songs with:
 *   [...songs].sort((a, b) => b.income - a.income)
 *
 * We test this sorting logic directly.
 */

/**
 * Sorts songs by income descending — mirrors TopSongsCard logic.
 */
function sortByIncomeDescending(songs: PortalTopSong[]): PortalTopSong[] {
  return [...songs].sort((a, b) => b.income - a.income);
}

/**
 * Generator for a PortalTopSong with arbitrary values.
 */
const portalTopSongArb: fc.Arbitrary<PortalTopSong> = fc.record({
  id: fc.uuid(),
  title: fc.string({ minLength: 1, maxLength: 50 }),
  composers: fc.array(fc.string({ minLength: 1, maxLength: 30 }), { minLength: 1, maxLength: 4 }),
  income: fc.integer({ min: 0, max: 100_000_000 }),
  percentage: fc.float({ min: 0, max: 100, noNaN: true }),
});

describe('Property 4: Top songs ordering', () => {
  it('for any array of songs, sorting by income descending produces a non-increasing sequence', () => {
    fc.assert(
      fc.property(
        fc.array(portalTopSongArb, { minLength: 0, maxLength: 20 }),
        (songs) => {
          const sorted = sortByIncomeDescending(songs);

          // Each element's income should be >= the next element's income
          for (let i = 0; i < sorted.length - 1; i++) {
            expect(sorted[i].income).toBeGreaterThanOrEqual(sorted[i + 1].income);
          }
        },
      ),
      { numRuns: 100 },
    );
  });

  it('for any array of songs, sorting preserves all original elements', () => {
    fc.assert(
      fc.property(
        fc.array(portalTopSongArb, { minLength: 0, maxLength: 20 }),
        (songs) => {
          const sorted = sortByIncomeDescending(songs);

          // Same length
          expect(sorted).toHaveLength(songs.length);

          // All original song ids are present in the sorted result
          const originalIds = songs.map((s) => s.id).sort();
          const sortedIds = sorted.map((s) => s.id).sort();
          expect(sortedIds).toEqual(originalIds);
        },
      ),
      { numRuns: 100 },
    );
  });

  it('for any array of songs, the first element has the highest income', () => {
    fc.assert(
      fc.property(
        fc.array(portalTopSongArb, { minLength: 1, maxLength: 20 }),
        (songs) => {
          const sorted = sortByIncomeDescending(songs);
          const maxIncome = Math.max(...songs.map((s) => s.income));

          // The first element should have the maximum income
          expect(sorted[0].income).toBe(maxIncome);
        },
      ),
      { numRuns: 100 },
    );
  });

  it('for any array of songs, the last element has the lowest income', () => {
    fc.assert(
      fc.property(
        fc.array(portalTopSongArb, { minLength: 1, maxLength: 20 }),
        (songs) => {
          const sorted = sortByIncomeDescending(songs);
          const minIncome = Math.min(...songs.map((s) => s.income));

          // The last element should have the minimum income
          expect(sorted[sorted.length - 1].income).toBe(minIncome);
        },
      ),
      { numRuns: 100 },
    );
  });
});
