import { describe, expect, test } from 'bun:test';
import { renderHook, act } from '@testing-library/react';
import { useDataControl } from './useDataControl';

type TestItem = {
  id: number;
  name: string;
  value: number;
  createdAt: Date;
};

const mockData: TestItem[] = [
  { id: 1, name: 'Alpha', value: 100, createdAt: new Date('2024-01-01') },
  { id: 2, name: 'Beta', value: 200, createdAt: new Date('2024-02-01') },
  { id: 3, name: 'Gamma', value: 150, createdAt: new Date('2024-03-01') },
  { id: 4, name: 'Delta', value: 50, createdAt: new Date('2024-04-01') },
  { id: 5, name: 'Epsilon', value: 300, createdAt: new Date('2024-05-01') },
  { id: 6, name: 'Zeta', value: 250, createdAt: new Date('2024-06-01') },
  { id: 7, name: 'Eta', value: 175, createdAt: new Date('2024-07-01') },
];

describe('useDataControl', () => {
  describe('initialization', () => {
    test('returns raw data unchanged', () => {
      const { result } = renderHook(() => useDataControl(mockData));
      expect(result.current.rawData).toBe(mockData);
    });

    test('handles null data', () => {
      const { result } = renderHook(() => useDataControl(null));
      expect(result.current.rawData).toBeNull();
      expect(result.current.data).toBeUndefined();
    });

    test('handles undefined data', () => {
      const { result } = renderHook(() => useDataControl(undefined));
      expect(result.current.rawData).toBeUndefined();
      expect(result.current.data).toBeUndefined();
    });

    test('handles empty array', () => {
      const { result } = renderHook(() => useDataControl<TestItem>([]));
      expect(result.current.rawData).toEqual([]);
      expect(result.current.data).toEqual([]);
    });
  });

  describe('pagination', () => {
    test('defaults to page size of 5', () => {
      const { result } = renderHook(() => useDataControl(mockData));
      expect(result.current.pageSize).toBe(5);
      expect(result.current.data?.length).toBe(5);
    });

    test('setPageSize changes page size', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.setPageSize(3);
      });

      expect(result.current.pageSize).toBe(3);
      expect(result.current.data?.length).toBe(3);
    });

    test('calculates correct page count', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      expect(result.current.pageCount).toBe(2); // 7 items / 5 per page = 2 pages

      act(() => {
        result.current.setPageSize(3);
      });

      expect(result.current.pageCount).toBe(3); // 7 items / 3 per page = 3 pages
    });

    test('changePage navigates to different pages', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      expect(result.current.currentPage).toBe(1);
      expect(result.current.data?.[0]?.id).toBe(1);

      act(() => {
        result.current.changePage(2);
      });

      expect(result.current.currentPage).toBe(2);
      expect(result.current.data?.[0]?.id).toBe(6);
    });

    test('changePage clamps to valid range', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.changePage(100);
      });
      expect(result.current.currentPage).toBe(2); // max page

      act(() => {
        result.current.changePage(-5);
      });
      expect(result.current.currentPage).toBe(1); // min page
    });

    test('resetPageSize restores default', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.setPageSize(10);
      });
      expect(result.current.pageSize).toBe(10);

      act(() => {
        result.current.resetPageSize();
      });
      expect(result.current.pageSize).toBe(5);
    });
  });

  describe('sorting', () => {
    test('sortBy sets ascending sort initially', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.sortBy('name');
      });

      expect(result.current.sortedBy).toBe('name-asc');
      expect(result.current.sortKey).toBe('name');
      expect(result.current.sortDirection).toBe('asc');
      expect(result.current.data?.[0]?.name).toBe('Alpha');
    });

    test('sortBy toggles to descending on second call', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.sortBy('name');
      });
      act(() => {
        result.current.sortBy('name');
      });

      expect(result.current.sortedBy).toBe('name-desc');
      expect(result.current.sortDirection).toBe('desc');
      expect(result.current.data?.[0]?.name).toBe('Zeta');
    });

    test('sortBy toggles back to ascending on third call', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.sortBy('name');
      });
      act(() => {
        result.current.sortBy('name');
      });
      act(() => {
        result.current.sortBy('name');
      });

      expect(result.current.sortedBy).toBe('name-asc');
    });

    test('sorts numbers correctly', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.sortBy('value');
      });

      expect(result.current.data?.[0]?.value).toBe(50); // Delta
      expect(result.current.data?.[4]?.value).toBe(200); // Beta (5th in page)
    });

    test('sorts dates correctly', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.sortBy('createdAt');
      });

      expect(result.current.data?.[0]?.id).toBe(1); // January
    });

    test('setSortedBy allows direct sort control', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.setSortedBy('value-desc');
      });

      expect(result.current.sortedBy).toBe('value-desc');
      expect(result.current.data?.[0]?.value).toBe(300); // Epsilon
    });

    test('sorting resets to page 1', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.changePage(2);
      });
      expect(result.current.currentPage).toBe(2);

      act(() => {
        result.current.sortBy('name');
      });
      expect(result.current.currentPage).toBe(1);
    });

    test('sortBy with null/undefined key does nothing', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.sortBy(null);
      });
      expect(result.current.sortedBy).toBeNull();

      act(() => {
        result.current.sortBy(undefined);
      });
      expect(result.current.sortedBy).toBeNull();
    });
  });

  describe('searching', () => {
    test('setSearchQuery filters data', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.setSearchKeys(['name']);
        result.current.setSearchQuery('Alpha');
      });

      expect(result.current.data?.length).toBe(1);
      expect(result.current.data?.[0]?.name).toBe('Alpha');
    });

    test('search is case-insensitive', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.setSearchKeys(['name']);
        result.current.setSearchQuery('BETA');
      });

      expect(result.current.data?.length).toBe(1);
      expect(result.current.data?.[0]?.name).toBe('Beta');
    });

    test('search strips diacritics', () => {
      const dataWithAccents = [
        { id: 1, name: 'Café' },
        { id: 2, name: 'Naïve' },
        { id: 3, name: 'Resume' },
      ];

      const { result } = renderHook(() => useDataControl(dataWithAccents));

      act(() => {
        result.current.setSearchKeys(['name']);
        result.current.setSearchQuery('cafe');
      });

      expect(result.current.data?.length).toBe(1);
      expect(result.current.data?.[0]?.name).toBe('Café');
    });

    test('search works across multiple keys', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.setSearchKeys(['name', 'id']);
        result.current.setSearchQuery('5');
      });

      // Should find id:5 (Epsilon) and value with 5 in name if any
      expect(result.current.data?.some((item) => item.id === 5)).toBe(true);
    });

    test('search on numbers works', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.setSearchKeys(['value']);
        result.current.setSearchQuery('100');
      });

      expect(result.current.data?.length).toBe(1);
      expect(result.current.data?.[0]?.value).toBe(100);
    });

    test('empty search returns all data', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.setSearchKeys(['name']);
        result.current.setSearchQuery('Alpha');
      });
      expect(result.current.data?.length).toBe(1);

      act(() => {
        result.current.setSearchQuery('');
      });
      expect(result.current.rawData?.length).toBe(7);
    });

    test('search resets to page 1', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.changePage(2);
      });
      expect(result.current.currentPage).toBe(2);

      act(() => {
        result.current.setSearchKeys(['name']);
        result.current.setSearchQuery('a');
      });
      expect(result.current.currentPage).toBe(1);
    });

    test('no results when search has no matches', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.setSearchKeys(['name']);
        result.current.setSearchQuery('xyz123');
      });

      expect(result.current.data?.length).toBe(0);
    });

    test('search without searchKeys does not filter', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.setSearchQuery('Alpha');
      });

      // No searchKeys set, so no filtering happens
      expect(result.current.data?.length).toBe(5);
    });
  });

  describe('combined operations', () => {
    test('search + sort work together', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.setSearchKeys(['name']);
        result.current.setSearchQuery('a'); // Alpha, Beta, Gamma, Delta, Zeta, Eta
        result.current.sortBy('value');
      });

      // Filtered by 'a' in name, sorted by value ascending
      const filtered = result.current.data;
      expect(filtered?.length).toBeGreaterThan(0);

      // Check sorted order within filtered results
      for (let i = 1; i < (filtered?.length || 0); i++) {
        expect(filtered![i]!.value).toBeGreaterThanOrEqual(filtered![i - 1]!.value);
      }
    });

    test('search + sort + pagination work together', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.setPageSize(2);
        result.current.setSearchKeys(['name']);
        result.current.setSearchQuery('a');
        result.current.sortBy('name');
      });

      // Should have multiple pages of results containing 'a'
      expect(result.current.pageCount).toBeGreaterThan(1);
      expect(result.current.data?.length).toBe(2);
    });
  });

  describe('custom sort', () => {
    test('setCustomSort allows custom sorting logic', () => {
      const { result } = renderHook(() => useDataControl(mockData));

      act(() => {
        result.current.setCustomSort(() => (a: TestItem, b: TestItem) => {
          // Sort by name length
          return a.name.length - b.name.length;
        });
        result.current.sortBy('name'); // Trigger a sort
      });

      // Eta (3), Beta (4), Zeta (4), Alpha (5), Gamma (5), Delta (5), Epsilon (7)
      expect(result.current.data?.[0]?.name).toBe('Eta');
    });
  });

  describe('data reactivity', () => {
    test('updates when raw data changes', () => {
      const { result, rerender } = renderHook(({ data }) => useDataControl(data), {
        initialProps: { data: mockData },
      });

      expect(result.current.rawData?.length).toBe(7);

      const newData = mockData.slice(0, 3);
      rerender({ data: newData });

      expect(result.current.rawData?.length).toBe(3);
    });

    test('clamps current page when data shrinks', () => {
      const { result, rerender } = renderHook(({ data }) => useDataControl(data), {
        initialProps: { data: mockData },
      });

      // Set page size first, then navigate in separate act
      act(() => {
        result.current.setPageSize(3);
      });

      act(() => {
        result.current.changePage(3); // Go to page 3 (7 items / 3 per page = 3 pages)
      });
      expect(result.current.currentPage).toBe(3);

      // Shrink data to only 2 items
      rerender({ data: mockData.slice(0, 2) });

      // Page should be clamped to 1 (2 items / 3 per page = 1 page)
      expect(result.current.currentPage).toBe(1);
    });
  });
});
