import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, waitFor, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter } from 'react-router-dom';
import PaycheckView, { ordinal, formatCurrency, formatPayDate } from '../pages/PaycheckView.jsx';

// ─── Pure function tests ───────────────────────────────────────────────────────

describe('ordinal', () => {
  it('formats 1st, 2nd, 3rd', () => {
    expect(ordinal(1)).toBe('1st');
    expect(ordinal(2)).toBe('2nd');
    expect(ordinal(3)).toBe('3rd');
    expect(ordinal(4)).toBe('4th');
    expect(ordinal(11)).toBe('11th');
    expect(ordinal(12)).toBe('12th');
    expect(ordinal(13)).toBe('13th');
    expect(ordinal(21)).toBe('21st');
    expect(ordinal(22)).toBe('22nd');
    expect(ordinal(31)).toBe('31st');
  });
});

describe('formatCurrency', () => {
  it('formats numbers as USD', () => {
    expect(formatCurrency(1500)).toBe('$1,500.00');
    expect(formatCurrency(0)).toBe('$0.00');
    expect(formatCurrency(99.9)).toBe('$99.90');
    expect(formatCurrency('150.5')).toBe('$150.50');
    expect(formatCurrency(null)).toBe('$0.00');
    expect(formatCurrency(undefined)).toBe('$0.00');
  });
});

describe('formatPayDate', () => {
  it('formats YYYY-MM-DD to readable date', () => {
    expect(formatPayDate('2026-03-01')).toBe('March 1, 2026');
    expect(formatPayDate('2026-01-15')).toBe('January 15, 2026');
    expect(formatPayDate('2025-12-31')).toBe('December 31, 2025');
  });
});

// ─── Component helpers ─────────────────────────────────────────────────────────

const virtualPaycheck = (num) => ({
  id: null,
  period_year: 2026,
  period_month: 3,
  paycheck_number: num,
  pay_date: `2026-03-${num === 1 ? '01' : '15'}`,
  gross: 3000,
  net: 2400,
  bills: [],
  one_time_expenses: [],
  financing: [],
});

const savedPaycheck = (num) => ({
  ...virtualPaycheck(num),
  id: num,
});

function mockFetch(paychecks, categories = []) {
  return vi.fn((url) => {
    if (url.includes('/api/paychecks')) {
      return Promise.resolve({ ok: true, json: () => Promise.resolve(paychecks) });
    }
    if (url.includes('/api/expense-categories')) {
      return Promise.resolve({ ok: true, json: () => Promise.resolve(categories) });
    }
    if (url.includes('/api/actuals')) {
      return Promise.resolve({ ok: true, json: () => Promise.resolve([]) });
    }
    return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
  });
}

function renderView() {
  return render(
    <MemoryRouter>
      <PaycheckView />
    </MemoryRouter>
  );
}

// ─── PaycheckView rendering tests ─────────────────────────────────────────────

