/**
 * ListDetail — D-07 optimistic update + rollback tests.
 *
 * Tests the React Query optimistic update pattern for:
 *   - Add item: appears immediately before server response
 *   - Check/uncheck: moves between sections immediately
 *   - Delete: removes immediately with no rollback (D-09 delete-wins)
 *   - Rollback: checked state restores if PATCH fails
 *   - Completed items sink to bottom section (D-05)
 *
 * Uses React Query's QueryClient directly (no mocked server).
 */

import { describe, it, expect, beforeEach } from 'vitest';
import { act } from '@testing-library/react';
import { QueryClient } from '@tanstack/react-query';
import type { ListItem, ListItemsResponse } from '../api/listsClient.js';

// ── Helpers ──────────────────────────────────────────────────────────────────

function makeItem(overrides: Partial<ListItem> = {}): ListItem {
  return {
    id: 1,
    listId: 10,
    text: 'bread',
    checked: false,
    rank: 'a0',
    ...overrides,
  };
}

function makeItemsResponse(items: ListItem[]): ListItemsResponse {
  return { items };
}

// ── Tests ──────────────────────────────────────────────────────────────────

describe('ListDetail — D-07 optimistic update + rollback', () => {
  let queryClient: QueryClient;
  const LIST_ID = 10;

  beforeEach(() => {
    queryClient = new QueryClient({
      defaultOptions: { queries: { retry: false } },
    });
  });

  it('checking an item immediately updates the UI before server responds', async () => {
    const item = makeItem({ checked: false });
    queryClient.setQueryData<ListItemsResponse>(['list', LIST_ID], makeItemsResponse([item]));

    // Simulate the onMutate optimistic update (D-07 pattern)
    await act(async () => {
      await queryClient.cancelQueries({ queryKey: ['list', LIST_ID] });
      queryClient.setQueryData<ListItemsResponse>(['list', LIST_ID], (old) => ({
        items: (old?.items ?? []).map((i) => (i.id === item.id ? { ...i, checked: true } : i)),
      }));
    });

    const updated = queryClient.getQueryData<ListItemsResponse>(['list', LIST_ID]);
    expect(updated?.items[0].checked).toBe(true);
  });

  it('unchecking an item immediately updates the UI before server responds', async () => {
    const item = makeItem({ checked: true });
    queryClient.setQueryData<ListItemsResponse>(['list', LIST_ID], makeItemsResponse([item]));

    await act(async () => {
      await queryClient.cancelQueries({ queryKey: ['list', LIST_ID] });
      queryClient.setQueryData<ListItemsResponse>(['list', LIST_ID], (old) => ({
        items: (old?.items ?? []).map((i) => (i.id === item.id ? { ...i, checked: false } : i)),
      }));
    });

    const updated = queryClient.getQueryData<ListItemsResponse>(['list', LIST_ID]);
    expect(updated?.items[0].checked).toBe(false);
  });

  it('rolls back the checked state if the server PATCH returns an error', () => {
    const item = makeItem({ checked: false });
    queryClient.setQueryData<ListItemsResponse>(['list', LIST_ID], makeItemsResponse([item]));

    // Step 1: capture previous state (as onMutate would)
    const previous = queryClient.getQueryData<ListItemsResponse>(['list', LIST_ID]);

    // Step 2: apply optimistic update
    act(() => {
      queryClient.setQueryData<ListItemsResponse>(['list', LIST_ID], (old) => ({
        items: (old?.items ?? []).map((i) => (i.id === item.id ? { ...i, checked: true } : i)),
      }));
    });

    // Verify it was applied
    const after = queryClient.getQueryData<ListItemsResponse>(['list', LIST_ID]);
    expect(after?.items[0].checked).toBe(true);

    // Step 3: simulate onError rollback
    act(() => {
      if (previous) {
        queryClient.setQueryData(['list', LIST_ID], previous);
      }
    });

    // Should be rolled back
    const rolledBack = queryClient.getQueryData<ListItemsResponse>(['list', LIST_ID]);
    expect(rolledBack?.items[0].checked).toBe(false);
  });

  it('adding an item shows it in the list immediately (optimistic insert)', () => {
    queryClient.setQueryData<ListItemsResponse>(['list', LIST_ID], makeItemsResponse([]));

    const optimisticItem: ListItem = {
      id: -Date.now(),
      listId: LIST_ID,
      text: 'milk',
      checked: false,
      rank: 'a0',
    };

    act(() => {
      queryClient.setQueryData<ListItemsResponse>(['list', LIST_ID], (old) => ({
        items: [...(old?.items ?? []), optimisticItem],
      }));
    });

    const data = queryClient.getQueryData<ListItemsResponse>(['list', LIST_ID]);
    expect(data?.items).toHaveLength(1);
    expect(data?.items[0].text).toBe('milk');
    // Optimistic item has negative id
    expect(data?.items[0].id).toBeLessThan(0);
  });

  it('removes the optimistically-added item if the server POST returns an error', () => {
    const existingItem = makeItem({ id: 1 });
    queryClient.setQueryData<ListItemsResponse>(
      ['list', LIST_ID],
      makeItemsResponse([existingItem]),
    );

    // Capture previous before optimistic add
    const previous = queryClient.getQueryData<ListItemsResponse>(['list', LIST_ID]);

    // Apply optimistic add
    const optimisticItem: ListItem = {
      id: -999,
      listId: LIST_ID,
      text: 'optimistic',
      checked: false,
      rank: 'a1',
    };

    act(() => {
      queryClient.setQueryData<ListItemsResponse>(['list', LIST_ID], (old) => ({
        items: [...(old?.items ?? []), optimisticItem],
      }));
    });

    expect(queryClient.getQueryData<ListItemsResponse>(['list', LIST_ID])?.items).toHaveLength(2);

    // Simulate rollback on error
    act(() => {
      if (previous) queryClient.setQueryData(['list', LIST_ID], previous);
    });

    const rolledBack = queryClient.getQueryData<ListItemsResponse>(['list', LIST_ID]);
    expect(rolledBack?.items).toHaveLength(1);
    expect(rolledBack?.items[0].id).toBe(1); // original item
  });

  it('completed items sink to the "completed" section at the bottom (D-05)', () => {
    const activeItem = makeItem({ id: 1, checked: false, rank: 'a0', text: 'active' });
    const completedItem = makeItem({ id: 2, checked: true, rank: 'a1', text: 'done' });
    const items = [activeItem, completedItem];

    // The ListDetail splits items into active and completed sections
    const activeItems = items
      .filter((i) => !i.checked)
      .sort((a, b) => (a.rank < b.rank ? -1 : a.rank > b.rank ? 1 : 0));
    const completedItems = items.filter((i) => i.checked);

    expect(activeItems).toHaveLength(1);
    expect(activeItems[0].text).toBe('active');
    expect(completedItems).toHaveLength(1);
    expect(completedItems[0].text).toBe('done');
    // Active comes before completed by data structure (rendered above completed section)
  });

  it('delete removes item immediately with NO rollback (delete-wins D-09)', async () => {
    const item1 = makeItem({ id: 1, text: 'keep', rank: 'a0' });
    const item2 = makeItem({ id: 2, text: 'delete-me', rank: 'a1' });
    queryClient.setQueryData<ListItemsResponse>(
      ['list', LIST_ID],
      makeItemsResponse([item1, item2]),
    );

    // onMutate for delete: remove immediately, no previous state saved (D-09)
    await act(async () => {
      await queryClient.cancelQueries({ queryKey: ['list', LIST_ID] });
      queryClient.setQueryData<ListItemsResponse>(['list', LIST_ID], (old) => ({
        items: (old?.items ?? []).filter((i) => i.id !== 2),
      }));
    });

    const data = queryClient.getQueryData<ListItemsResponse>(['list', LIST_ID]);
    expect(data?.items).toHaveLength(1);
    expect(data?.items[0].id).toBe(1);
    // item2 is gone, no rollback mechanism exists (delete-wins)
  });
});
