import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, act, cleanup } from '@testing-library/react';
import {
  InteractiveRegionsMap,
  MapJourney,
  JourneyControls,
  JourneyStepList,
  JourneyCallout,
  useJourneyContext,
} from '../src/components/interactive-regions-map/index.js';

const WAYPOINTS = [
  { id: 'a', label: 'London', x: 468, y: 195, transport: 'flight' as const },
  {
    id: 'b',
    label: 'Dubai',
    x: 610,
    y: 330,
    transport: 'ferry' as const,
    duration: '7h',
  },
  { id: 'c', label: 'Sydney', x: 870, y: 510, transport: 'train' as const },
];

function JourneyWrapper({
  onStepChange,
  activeIndex,
  defaultActiveIndex,
}: {
  onStepChange?: (i: number) => void;
  activeIndex?: number;
  defaultActiveIndex?: number;
}) {
  return (
    <InteractiveRegionsMap title='Journey Map' regions={[]}>
      <MapJourney
        waypoints={WAYPOINTS}
        onStepChange={onStepChange}
        activeIndex={activeIndex}
        defaultActiveIndex={defaultActiveIndex}
        autoFocus={false}
      >
        <JourneyControls />
        <JourneyStepList />
      </MapJourney>
    </InteractiveRegionsMap>
  );
}

describe('JourneyControls — step navigation', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

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

  it('renders step counter starting at 1', () => {
    render(<JourneyWrapper />);
    expect(screen.getByText('1 / 3')).toBeDefined();
  });

  it('Previous button is disabled at the first step', () => {
    render(<JourneyWrapper />);
    const prev = screen.getByLabelText('Previous waypoint');
    expect((prev as HTMLButtonElement).disabled).toBe(true);
  });

  it('Next button is enabled at the first step', () => {
    render(<JourneyWrapper />);
    const next = screen.getByLabelText('Next waypoint');
    expect((next as HTMLButtonElement).disabled).toBe(false);
  });

  it('clicking Next advances the counter', async () => {
    render(<JourneyWrapper />);
    const next = screen.getByLabelText('Next waypoint');
    await act(async () => {
      next.click();
      vi.runAllTimers();
    });
    expect(screen.getByText('2 / 3')).toBeDefined();
  });

  it('clicking Next twice reaches the last step', async () => {
    render(<JourneyWrapper />);
    const next = screen.getByLabelText('Next waypoint');
    await act(async () => {
      next.click();
      vi.runAllTimers();
    });
    await act(async () => {
      next.click();
      vi.runAllTimers();
    });
    expect(screen.getByText('3 / 3')).toBeDefined();
  });

  it('Next button is disabled at the last step', async () => {
    render(<JourneyWrapper />);
    const next = screen.getByLabelText('Next waypoint');
    await act(async () => {
      next.click();
      vi.runAllTimers();
    });
    await act(async () => {
      next.click();
      vi.runAllTimers();
    });
    expect((next as HTMLButtonElement).disabled).toBe(true);
  });

  it('Previous becomes enabled after advancing', async () => {
    render(<JourneyWrapper />);
    const next = screen.getByLabelText('Next waypoint');
    await act(async () => {
      next.click();
      vi.runAllTimers();
    });
    const prev = screen.getByLabelText('Previous waypoint');
    expect((prev as HTMLButtonElement).disabled).toBe(false);
  });

  it('Previous navigates back', async () => {
    render(<JourneyWrapper />);
    const next = screen.getByLabelText('Next waypoint');
    await act(async () => {
      next.click();
      vi.runAllTimers();
    });
    const prev = screen.getByLabelText('Previous waypoint');
    await act(async () => {
      prev.click();
      vi.runAllTimers();
    });
    expect(screen.getByText('1 / 3')).toBeDefined();
  });

  it('calls onStepChange with correct index and waypoint', async () => {
    const onStepChange = vi.fn();
    render(<JourneyWrapper onStepChange={onStepChange} />);
    const next = screen.getByLabelText('Next waypoint');
    await act(async () => {
      next.click();
      vi.runAllTimers();
    });
    expect(onStepChange).toHaveBeenCalledWith(1, WAYPOINTS[1]);
  });

  it('respects defaultActiveIndex', () => {
    render(<JourneyWrapper defaultActiveIndex={1} />);
    expect(screen.getByText('2 / 3')).toBeDefined();
  });
});

describe('JourneyControls — controlled mode', () => {
  it('reflects the controlled activeIndex', () => {
    render(<JourneyWrapper activeIndex={2} />);
    expect(screen.getByText('3 / 3')).toBeDefined();
  });

  it('Next is disabled when controlled at last index', () => {
    render(<JourneyWrapper activeIndex={2} />);
    const next = screen.getByLabelText('Next waypoint');
    expect((next as HTMLButtonElement).disabled).toBe(true);
  });

  it('Previous is disabled when controlled at first index', () => {
    render(<JourneyWrapper activeIndex={0} />);
    const prev = screen.getByLabelText('Previous waypoint');
    expect((prev as HTMLButtonElement).disabled).toBe(true);
  });
});

