/**
 * (URL sync: sort, debounced search, params, table sort bridge; context guard).
 */
import React from 'react'
import { act, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { FilterProvider, useFilter } from '../filter-provider'

const mockPush = jest.fn()
jest.mock('@/i18n/navigation', () => ({ useRouter: () => ({ push: mockPush }) }))
jest.mock('next/navigation', () => ({
  useSearchParams: () => new URLSearchParams('sort=name:asc'),
}))

function TestConsumer() {
  const { filter } = useFilter()
  return <span>Limit: {filter.limit}</span>
}

function TestConsumerWithActions() {
  const { onSortChange, onSearchChange, updateParam, table } = useFilter()
  return (
    <div>
      <button onClick={() => onSortChange({ name: 'created', dir: 'desc' })}>
        Sort
      </button>
      <button onClick={() => onSearchChange('keyword')}>Search</button>
      <button onClick={() => updateParam('page', '2')}>Update</button>
      <button onClick={() => updateParam('page')}>Delete</button>
      <button
        onClick={() =>
          table.updateSortState([{ id: 'name', desc: true }])
        }
      >
        TableSort
      </button>
      <button
        onClick={() =>
          table.updateSortState((prev) =>
            prev.length ? prev : [{ id: 'created_at', desc: false }]
          )
        }
      >
        TableSortFn
      </button>
    </div>
  )
}

describe('[providers/filter-provider] FilterProvider', () => {
  beforeEach(() => {
    mockPush.mockClear()
    jest.useRealTimers()
  })

  describe('context', () => {
    it('exposes filter from initial prop', () => {
      render(
        <FilterProvider filter={{ limit: 10, offset: 0 }}>
          <TestConsumer />
        </FilterProvider>,
      )
      expect(screen.getByText('Limit: 10')).toBeInTheDocument()
    })

    it('throws when useFilter runs outside provider', () => {
      function BadConsumer() {
        useFilter()
        return null
      }
      expect(() => render(<BadConsumer />)).toThrow(
        'useFilter must be used within a FilterProvider',
      )
    })
  })

  describe('router.push sync', () => {
    it('onSortChange pushes sort query', async () => {
      render(
        <FilterProvider filter={{ limit: 10, offset: 0 }}>
          <TestConsumerWithActions />
        </FilterProvider>,
      )
      await userEvent.click(screen.getByText('Sort'))
      expect(mockPush).toHaveBeenCalledWith(
        expect.stringContaining('sort=created%3Adesc'),
      )
    })

    it('onSearchChange debounces then pushes keyword', async () => {
      jest.useFakeTimers()
      const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime })
      render(
        <FilterProvider filter={{ limit: 10, offset: 0 }}>
          <TestConsumerWithActions />
        </FilterProvider>,
      )
      await user.click(screen.getByText('Search'))
      expect(mockPush).not.toHaveBeenCalled()
      await act(async () => {
        jest.advanceTimersByTime(350)
      })
      expect(mockPush).toHaveBeenCalledWith(
        expect.stringContaining('keyword=keyword'),
      )
      jest.useRealTimers()
    })

    it('updateParam sets key=value', async () => {
      render(
        <FilterProvider filter={{ limit: 10, offset: 0 }}>
          <TestConsumerWithActions />
        </FilterProvider>,
      )
      await userEvent.click(screen.getByText('Update'))
      expect(mockPush).toHaveBeenCalledWith(expect.stringContaining('page=2'))
    })

    it('updateParam without value deletes key', async () => {
      render(
        <FilterProvider filter={{ limit: 10, offset: 0 }}>
          <TestConsumerWithActions />
        </FilterProvider>,
      )
      await userEvent.click(screen.getByText('Delete'))
      expect(mockPush).toHaveBeenCalled()
    })
  })

  describe('table sort bridge', () => {
    it('updateSortState with array calls onSortChange', async () => {
      render(
        <FilterProvider filter={{ limit: 10, offset: 0 }}>
          <TestConsumerWithActions />
        </FilterProvider>,
      )
      await userEvent.click(screen.getByText('TableSort'))
      expect(mockPush).toHaveBeenCalledWith(
        expect.stringContaining('sort=name%3Adesc'),
      )
    })

    it('updateSortState with updater resolves default sort row', async () => {
      render(
        <FilterProvider filter={{ limit: 10, offset: 0 }}>
          <TestConsumerWithActions />
        </FilterProvider>,
      )
      await userEvent.click(screen.getByText('TableSortFn'))
      expect(mockPush).toHaveBeenCalledWith(
        expect.stringContaining('sort=created_at%3Aasc'),
      )
    })

    it('TableSortFn path still pushes', async () => {
      render(
        <FilterProvider filter={{ limit: 10, offset: 0 }}>
          <TestConsumerWithActions />
        </FilterProvider>,
      )
      await userEvent.click(screen.getByText('TableSortFn'))
      expect(mockPush).toHaveBeenCalled()
    })

    it('clear sort when updater returns empty array', async () => {
      function ClearSortConsumer() {
        const { table } = useFilter()
        return (
          <button onClick={() => table.updateSortState(() => [])}>
            ClearSort
          </button>
        )
      }
      render(
        <FilterProvider
          filter={{ limit: 10, offset: 0, sort: { name: 'name', dir: 'asc' } }}
        >
          <ClearSortConsumer />
        </FilterProvider>,
      )
      await userEvent.click(screen.getByText('ClearSort'))
      expect(mockPush).toHaveBeenCalled()
    })
  })

  describe('table.sortState mirror', () => {
    it('reflects filter.sort into TanStack sortState shape', () => {
      function SortConsumer() {
        const { table } = useFilter()
        return (
          <span>
            {table.sortState
              ? `${table.sortState[0]?.id}-${table.sortState[0]?.desc}`
              : 'none'}
          </span>
        )
      }
      render(
        <FilterProvider
          filter={{
            limit: 10,
            offset: 0,
            sort: { name: 'created', dir: 'desc' },
          }}
        >
          <SortConsumer />
        </FilterProvider>,
      )
      expect(screen.getByText('created-true')).toBeInTheDocument()
    })
  })
})
