import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { useOfflineQueue, MAX_ENTRIES, enqueueOfflineRequest } from './offlineQueue';

// Reset the store + localStorage between tests
const resetStore = () => {
  localStorage.clear();
  useOfflineQueue.setState({ items: [], length: 0 });
  useOfflineQueue.getState().hydrate();
};

describe('offlineQueue', () => {
  beforeEach(() => {
    resetStore();
    vi.restoreAllMocks();
  });

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

  it('push appends to the tail of the queue and updates length', () => {
    const { push, length } = useOfflineQueue.getState();
    push({ method: 'POST', url: '/api/x', data: { a: 1 }, headers: {} });
    expect(length).toBe(0); // length is a getter, not auto-updated; use state directly
    expect(useOfflineQueue.getState().items.length).toBe(1);
    push({ method: 'PUT', url: '/api/y', data: { a: 2 }, headers: {} });
    expect(useOfflineQueue.getState().items.length).toBe(2);
    expect(useOfflineQueue.getState().items[0].method).toBe('POST');
    expect(useOfflineQueue.getState().items[1].method).toBe('PUT');
  });

  it('drops the oldest entry when MAX_ENTRIES is exceeded', () => {
    const { push } = useOfflineQueue.getState();
    for (let i = 0; i < MAX_ENTRIES + 5; i++) {
      push({ method: 'POST', url: `/api/x/${i}`, data: { i }, headers: {} });
    }
    const items = useOfflineQueue.getState().items;
    expect(items.length).toBe(MAX_ENTRIES);
    // The oldest should have been dropped — first remaining is i=5, not i=0
    expect(items[0].url).toBe('/api/x/5');
    expect(items[items.length - 1].url).toBe(`/api/x/${MAX_ENTRIES + 4}`);
  });

  it('persists queue to localStorage', () => {
    useOfflineQueue.getState().push({ method: 'POST', url: '/api/x', data: { a: 1 }, headers: {} });
    const raw = localStorage.getItem('offline-queue');
    expect(raw).toBeTruthy();
    const parsed = JSON.parse(raw!);
    expect(Array.isArray(parsed)).toBe(true);
    expect(parsed.length).toBe(1);
    expect(parsed[0].url).toBe('/api/x');
  });

  it('rehydrates from localStorage on hydrate()', () => {
    localStorage.setItem('offline-queue', JSON.stringify([
      { uid: 'a', method: 'POST', url: '/api/x', data: {}, headers: {}, createdAt: 1, attempts: 0 },
    ]));
    useOfflineQueue.getState().hydrate();
    expect(useOfflineQueue.getState().items.length).toBe(1);
    expect(useOfflineQueue.getState().items[0].uid).toBe('a');
  });

  it('clear() empties the queue and storage', () => {
    const { push, clear } = useOfflineQueue.getState();
    push({ method: 'POST', url: '/api/x', data: {}, headers: {} });
    push({ method: 'POST', url: '/api/y', data: {}, headers: {} });
    expect(useOfflineQueue.getState().items.length).toBe(2);
    clear();
    expect(useOfflineQueue.getState().items.length).toBe(0);
    expect(localStorage.getItem('offline-queue')).toBe('[]');
  });

  it('flush() with no items returns 0', async () => {
    const flushed = await useOfflineQueue.getState().flush();
    expect(flushed).toBe(0);
  });

  it('flush() drops 4xx entries and keeps network/5xx entries for next flush', async () => {
    const axios = (await import('axios')).default;
    const postSpy = vi.spyOn(axios, 'create').mockReturnValue({
      request: vi.fn().mockImplementation((config) => {
        if (config.url.includes('success')) {
          return Promise.resolve({ status: 200, data: { ok: true } });
        }
        if (config.url.includes('client-error')) {
          return Promise.reject({ response: { status: 400, data: { error: 'bad' } } });
        }
        // network error
        return Promise.reject({ message: 'Network Error' });
      }),
    } as any);

    useOfflineQueue.getState().push({ method: 'POST', url: '/api/success', data: {}, headers: {} });
    useOfflineQueue.getState().push({ method: 'POST', url: '/api/client-error', data: {}, headers: {} });
    useOfflineQueue.getState().push({ method: 'POST', url: '/api/network-err', data: {}, headers: {} });

    const flushed = await useOfflineQueue.getState().flush();
    expect(flushed).toBe(1); // only the success path

    const remaining = useOfflineQueue.getState().items;
    expect(remaining.length).toBe(1);
    expect(remaining[0].url).toBe('/api/network-err');
    expect(remaining[0].attempts).toBe(1);
    expect(remaining[0].lastError).toBe('Network Error');
    postSpy.mockRestore();
  });

  it('enqueueOfflineRequest() returns 202 + queued flag and pushes to queue', () => {
    const fake = enqueueOfflineRequest({
      method: 'post',
      url: '/api/finance/expenses',
      data: { amount: 100 },
      headers: { 'Content-Type': 'application/json' },
    } as any);
    expect(fake.status).toBe(202);
    expect(fake.data.queued).toBe(true);
    expect(fake.data.uid).toBeTruthy();
    expect(useOfflineQueue.getState().items.length).toBe(1);
    expect(useOfflineQueue.getState().items[0].url).toBe('/api/finance/expenses');
  });
});
