import { renderHook, act } from '@testing-library/react';
import { RefObject } from 'react';
import useAdjacentCardWidth from '../useAdjacentCardWidth';
import { IMedia } from '../../types';

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

const makePost = (id: string, image = 'https://cdn.example.com/img.jpg'): IMedia => ({
  id,
  type: 'image',
  caption: '',
  source: 'instagram',
  post: `https://www.instagram.com/p/${id}`,
  image,
  productsLinked: {},
  postIndex: 0,
});

const makeContainerRef = (
  clientWidth: number,
  clientHeight: number,
): RefObject<HTMLDivElement> => ({
  current: { clientWidth, clientHeight } as HTMLDivElement,
});

// ---------------------------------------------------------------------------
// Image mock — same pattern as useMediaCardWidth tests.
// We intercept window.Image so we can control naturalWidth/Height and fire
// onload/onerror manually without a real network request.
// ---------------------------------------------------------------------------

interface MockImage {
  onload: (() => void) | null;
  onerror: (() => void) | null;
  src: string;
  naturalWidth: number;
  naturalHeight: number;
}

let capturedImages: MockImage[] = [];

beforeEach(() => {
  capturedImages = [];
  (global as any).Image = jest.fn().mockImplementation(() => {
    const img: MockImage = {
      onload: null,
      onerror: null,
      src: '',
      naturalWidth: 0,
      naturalHeight: 0,
    };
    capturedImages.push(img);
    return img;
  });
});

