import { describe, it, expect } from 'vitest';
import {
  rowThreshold, rowEffectiveStatus, rowIsPaid, rowIsDebt,
  sortTrackerRows, moveInArray, paymentSummary, amountSearchText,
  normalizeTrackerSortKey, normalizeTrackerSortDir,
  TRACKER_SORT_DEFAULT, TRACKER_SORT_ASC, TRACKER_SORT_DESC,
} from './trackerUtils';

const row = (overrides = {}) => ({
  id: 1, name: 'Bill', due_date: '2026-06-12', due_day: 12, bucket: '1st',
  status: 'upcoming', expected_amount: 100, actual_amount: null,
  total_paid: 0, is_skipped: false, ...overrides,
});

describe('rowThreshold / rowEffectiveStatus / rowIsPaid', () => {
  it('uses the monthly override over the bill default', () => {
    expect(rowThreshold(row())).toBe(100);
    expect(rowThreshold(row({ actual_amount: 80 }))).toBe(80);
    expect(rowThreshold(row({ actual_amount: 0 }))).toBe(0); // 0 is a real override
  });

  it('promotes to paid when payments meet the threshold', () => {
    expect(rowEffectiveStatus(row({ total_paid: 100, status: 'upcoming' }))).toBe('paid');
    expect(rowEffectiveStatus(row({ total_paid: 99.99, status: 'upcoming' }))).toBe('upcoming');
    // the override lowers the bar
    expect(rowEffectiveStatus(row({ total_paid: 80, actual_amount: 80, status: 'late' }))).toBe('paid');
  });

  it('skipped always wins; autodraft with a pending suggestion is not "paid"', () => {
    expect(rowEffectiveStatus(row({ is_skipped: true, total_paid: 500 }))).toBe('skipped');
    expect(rowIsPaid(row({ status: 'autodraft' }))).toBe(true);
    expect(rowIsPaid(row({ status: 'autodraft', autopay_suggestion: { id: 9 } }))).toBe(false);
  });

  it('detects debt rows by balance, minimum payment, or category', () => {
    expect(rowIsDebt(row({ current_balance: 1200 }))).toBe(true);
    expect(rowIsDebt(row({ minimum_payment: 35 }))).toBe(true);
    expect(rowIsDebt(row({ category_name: 'Credit Cards' }))).toBe(true);
    expect(rowIsDebt(row({ category_name: 'Utilities' }))).toBe(false);
  });
});

describe('sortTrackerRows', () => {
  const rows = [
    row({ id: 1, name: 'Zeta', due_date: '2026-06-20', expected_amount: 50, status: 'paid' }),
    row({ id: 2, name: 'Alpha', due_date: '2026-06-05', expected_amount: 200, status: 'missed' }),
    row({ id: 3, name: 'Mid', due_date: '2026-06-12', expected_amount: 100, status: 'upcoming' }),
  ];

  it('manual (default) sort leaves order untouched', () => {
    expect(sortTrackerRows(rows, TRACKER_SORT_DEFAULT, TRACKER_SORT_ASC)).toEqual(rows);
  });

  it('sorts by name, both directions', () => {
    expect(sortTrackerRows(rows, 'name', TRACKER_SORT_ASC).map(r => r.name)).toEqual(['Alpha', 'Mid', 'Zeta']);
    expect(sortTrackerRows(rows, 'name', TRACKER_SORT_DESC).map(r => r.name)).toEqual(['Zeta', 'Mid', 'Alpha']);
  });

  it('does not mutate the input array', () => {
    const copy = [...rows];
    sortTrackerRows(rows, 'name', TRACKER_SORT_ASC);
    expect(rows).toEqual(copy);
  });

  it('normalizes unknown keys/dirs to safe defaults', () => {
    expect(normalizeTrackerSortKey('nope')).toBe(TRACKER_SORT_DEFAULT);
    expect(normalizeTrackerSortDir('sideways')).toBe(TRACKER_SORT_ASC);
  });
});

describe('paymentSummary (payment form math)', () => {
  it('computes partial payment state', () => {
    const s = paymentSummary(row({ total_paid: 40 }), 100);
    expect(s).toMatchObject({ target: 100, paid: 40, paidTowardDue: 40, overpaid: 0, remaining: 60, percent: 40, partial: true });
  });

  it('caps percent at 100 and tracks overpayment', () => {
    const s = paymentSummary(row({ total_paid: 130 }), 100);
    expect(s.percent).toBe(100);
    expect(s.overpaid).toBe(30);
    expect(s.remaining).toBe(0);
    expect(s.partial).toBe(false);
  });

  it('handles a zero target without dividing by zero', () => {
    const s = paymentSummary(row({ total_paid: 10 }), 0);
    expect(s.percent).toBe(0);
    expect(s.remaining).toBe(0);
  });

  it('prefers server-computed paid_toward_due/overpaid_amount when present', () => {
    const s = paymentSummary(row({ total_paid: 130, paid_toward_due: 100, overpaid_amount: 30 }), 100);
    expect(s.paidTowardDue).toBe(100);
    expect(s.overpaid).toBe(30);
  });
});

describe('amountSearchText / moveInArray', () => {
  it('indexes amounts in plain, fixed, and $ forms', () => {
    expect(amountSearchText(12.5)).toBe('12.5 12.50 $12.50');
    expect(amountSearchText(null, undefined, 'abc')).toBe('');
  });

  it('moves items preserving the rest of the order', () => {
    expect(moveInArray(['a', 'b', 'c', 'd'], 0, 2)).toEqual(['b', 'c', 'a', 'd']);
    expect(moveInArray(['a', 'b'], 1, 0)).toEqual(['b', 'a']);
  });
});
