import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import SliderPage from './slider-page';

const mockMutateAsync = vi.fn();
const mockGetMarriageProfile = vi.fn();
const mockSetQueryData = vi.fn();
const mockReplace = vi.fn();
const mockRemoveItem = vi.spyOn(Storage.prototype, 'removeItem');
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

vi.mock('@/hooks/marriage/use-profile-basic', () => ({
  useUpdateMarriageProfileBasicMutation: () => ({
    mutateAsync: mockMutateAsync,
    isPending: false,
  }),
}));

vi.mock('@/hooks/marriage/use-profile-main', () => ({
  getMarriageProfile: (...args: any[]) => mockGetMarriageProfile(...args),
  useMarriageProfileQuery: () => ({ data: undefined, refetch: vi.fn() }),
}));

vi.mock('@/hooks/marriage/query-keys', () => ({
  marriageQueryKeys: {
    profile: () => ['marriage', 'profile'],
    config: () => ['marriage', 'config'],
  },
}));

vi.mock('next/navigation', () => ({
  useRouter: () => ({
    replace: mockReplace,
    prefetch: vi.fn(),
    back: vi.fn(),
  }),
}));

vi.mock('@/translations/provider', () => ({
  useI18n: () => ({
    locale: 'en',
    dictionary: {
      "Something went wrong. Please check your internet connection and try again.": "Something went wrong. Please check your internet connection and try again.",
      "Accept & Continue": "Accept & Continue",
    },
  }),
}));

vi.mock('@/translations/config', () => ({
  localizePath: (path: string, locale: string) => path,
}));

