/**
 * 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, vi, beforeEach } from 'vitest'
import { renderHook, 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', async () => {
    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
    await act(async () => {
      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
    await act(async () => {
      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)', async () => {
    queryClient.setQueryData<ListItemsResponse>(['list', LIST_ID], makeItemsResponse([]))

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

    await act(async () => {
      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', async () => {
    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',
    }

    await act(async () => {
      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
    await act(async () => {
      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)
  })
})