describe('PaycheckView', () => {
  beforeEach(() => {
    // Fake only Date (not setTimeout/setInterval) so RTL's waitFor keeps working
    vi.useFakeTimers({ toFake: ['Date'], now: new Date('2026-03-19') });
  });

  afterEach(() => {
    vi.useRealTimers();
    vi.restoreAllMocks();
  });

  it('shows current month and year in nav', async () => {
    global.fetch = mockFetch([virtualPaycheck(1), virtualPaycheck(2)]);

    renderView();

    expect(screen.getByText('March 2026')).toBeInTheDocument();
  });

  it('shows loading state then renders paychecks', async () => {
    global.fetch = mockFetch([savedPaycheck(1), savedPaycheck(2)]);

    renderView();

    expect(screen.getByText('Loading…')).toBeInTheDocument();

    await waitFor(() => {
      expect(screen.getByText('Paycheck 1')).toBeInTheDocument();
      expect(screen.getByText('Paycheck 2')).toBeInTheDocument();
    });
  });

  it('shows virtual preview banner for virtual paychecks', async () => {
    global.fetch = mockFetch([virtualPaycheck(1), virtualPaycheck(2)]);

    renderView();

    await waitFor(() => {
      expect(screen.getByText(/Previewing from current settings/)).toBeInTheDocument();
    });
  });

  it('does not show preview banner for saved paychecks', async () => {
    global.fetch = mockFetch([savedPaycheck(1), savedPaycheck(2)]);

    renderView();

    await waitFor(() => {
      expect(screen.queryByText(/Previewing from current settings/)).not.toBeInTheDocument();
    });
  });

  it('shows error state on fetch failure', async () => {
    global.fetch = vi.fn().mockRejectedValue(new Error('Network error'));

    renderView();

    await waitFor(() => {
      expect(screen.getByText(/Error: Network error/)).toBeInTheDocument();
    });
  });

  it('navigates to previous month', async () => {
    global.fetch = mockFetch([savedPaycheck(1), savedPaycheck(2)]);

    renderView();

    await waitFor(() => screen.getByText('March 2026'));

    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: '←' }));
    });

    expect(screen.getByText('February 2026')).toBeInTheDocument();
  });

  it('navigates to next month', async () => {
    global.fetch = mockFetch([savedPaycheck(1), savedPaycheck(2)]);

    renderView();

    await waitFor(() => screen.getByText('March 2026'));

    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: '→' }));
    });

    expect(screen.getByText('April 2026')).toBeInTheDocument();
  });

  it('wraps from January to December on prev', async () => {
    vi.useRealTimers();
    vi.useFakeTimers({ toFake: ['Date'], now: new Date('2026-01-15') });
    global.fetch = mockFetch([savedPaycheck(1), savedPaycheck(2)]);

    renderView();

    await waitFor(() => screen.getByText('January 2026'));

    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: '←' }));
    });

    expect(screen.getByText('December 2025')).toBeInTheDocument();
  });

  it('renders bills in paycheck columns', async () => {
    const pc1 = {
      ...savedPaycheck(1),
      bills: [
        { paycheck_bill_id: 10, bill_id: 1, name: 'Electric', amount: 150, effective_amount: 150, due_day: 5, category: 'Utilities', variable_amount: false, paid: false, paid_at: null, amount_override: null },
      ],
    };
    global.fetch = mockFetch([pc1, savedPaycheck(2)]);

    renderView();

    await waitFor(() => {
      expect(screen.getByText('Electric')).toBeInTheDocument();
      expect(screen.getByText('$150.00')).toBeInTheDocument();
      expect(screen.getByText('due 5th')).toBeInTheDocument();
    });
  });

  it('shows remaining balance', async () => {
    const pc1 = {
      ...savedPaycheck(1),
      net: 2400,
      bills: [
        { paycheck_bill_id: 10, bill_id: 1, name: 'Electric', amount: 150, effective_amount: 150, due_day: 5, category: 'Utilities', variable_amount: false, paid: false, paid_at: null, amount_override: null },
      ],
      one_time_expenses: [],
      financing: [],
    };
    global.fetch = mockFetch([pc1, savedPaycheck(2)]);

    renderView();

    await waitFor(() => {
      // Remaining = 2400 - 150 = 2250
      expect(screen.getAllByText('$2,250.00')[0]).toBeInTheDocument();
    });
  });

  it('shows empty state when no bills', async () => {
    global.fetch = mockFetch([savedPaycheck(1), savedPaycheck(2)]);

    renderView();

    await waitFor(() => {
      // Two paycheck columns, each with "(none)" for bills
      expect(screen.getAllByText('(none)').length).toBeGreaterThanOrEqual(2);
    });
  });

  it('shows gross and net amounts', async () => {
    global.fetch = mockFetch([savedPaycheck(1), savedPaycheck(2)]);

    renderView();

    await waitFor(() => {
      expect(screen.getAllByText('$3,000.00').length).toBeGreaterThan(0); // gross
      expect(screen.getAllByText('$2,400.00').length).toBeGreaterThan(0); // net
    });
  });
});
