/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { fireEvent, render } from '@testing-library/react';
import React, { ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { ImageLoader } from './ImageLoader';

describe('ImageLoader', () => {
  it('renders the component without crashing', () => {
    const { container } = render(<ImageLoader imgSrc="" />);

    expect(container).toBeTruthy();
  });

  it(`gives the image element it's src, height, width, and alt attributes`, () => {
    const mockSrc = 'test-src';
    const mockHeight = 'test-height';
    const mockWidth = 'test-width';
    const mockAlt = 'test-alt';

    const { container } = render(
      <ImageLoader
        imgSrc={mockSrc}
        alt={mockAlt}
        imgHeight={mockHeight}
        imgWidth={mockWidth}
      />,
    );

    const img = container.querySelector('img') as HTMLImageElement;

    expect(img.src).toContain(mockSrc);
    expect(img.getAttribute('height')).toBe(mockHeight);
    expect(img.getAttribute('width')).toBe(mockWidth);
    expect(img.alt).toBe(mockAlt);
  });

  it('renders the loading animation while an image is loading', () => {
    const { container } = render(<ImageLoader imgSrc="" />);

    const loader = container.querySelector('svg');
    const img = container.querySelector('img') as HTMLImageElement;

    expect(loader).toBeInTheDocument();
    expect(img.style.display).toBe('none');
  });

  it('renders the image after loading has completed', () => {
    const { container } = render(<ImageLoader imgSrc="" />);
    const img = container.querySelector('img') as HTMLImageElement;

    fireEvent.load(img);

    const loader = container.querySelector('svg');

    expect(loader).not.toBeInTheDocument();
    expect(img.style.display).toBe('unset');
  });

  it('emits onLoad callback with img src after successful load', () => {
    const spy = vi.fn();
    const mockUrl = 'mock-url';
    const { container } = render(<ImageLoader imgSrc={mockUrl} onLoad={spy} />);
    const img = container.querySelector('img') as HTMLImageElement;

    fireEvent.load(img);

    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy).toHaveBeenCalledWith(mockUrl);
  });

  it('emits onImageClicked when the image is clicked with the event', () => {
    const spy = vi.fn();
    const mockUrl = 'mock-url';
    const { container } = render(
      <ImageLoader imgSrc={mockUrl} onImageClick={spy} />,
    );

    const image = container.querySelector('img') as HTMLImageElement;
    fireEvent.click(image);

    expect(spy).toHaveBeenCalledTimes(1);
  });

  it('renders the fallback background color when loading has failed', () => {
    const { container } = render(<ImageLoader imgSrc="" />);
    const img = container.querySelector('img') as HTMLImageElement;

    fireEvent.error(img);

    const loader = container.querySelector('svg');
    const fallbackContainer = container.querySelectorAll('.container')[1];

    expect(container.querySelector('img')).not.toBeInTheDocument();
    expect(loader).not.toBeInTheDocument();
    expect(fallbackContainer?.classList.contains('fallback')).toBe(true);
  });

  it('renders a fallback image when loading has failed', () => {
    const mockFallbackUrl = 'mock-url';
    const { container } = render(
      <ImageLoader imgSrc="" fallbackSrc={mockFallbackUrl} />,
    );
    const img = container.querySelector('img') as HTMLImageElement;

    fireEvent.error(img);

    const loader = container.querySelector('svg');
    const images = container.querySelectorAll('img');
    const fallbackContainers = container.querySelectorAll('.container');

    expect(loader).not.toBeInTheDocument();
    expect(fallbackContainers).toHaveLength(1); // no background color fallback, just one container
    expect(images[0]).toBeInTheDocument(); // fallback image
    expect(images[0].src).toContain(mockFallbackUrl);
    expect(images[0].classList.contains('fallBackImage')).toBe(true);
  });

  it('emits onError callback with img src after load failure', () => {
    const spy = vi.fn();
    const mockUrl = 'mock-url';
    const { container } = render(
      <ImageLoader imgSrc={mockUrl} onError={spy} />,
    );
    const img = container.querySelector('img') as HTMLImageElement;

    fireEvent.error(img);

    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy).toHaveBeenCalledWith(mockUrl);
  });

  it('replaces default loading SVG with a custom SVG', () => {
    const mockId = 'mock-id';
    const mockSVG: ReactNode = (
      <svg id={mockId}>
        <circle r="50" />
      </svg>
    );
    const { container } = render(
      <ImageLoader imgSrc="" loadingSkeleton={mockSVG} />,
    );

    const svg = container.querySelector(`#${mockId}`);

    expect(svg).toBeInTheDocument();
  });

  it('default square skeleton should take up the full space of the loader by default', () => {
    const { container } = render(<ImageLoader imgSrc="" />);

    const svg = container.querySelector('svg');
    const skeleton = container.querySelector('rect');

    expect(skeleton?.getAttribute('width')).toBe('100%');
    expect(skeleton?.getAttribute('height')).toBe('100%');
    expect(svg?.getAttribute('viewBox')).toBe('0 0 50 50');
  });

  it('uses width and height for the squarebox', () => {
    const { container } = render(
      <ImageLoader imgSrc="" width={123} height={456} />,
    );

    const svg = container.querySelector('svg');

    expect(svg?.getAttribute('viewBox')).toBe('0 0 123 456');
  });

  it('passes svg viewbox, animation speed, color props to ContentLoader', () => {
    const mockViewbox = '75 75 75 75';
    const { container } = render(
      <ImageLoader
        imgSrc=""
        speed={1}
        backgroundColor="#CCC"
        foregroundColor="#FFF"
        viewBox={mockViewbox}
      />,
    );

    const svg = container.querySelector('svg');

    expect(svg?.getAttribute('viewBox')).toBe(mockViewbox);
    // Note: speed and color props are internal to ContentLoader
  });

  it(`loader dimensions defaults to '50px' by '50px'`, () => {
    const { container } = render(<ImageLoader imgSrc="" />);

    const loader = container.querySelector('.loader') as HTMLDivElement;

    expect(loader.style.height).toBe('50px');
    expect(loader.style.width).toBe('50px');
  });

  it('sets the height and width of the loader', () => {
    const mockHeight = 15;
    const mockWidth = 75;
    const { container } = render(
      <ImageLoader imgSrc="" height={mockHeight} width={mockWidth} />,
    );

    const loader = container.querySelector('.loader') as HTMLDivElement;

    expect(loader.style.height).toBe(`${mockHeight}px`);
    expect(loader.style.width).toBe(`${mockWidth}px`);
  });
});