afterEach(() => {
  jest.restoreAllMocks();
});

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe('useAdjacentCardWidth', () => {
  const containerRef = makeContainerRef(1000, 800);
  const handleNextPost = jest.fn();
  const handlePreviousPost = jest.fn();

  const defaultProps = {
    containerRef,
    nextPost: null,
    prevPost: null,
    currentCardWidth: 'auto',
    handleNextPost,
    handlePreviousPost,
  };

  beforeEach(() => {
    jest.clearAllMocks();
  });

  // ── initial state ──────────────────────────────────────────────────────────

  it('starts with pendingCardWidth as null', () => {
    const { result } = renderHook(() => useAdjacentCardWidth(defaultProps));
    expect(result.current.pendingCardWidth).toBeNull();
  });

  // ── precompute on mount / post change ─────────────────────────────────────

  it('pre-loads images for both adjacent posts on mount', () => {
    const next = makePost('next-1');
    const prev = makePost('prev-1');

    renderHook(() =>
      useAdjacentCardWidth({ ...defaultProps, nextPost: next, prevPost: prev }),
    );

    // Two Image instances should have been created (one per adjacent post).
    expect(capturedImages).toHaveLength(2);
    const srcs = capturedImages.map((img) => img.src);
    expect(srcs).toContain(next.image);
    expect(srcs).toContain(prev.image);
  });

  it('does not create an Image when a post has no image or thumbnail', () => {
    const noImage = makePost('no-img', undefined as unknown as string);
    noImage.image = undefined;
    noImage.thumbnail = undefined;

    renderHook(() =>
      useAdjacentCardWidth({ ...defaultProps, nextPost: noImage }),
    );

    expect(capturedImages).toHaveLength(0);
  });

  it('re-runs precompute when adjacent posts change', () => {
    const next1 = makePost('next-1');
    const next2 = makePost('next-2');

    const { rerender } = renderHook(
      ({ nextPost }) => useAdjacentCardWidth({ ...defaultProps, nextPost }),
      { initialProps: { nextPost: next1 } },
    );

    const firstCount = capturedImages.length;

    rerender({ nextPost: next2 });

    expect(capturedImages.length).toBeGreaterThan(firstCount);
    const newSrc = capturedImages[capturedImages.length - 1].src;
    expect(newSrc).toBe(next2.image);
  });

  // ── width computation ──────────────────────────────────────────────────────

  it('stores the computed px width after onload fires', () => {
    const next = makePost('next-compute');
    const { result, rerender } = renderHook(
      ({ nextPost }) => useAdjacentCardWidth({ ...defaultProps, containerRef, nextPost }),
      { initialProps: { nextPost: next } },
    );

    // Fire onload with a 2:1 landscape image.
    // Container: 1000w × 800h → natural card width = 800 * 2 = 1600, capped at 1000*0.8 = 800px.
    act(() => {
      const img = capturedImages.find((i) => i.src === next.image)!;
      img.naturalWidth = 1600;
      img.naturalHeight = 800;
      img.onload!();
    });

    // Now navigate to next → pendingCardWidth should be 800px.
    act(() => {
      result.current.handleNextWithWidth();
    });

    expect(result.current.pendingCardWidth).toBe('800px');
    expect(handleNextPost).toHaveBeenCalledTimes(1);
  });

  it('stores "auto" when the image fails to load', () => {
    const next = makePost('next-error');
    const { result } = renderHook(() =>
      useAdjacentCardWidth({ ...defaultProps, nextPost: next }),
    );

    act(() => {
      const img = capturedImages.find((i) => i.src === next.image)!;
      img.onerror!();
    });

    act(() => {
      result.current.handleNextWithWidth();
    });

    expect(result.current.pendingCardWidth).toBe('auto');
  });

  it('stores "auto" when the container has zero dimensions', () => {
    const zeroRef = makeContainerRef(0, 0);
    const next = makePost('next-zero');
    const { result } = renderHook(() =>
      useAdjacentCardWidth({ ...defaultProps, containerRef: zeroRef, nextPost: next }),
    );

    act(() => {
      const img = capturedImages.find((i) => i.src === next.image)!;
      img.naturalWidth = 800;
      img.naturalHeight = 600;
      img.onload!();
    });

    act(() => {
      result.current.handleNextWithWidth();
    });

    expect(result.current.pendingCardWidth).toBe('auto');
  });

  it('respects a custom maxWidthRatio', () => {
    const next = makePost('next-ratio');
    const { result } = renderHook(() =>
      useAdjacentCardWidth({
        ...defaultProps,
        containerRef: makeContainerRef(1000, 800),
        nextPost: next,
        maxWidthRatio: 0.5,
      }),
    );

    // 1:1 image → natural card width = 800px, capped at 1000 * 0.5 = 500px.
    act(() => {
      const img = capturedImages.find((i) => i.src === next.image)!;
      img.naturalWidth = 800;
      img.naturalHeight = 800;
      img.onload!();
    });

    act(() => {
      result.current.handleNextWithWidth();
    });

    expect(result.current.pendingCardWidth).toBe('500px');
  });

  // ── handleNextWithWidth ───────────────────────────────────────────────────

  it('calls handleNextPost even when no width is precomputed', () => {
    const { result } = renderHook(() => useAdjacentCardWidth(defaultProps));

    act(() => {
      result.current.handleNextWithWidth();
    });

    expect(handleNextPost).toHaveBeenCalledTimes(1);
    expect(result.current.pendingCardWidth).toBeNull();
  });

  it('sets pendingCardWidth to null when nextPost is null', () => {
    const { result } = renderHook(() =>
      useAdjacentCardWidth({ ...defaultProps, nextPost: null }),
    );

    act(() => {
      result.current.handleNextWithWidth();
    });

    expect(result.current.pendingCardWidth).toBeNull();
  });

  // ── handlePrevWithWidth ───────────────────────────────────────────────────

  it('sets pendingCardWidth for the previous post and calls handlePreviousPost', () => {
    const prev = makePost('prev-compute');
    const { result } = renderHook(() =>
      useAdjacentCardWidth({ ...defaultProps, prevPost: prev }),
    );

    // 1:1 image → natural = 800px, capped at 800px (< 1000 * 0.8 = 800), so exactly 800px.
    act(() => {
      const img = capturedImages.find((i) => i.src === prev.image)!;
      img.naturalWidth = 800;
      img.naturalHeight = 800;
      img.onload!();
    });

    act(() => {
      result.current.handlePrevWithWidth();
    });

    expect(result.current.pendingCardWidth).toBe('800px');
    expect(handlePreviousPost).toHaveBeenCalledTimes(1);
  });

  it('calls handlePreviousPost even when no width is precomputed', () => {
    const { result } = renderHook(() => useAdjacentCardWidth(defaultProps));

    act(() => {
      result.current.handlePrevWithWidth();
    });

    expect(handlePreviousPost).toHaveBeenCalledTimes(1);
    expect(result.current.pendingCardWidth).toBeNull();
  });

  // ── pendingCardWidth reset ────────────────────────────────────────────────

  it('clears pendingCardWidth when currentCardWidth changes', () => {
    const next = makePost('next-reset');
    const { result, rerender } = renderHook(
      ({ currentCardWidth }) =>
        useAdjacentCardWidth({ ...defaultProps, nextPost: next, currentCardWidth }),
      { initialProps: { currentCardWidth: 'auto' } },
    );

    act(() => {
      const img = capturedImages.find((i) => i.src === next.image)!;
      img.naturalWidth = 400;
      img.naturalHeight = 800;
      img.onload!();
    });

    act(() => {
      result.current.handleNextWithWidth();
    });
    expect(result.current.pendingCardWidth).not.toBeNull();

    // Simulate useMediaCardWidth settling on the real width.
    rerender({ currentCardWidth: '400px' });
    expect(result.current.pendingCardWidth).toBeNull();
  });
});
