import React from 'react';
import { render, waitFor } from '@testing-library/react';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import '@testing-library/jest-dom';
import { useGuidedTour } from '@/features/onboarding/hooks/useGuidedTour';
import * as tourScroll from '@/features/onboarding/utils/tourScroll';

vi.mock('@/features/onboarding/hooks/useOnboarding', () => ({
  useOnboarding: () => ({
    dismissedTours: [],
    dismissTour: vi.fn(),
  }),
}));

function TourHarness({ onUpdate }: { onUpdate: (state: ReturnType<typeof useGuidedTour>) => void }) {
  const state = useGuidedTour();
  React.useEffect(() => {
    onUpdate(state);
  }, [onUpdate, state]);
  return null;
}

describe('useGuidedTour', () => {
  beforeEach(() => {
    document.body.innerHTML = '';
    vi.spyOn(tourScroll, 'scrollTargetIntoView').mockResolvedValue(undefined);
  });

  afterEach(() => {
    vi.restoreAllMocks();
    document.body.innerHTML = '';
  });

  it('scrolls to target and recalculates rect after scroll settles', async () => {
    document.body.innerHTML = '<div data-tour="dashboard-stats" style="height: 40px;">Stats</div>';
    const target = document.querySelector('[data-tour="dashboard-stats"]') as HTMLElement;
    const rect = {
      top: 120,
      left: 24,
      bottom: 160,
      right: 300,
      width: 276,
      height: 40,
      x: 24,
      y: 120,
      toJSON: () => ({}),
    } as DOMRect;
    vi.spyOn(target, 'getBoundingClientRect').mockReturnValue(rect);

    let latest: ReturnType<typeof useGuidedTour> | null = null;

    render(
      <MemoryRouter initialEntries={['/dashboard']}>
        <Routes>
          <Route path="/dashboard" element={<TourHarness onUpdate={(state) => { latest = state; }} />} />
        </Routes>
      </MemoryRouter>,
    );

    await waitFor(
      () => {
        expect(tourScroll.scrollTargetIntoView).toHaveBeenCalled();
        expect(latest?.targetRect).toEqual(rect);
      },
      { timeout: 2000 },
    );
  });

  it('does not crash when target is missing and no forward step exists', async () => {
    document.body.innerHTML = '<main></main>';

    let latest: ReturnType<typeof useGuidedTour> | null = null;

    render(
      <MemoryRouter initialEntries={['/settings']}>
        <Routes>
          <Route path="/settings" element={<TourHarness onUpdate={(state) => { latest = state; }} />} />
        </Routes>
      </MemoryRouter>,
    );

    await waitFor(
      () => {
        expect(latest?.isActive).toBe(true);
        expect(latest?.hasMissingTarget).toBe(true);
        expect(latest?.targetRect).toBeNull();
      },
      { timeout: 2000 },
    );
  });

  it('removes scroll and resize listeners on unmount', async () => {
    document.body.innerHTML = '<div data-tour="dashboard-stats">Stats</div>';
    const removeListener = vi.spyOn(window, 'removeEventListener');

    const { unmount } = render(
      <MemoryRouter initialEntries={['/dashboard']}>
        <Routes>
          <Route path="/dashboard" element={<TourHarness onUpdate={() => undefined} />} />
        </Routes>
      </MemoryRouter>,
    );

    await waitFor(() => {
      expect(tourScroll.scrollTargetIntoView).toHaveBeenCalled();
    }, { timeout: 2000 });

    unmount();

    expect(removeListener).toHaveBeenCalledWith('resize', expect.any(Function));
    expect(removeListener).toHaveBeenCalledWith('scroll', expect.any(Function), true);
  });
});