describe('JourneyStepList', () => {
  it('renders all waypoint labels', () => {
    render(<JourneyWrapper />);
    expect(screen.getByText('London')).toBeDefined();
    expect(screen.getByText('Dubai')).toBeDefined();
    expect(screen.getByText('Sydney')).toBeDefined();
  });

  it('clicking a step item navigates to it', async () => {
    vi.useFakeTimers();
    render(<JourneyWrapper />);
    const dubaiBtn = screen.getByText('Dubai').closest('button');
    if (dubaiBtn) {
      await act(async () => {
        dubaiBtn.click();
        vi.runAllTimers();
      });
      expect(screen.getByText('2 / 3')).toBeDefined();
    }
    vi.useRealTimers();
  });

  it('shows duration for waypoints that have it', () => {
    render(<JourneyWrapper />);
    expect(screen.getByText('7h')).toBeDefined();
  });

  it('active step has different styling (aria or class based)', async () => {
    vi.useFakeTimers();
    render(<JourneyWrapper defaultActiveIndex={1} />);
    // The second step button should have sky-themed classes
    const dubaiBtn = screen.getByText('Dubai').closest('button');
    expect(dubaiBtn?.className).toContain('sky');
    vi.useRealTimers();
  });
});

describe('useJourneyContext — error outside provider', () => {
  it('throws when used outside Journey provider', () => {
    function BadComponent() {
      useJourneyContext();
      return null;
    }
    expect(() => render(<BadComponent />)).toThrow(
      'InteractiveRegionsMap.Journey sub-components must be used inside'
    );
  });
});

describe('JourneyControls — showCounter prop', () => {
  it('hides counter when showCounter=false', () => {
    render(
      <InteractiveRegionsMap title='Map' regions={[]}>
        <MapJourney waypoints={WAYPOINTS} autoFocus={false}>
          <JourneyControls showCounter={false} />
        </MapJourney>
      </InteractiveRegionsMap>
    );
    expect(screen.queryByText('1 / 3')).toBeNull();
  });
});

describe('JourneyCallout', () => {
  beforeEach(() => vi.useFakeTimers());
  afterEach(() => {
    vi.useRealTimers();
    cleanup();
  });

  it('renders the active waypoint label inside the SVG overlay', () => {
    render(
      <InteractiveRegionsMap title='Map' regions={[]}>
        <MapJourney waypoints={WAYPOINTS} autoFocus={false}>
          <JourneyCallout />
          <JourneyControls />
        </MapJourney>
      </InteractiveRegionsMap>
    );
    // London is the first waypoint — its label should appear in the callout
    expect(screen.getAllByText('London').length).toBeGreaterThan(0);
  });

  it('updates the callout label when advancing steps', async () => {
    render(
      <InteractiveRegionsMap title='Map' regions={[]}>
        <MapJourney waypoints={WAYPOINTS} autoFocus={false}>
          <JourneyCallout />
          <JourneyControls />
        </MapJourney>
      </InteractiveRegionsMap>
    );
    const next = screen.getByLabelText('Next waypoint');
    await act(async () => {
      next.click();
      vi.runAllTimers();
    });
    // Dubai is now the active waypoint
    expect(screen.getAllByText('Dubai').length).toBeGreaterThan(0);
  });
});

describe('MapJourney — mixed transport modes', () => {
  beforeEach(() => vi.useFakeTimers());
  afterEach(() => {
    vi.useRealTimers();
    cleanup();
  });

  it('renders the step list with the correct transport for each waypoint', () => {
    // WAYPOINTS[1] has transport='ferry', WAYPOINTS[2] has transport='train'
    // JourneyStepList renders an icon per waypoint; we verify the list renders all labels
    render(<JourneyWrapper />);
    expect(screen.getByText('London')).toBeDefined();
    expect(screen.getByText('Dubai')).toBeDefined();
    expect(screen.getByText('Sydney')).toBeDefined();
  });

  it('duration label is shown for waypoints that have it', () => {
    render(<JourneyWrapper />);
    expect(screen.getByText('7h')).toBeDefined();
  });
});

describe('MapJourney — showFullRoute=false (progressive reveal)', () => {
  beforeEach(() => vi.useFakeTimers());
  afterEach(() => {
    vi.useRealTimers();
    cleanup();
  });

  it('at step 0 no route legs are visible in the SVG', () => {
    // We cannot easily inspect SVG paths in jsdom, but we can verify the component
    // renders without error at step 0 with showFullRoute=false
    expect(() =>
      render(
        <InteractiveRegionsMap title='Map' regions={[]}>
          <MapJourney
            waypoints={WAYPOINTS}
            autoFocus={false}
            showFullRoute={false}
          >
            <JourneyControls />
          </MapJourney>
        </InteractiveRegionsMap>
      )
    ).not.toThrow();
  });

  it('advancing shows the route for completed legs', async () => {
    render(
      <InteractiveRegionsMap title='Map' regions={[]}>
        <MapJourney
          waypoints={WAYPOINTS}
          autoFocus={false}
          showFullRoute={false}
        >
          <JourneyControls />
        </MapJourney>
      </InteractiveRegionsMap>
    );
    const next = screen.getByLabelText('Next waypoint');
    await act(async () => {
      next.click();
      vi.runAllTimers();
    });
    // After advancing to step 1, the leg 0→1 should be visible (component doesn't throw)
    expect(screen.getByText('2 / 3')).toBeDefined();
  });
});

describe('MapJourney — custom renderers in JourneyStepList', () => {
  it('accepts a renderItem prop and uses it', () => {
    render(
      <InteractiveRegionsMap title='Map' regions={[]}>
        <MapJourney waypoints={WAYPOINTS} autoFocus={false}>
          <JourneyStepList
            renderItem={(wp, _i, isActive) => (
              <span data-testid={`item-${wp.id}`} data-active={isActive}>
                {wp.label}
              </span>
            )}
          />
        </MapJourney>
      </InteractiveRegionsMap>
    );
    const item = document.querySelector(
      '[data-testid="item-a"]'
    ) as HTMLElement | null;
    expect(item).not.toBeNull();
    expect(item?.getAttribute('data-active')).toBe('true');
    expect(document.querySelector('[data-testid="item-b"]')).not.toBeNull();
  });
});
