/** @vitest-environment jsdom */
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react';
import { GalleryView } from './GalleryView';
import { downloadImage } from '../imageUtils';
import { en } from '../i18n/en';
import type { TryOnHistoryItem } from '../types';

// The only import of imageUtils in this tree is the share fallback.
vi.mock('../imageUtils', () => ({ downloadImage: vi.fn() }));

const NOW = new Date(2026, 6, 30, 12, 0, 0);

function item(overrides: Partial<TryOnHistoryItem> = {}): TryOnHistoryItem {
  return {
    id: 'try-1',
    resultImage: 'data:image/png;base64,RESULT',
    clothingImage: 'data:image/png;base64,GARMENT',
    productTitle: 'Linen Shirt',
    productUrl: '/product/linen-shirt',
    createdAt: new Date(2026, 6, 30, 9, 0, 0).toISOString(),
    ...overrides,
  };
}

function renderGallery(items: TryOnHistoryItem[], onDelete = vi.fn(), onBack = vi.fn()) {
  return {
    onDelete,
    onBack,
    ...render(
      <GalleryView
        items={items}
        onBack={onBack}
        onDelete={onDelete}
        t={en.gallery}
        shareT={en.share}
        now={NOW}
      />
    ),
  };
}

beforeEach(() => {
  vi.mocked(downloadImage).mockClear();
});

afterEach(() => {
  cleanup();
  Reflect.deleteProperty(navigator, 'share');
  Reflect.deleteProperty(navigator, 'canShare');
});

describe('an empty gallery', () => {
  it('explains what will show up there', () => {
    renderGallery([]);
    expect(screen.getByText(en.gallery.emptyTitle)).toBeDefined();
    expect(screen.getByText(en.gallery.emptyMessage)).toBeDefined();
  });

  it('still offers the way back', () => {
    const { onBack } = renderGallery([]);
    fireEvent.click(screen.getByText(en.gallery.back));
    expect(onBack).toHaveBeenCalledTimes(1);
  });
});

describe('a saved try-on', () => {
  it('shows the render, the product name and when it was made', () => {
    const { container } = renderGallery([item()]);

    expect(screen.getByText('Linen Shirt')).toBeDefined();
    expect(screen.getByText(en.gallery.today)).toBeDefined();
    const render_ = container.querySelector('.aisthetix-tryon-figure-image');
    expect(render_?.getAttribute('src')).toBe('data:image/png;base64,RESULT');
  });

  // The detail the owner cared about most: judging fidelity without leaving
  // the picture. It has to be on the gallery too, not only on a fresh result.
  it('keeps the garment thumbnail on the render', () => {
    const { container } = renderGallery([item()]);
    const pip = container.querySelector('.aisthetix-tryon-figure-pip');
    expect(pip?.getAttribute('src')).toBe('data:image/png;base64,GARMENT');
    expect(pip?.getAttribute('alt')).toBe(en.gallery.garmentThumbnail);
  });

  it('names an item saved without a product title', () => {
    renderGallery([item({ productTitle: undefined })]);
    expect(screen.getByText(en.gallery.untitledProduct)).toBeDefined();
  });

  it('writes an older try-on as a date', () => {
    renderGallery([item({ createdAt: new Date(2026, 5, 12, 9, 0, 0).toISOString() })]);
    expect(screen.getByText('Jun 12, 2026')).toBeDefined();
  });

  it('lists one row per try-on', () => {
    const { container } = renderGallery([
      item(),
      item({ id: 'try-2', productTitle: 'Wool Coat' }),
    ]);
    expect(container.querySelectorAll('.aisthetix-gallery-item')).toHaveLength(2);
  });
});

// Nothing is for sale on the demo, so the row leads back to the item rather
// than into a cart.
describe('going back to the item', () => {
  it('links to the product page recorded when the try-on was made', () => {
    renderGallery([item({ productUrl: '/product/amalfi-linen-dress' })]);
    expect(
      screen.getByRole('link', { name: en.gallery.viewProduct }).getAttribute('href')
    ).toBe('/product/amalfi-linen-dress');
  });

  // A link that cannot go anywhere is worse than no link.
  it('omits the link on an item saved without a product url', () => {
    renderGallery([item({ productUrl: undefined })]);
    expect(screen.queryByRole('link', { name: en.gallery.viewProduct })).toBeNull();
    expect(screen.getByText(en.gallery.share)).toBeDefined();
  });
});

describe('sharing from the gallery', () => {
  it('hands the render to the share sheet as a file', async () => {
    const share = vi.fn().mockResolvedValue(undefined);
    Object.defineProperty(navigator, 'share', { value: share, configurable: true });
    Object.defineProperty(navigator, 'canShare', { value: () => true, configurable: true });

    renderGallery([item()]);
    fireEvent.click(screen.getByText(en.gallery.share));

    await waitFor(() => expect(share).toHaveBeenCalledTimes(1));
    expect(share.mock.calls[0][0].files[0].type).toBe('image/png');
  });

  it('saves the image and says so when there is no share sheet', async () => {
    renderGallery([item()]);
    fireEvent.click(screen.getByText(en.gallery.share));

    await waitFor(() => expect(downloadImage).toHaveBeenCalledTimes(1));
    expect(screen.getByText(en.share.downloaded)).toBeDefined();
  });
});

describe('removing a try-on', () => {
  it('asks the parent to forget that id', () => {
    const { onDelete } = renderGallery([item({ id: 'try-42' })]);
    fireEvent.click(screen.getByLabelText(en.gallery.removeLabel('Linen Shirt')));
    expect(onDelete).toHaveBeenCalledWith('try-42');
  });
});