describe('SliderPage', () => {
  let queryClient: QueryClient;

  beforeEach(() => {
    vi.resetAllMocks();
    queryClient = new QueryClient();
    vi.spyOn(queryClient, 'setQueryData');
  });

  afterEach(() => {
    vi.useRealTimers();
    cleanup();
  });

  const navigateToFinalSlide = async () => {
    render(
      <QueryClientProvider client={queryClient}>
        <SliderPage />
      </QueryClientProvider>
    );
    const acceptBtn = screen.getByRole('button', { name: /accept/i });
    if (!acceptBtn.hasAttribute('disabled')) {
      fireEvent.click(acceptBtn);
    }
    const dots = screen.getAllByRole('button', { name: /go to slide/i });
    fireEvent.click(dots[4]); // Go directly to slide 5
    return await screen.findByText('Finish');
  };

  it('a. Success: PATCH ok, GET ok -> replace', async () => {
    const finishBtn = await navigateToFinalSlide();
    const initialRemoveCount = mockRemoveItem.mock.calls.filter(call => call[0] === 'marriage-slider-answers').length;
    
    let resolveGet: any;
    
    mockMutateAsync.mockResolvedValueOnce({
      status: 'pending_info',
      gender: 'female',
      is_registering_for_self: true
    });
    
    mockGetMarriageProfile.mockReturnValueOnce(new Promise(resolve => {
      resolveGet = resolve;
    }));

    fireEvent.click(finishBtn);

    // Wait for PATCH to resolve and GET to be called
    await waitFor(() => {
      expect(mockMutateAsync).toHaveBeenCalled();
      expect(mockGetMarriageProfile).toHaveBeenCalled();
    });

    // Assert navigation and localStorage clear did not happen before GET resolves
    expect(mockReplace).not.toHaveBeenCalled();
    const currentRemoveCount = mockRemoveItem.mock.calls.filter(call => call[0] === 'marriage-slider-answers').length;
    expect(currentRemoveCount).toBe(initialRemoveCount);

    // Resolve GET
    resolveGet({
      status: 'pending_info',
      gender: 'female',
      is_registering_for_self: true
    });

    await waitFor(() => {
      expect(queryClient.setQueryData).toHaveBeenCalled();
      const finalRemoveCount = mockRemoveItem.mock.calls.filter(call => call[0] === 'marriage-slider-answers').length;
      expect(finalRemoveCount).toBe(initialRemoveCount + 1);
      expect(mockReplace).toHaveBeenCalledWith('/questions-list');
    });
  });

  it('b. PATCH failure: no nav, Error UI, Retry works', async () => {
    const finishBtn = await navigateToFinalSlide();
    
    const initialRemoveCount = mockRemoveItem.mock.calls.filter(call => call[0] === 'marriage-slider-answers').length;

    mockMutateAsync.mockRejectedValueOnce(new Error('Network Error'));
    
    fireEvent.click(finishBtn);

    await waitFor(() => {
      expect(screen.getByRole('alert')).toHaveTextContent('Something went wrong. Please check your internet connection and try again.');
    });
    expect(mockReplace).not.toHaveBeenCalled();
    
    const currentRemoveCount = mockRemoveItem.mock.calls.filter(call => call[0] === 'marriage-slider-answers').length;
    expect(currentRemoveCount).toBe(initialRemoveCount);

    // Retry succeeds
    mockMutateAsync.mockResolvedValueOnce({
      status: 'pending_info',
      gender: 'female',
      is_registering_for_self: true
    });
    mockGetMarriageProfile.mockResolvedValueOnce({
      status: 'pending_info',
      gender: 'female',
      is_registering_for_self: true
    });

    const retryBtn = screen.getByText('Finish');
    fireEvent.click(retryBtn);

    await waitFor(() => {
      expect(mockReplace).toHaveBeenCalledWith('/questions-list');
    });
    
    const finalRemoveCount = mockRemoveItem.mock.calls.filter(call => call[0] === 'marriage-slider-answers').length;
    expect(finalRemoveCount).toBe(initialRemoveCount + 1);
  });

  it('c. Slow request: Promise pending -> no redirect', async () => {
    const finishBtn = await navigateToFinalSlide();
    vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'Date'] });
    
    let resolvePatch: any;
    mockMutateAsync.mockReturnValueOnce(new Promise(resolve => {
      resolvePatch = resolve;
    }));
    mockGetMarriageProfile.mockResolvedValueOnce({
      status: 'pending_info',
      gender: 'female',
      is_registering_for_self: true
    });

    fireEvent.click(finishBtn);

    await vi.advanceTimersByTimeAsync(6000);

    expect(mockReplace).not.toHaveBeenCalled();
    
    resolvePatch({
      status: 'pending_info',
      gender: 'female',
      is_registering_for_self: true
    });
    
    vi.useRealTimers();

    await waitFor(() => {
      expect(mockReplace).toHaveBeenCalledWith('/questions-list');
    });
  });

  it('d. GET with completed profile basics routes even if status still says onboarding', async () => {
    const finishBtn = await navigateToFinalSlide();
    
    mockMutateAsync.mockResolvedValueOnce({
      status: 'pending_info',
      gender: 'female',
      is_registering_for_self: true
    });
    
    mockGetMarriageProfile.mockResolvedValueOnce({
      status: 'pending_onboarding',
      gender: 'female',
      is_registering_for_self: true
    });

    fireEvent.click(finishBtn);

    await waitFor(() => {
      expect(mockReplace).toHaveBeenCalledWith('/questions-list');
    });
  });

  it('e. Double click: only 1 request', async () => {
    const finishBtn = await navigateToFinalSlide();
    
    let resolvePatch: any;
    mockMutateAsync.mockReturnValueOnce(new Promise(resolve => {
      resolvePatch = resolve;
    }));
    mockGetMarriageProfile.mockResolvedValueOnce({
      status: 'pending_info',
      gender: 'female',
      is_registering_for_self: true
    });

    fireEvent.click(finishBtn);
    fireEvent.click(finishBtn);
    fireEvent.click(finishBtn);

    expect(mockMutateAsync).toHaveBeenCalledTimes(1);
    
    resolvePatch({ status: 'pending_info', gender: 'female', is_registering_for_self: true });
    
    await waitFor(() => {
      expect(mockReplace).toHaveBeenCalled();
    });
  });

  it('f. GET with an advanced status routes to the matching page', async () => {
    const finishBtn = await navigateToFinalSlide();
    
    mockMutateAsync.mockResolvedValueOnce({
      status: 'pending_info',
      gender: 'female',
      is_registering_for_self: true
    });
    
    mockGetMarriageProfile.mockResolvedValueOnce({
      status: 'waiting',
      gender: 'female',
      is_registering_for_self: true
    });

    fireEvent.click(finishBtn);

    await waitFor(() => {
      expect(mockReplace).toHaveBeenCalledWith('/finding-match');
    });
  });

  it('g. Slide 0 navigation guard: cannot swipe or click dots when rules not read', async () => {
    // Render slider page with rules container that has not scrolled to bottom
    // We mock element scroll properties before render
    const origScrollHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'scrollHeight');
    const origClientHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'clientHeight');
    const origScrollTop = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'scrollTop');

    Object.defineProperty(HTMLElement.prototype, 'scrollHeight', { configurable: true, value: 2000 });
    Object.defineProperty(HTMLElement.prototype, 'clientHeight', { configurable: true, value: 500 });
    Object.defineProperty(HTMLElement.prototype, 'scrollTop', { configurable: true, value: 0 });

    try {
      render(
        <QueryClientProvider client={queryClient}>
          <SliderPage />
        </QueryClientProvider>
      );

      const acceptBtn = screen.getByRole('button', { name: /accept/i });
      expect(acceptBtn).toBeDisabled();

      // Attempting to click dot 2 (index 1) should be disabled and not navigate
      const dots = screen.getAllByRole('button', { name: /go to slide/i });
      expect(dots[1]).toBeDisabled();
      fireEvent.click(dots[1]);

      // We should still be on Slide 1 (terms)
      expect(screen.getByText('Terms & Conditions')).toBeInTheDocument();

      // Swipe touch gesture forward (touchStart at 200, touchEnd at 50 -> deltaX = 150 > 40)
      const main = screen.getByRole('main');
      fireEvent.touchStart(main, { touches: [{ clientX: 200 }] });
      fireEvent.touchEnd(main, { changedTouches: [{ clientX: 50 }] });

      // Still on Slide 1 (terms)
      expect(screen.getByText('Terms & Conditions')).toBeInTheDocument();
    } finally {
      if (origScrollHeight) Object.defineProperty(HTMLElement.prototype, 'scrollHeight', origScrollHeight);
      if (origClientHeight) Object.defineProperty(HTMLElement.prototype, 'clientHeight', origClientHeight);
      if (origScrollTop) Object.defineProperty(HTMLElement.prototype, 'scrollTop', origScrollTop);
    }
  });
});
