/**
 * Tests for imageUrlUpload utils: fetchImageFromUrl and uploadImageFromUrlHelper
 */

import { fetchImageFromUrl, uploadImageFromUrlHelper } from '../imageUrlUpload';

describe('imageUrlUpload', () => {
  const formatMessage = jest.fn((msg) => msg?.id ?? msg?.defaultMessage ?? 'formatted');
  const messages = {
    imageTypeInvalid: { id: 'imageTypeInvalid' },
    imageSizeInvalid: { id: 'imageSizeInvalid' },
    imageLoadError: { id: 'imageLoadError' },
  };

  beforeEach(() => {
    jest.clearAllMocks();
    formatMessage.mockImplementation((msg) => (msg && typeof msg === 'object' && msg.id) ? msg.id : 'formatted');
  });

  describe('fetchImageFromUrl', () => {
    const validUrl = 'https://example.com/image.jpg';

    it('throws when url is missing', async () => {
      await expect(fetchImageFromUrl()).rejects.toThrow('URL is required');
      await expect(fetchImageFromUrl(null)).rejects.toThrow('URL is required');
    });

    it('throws when url is empty string', async () => {
      await expect(fetchImageFromUrl('')).rejects.toThrow('URL is required');
    });

    it('throws when url is only whitespace', async () => {
      await expect(fetchImageFromUrl('   ')).rejects.toThrow('URL is required');
    });

    it('trims url before fetching', async () => {
      const mockResponse = { ok: true, headers: new Headers() };
      global.fetch = jest.fn().mockResolvedValue(mockResponse);

      await fetchImageFromUrl('  https://example.com/img.png  ');
      expect(global.fetch).toHaveBeenCalledWith(
        'https://example.com/img.png',
        expect.objectContaining({ method: 'GET', redirect: 'follow', mode: 'cors' })
      );
    });

    it('returns response when fetch is ok', async () => {
      const mockResponse = { ok: true, status: 200, headers: new Headers() };
      global.fetch = jest.fn().mockResolvedValue(mockResponse);

      const result = await fetchImageFromUrl(validUrl);
      expect(result).toBe(mockResponse);
      expect(global.fetch).toHaveBeenCalledWith(
        validUrl,
        expect.objectContaining({ method: 'GET', redirect: 'follow', mode: 'cors' })
      );
    });

    it('throws when response is not ok', async () => {
      const mockResponse = { ok: false, status: 404, statusText: 'Not Found' };
      global.fetch = jest.fn().mockResolvedValue(mockResponse);

      await expect(fetchImageFromUrl(validUrl)).rejects.toThrow(
        'Failed to fetch image: 404 Not Found'
      );
    });

    it('throws on network or CORS error', async () => {
      global.fetch = jest.fn().mockRejectedValue(new Error('Network error'));

      await expect(fetchImageFromUrl(validUrl)).rejects.toThrow('Network error');
    });
  });

  describe('uploadImageFromUrlHelper', () => {
    const uploadAssetFn = jest.fn();
    const fileNamePrefix = 'test-image';
    const maxSize = 5000000;

    it('returns error when content type is not allowed', async () => {
      const response = {
        ok: true,
        headers: new Headers({ 'Content-Type': 'text/html' }),
        blob: jest.fn().mockResolvedValue(new Blob()),
      };
      global.fetch = jest.fn().mockResolvedValue(response);

      const result = await uploadImageFromUrlHelper(
        'https://example.com/page',
        formatMessage,
        messages,
        uploadAssetFn,
        fileNamePrefix,
        maxSize,
        ['image/jpeg', 'image/png']
      );

      expect(result).toEqual({ success: false, error: 'imageTypeInvalid' });
      expect(formatMessage).toHaveBeenCalledWith(messages.imageTypeInvalid);
      expect(uploadAssetFn).not.toHaveBeenCalled();
    });

    it('normalizes content type by stripping charset and trimming', async () => {
      const response = {
        ok: true,
        headers: new Headers({ 'Content-Type': '  image/PNG; charset=utf-8  ' }),
        blob: jest.fn().mockResolvedValue(new Blob(['x'], { type: 'image/png' })),
      };
      global.fetch = jest.fn().mockResolvedValue(response);

      const OriginalImage = global.Image;
      global.Image = class MockImage {
        constructor() {
          this.width = 10;
          this.height = 10;
          setTimeout(() => { if (this.onload) this.onload(); }, 0);
        }

        get src() { return this._src; }

        set src(v) { this._src = v; }
      };

      const result = await uploadImageFromUrlHelper(
        'https://example.com/img.png',
        formatMessage,
        messages,
        uploadAssetFn,
        fileNamePrefix,
        maxSize,
        ['image/png']
      );

      global.Image = OriginalImage;

      expect(result.success).toBe(true);
      expect(uploadAssetFn).toHaveBeenCalledWith(
        expect.any(File),
        'image',
        expect.objectContaining({ width: 10, height: 10, error: false })
      );
      const file = uploadAssetFn.mock.calls[0][0];
      expect(file.name).toMatch(/\.png$/);
    });

    it('returns error when blob size exceeds maxSize', async () => {
      const largeBlob = new Blob([new ArrayBuffer(maxSize + 1)]);
      const response = {
        ok: true,
        headers: new Headers({ 'Content-Type': 'image/jpeg' }),
        blob: jest.fn().mockResolvedValue(largeBlob),
      };
      global.fetch = jest.fn().mockResolvedValue(response);

      const result = await uploadImageFromUrlHelper(
        'https://example.com/large.jpg',
        formatMessage,
        messages,
        uploadAssetFn,
        fileNamePrefix,
        maxSize
      );

      expect(result).toEqual({ success: false, error: 'imageSizeInvalid' });
      expect(formatMessage).toHaveBeenCalledWith(messages.imageSizeInvalid);
      expect(uploadAssetFn).not.toHaveBeenCalled();
    });

    it('returns error when image fails to load (invalid image data)', async () => {
      const blob = new Blob(['not-an-image'], { type: 'image/jpeg' });
      const response = {
        ok: true,
        headers: new Headers({ 'Content-Type': 'image/jpeg' }),
        blob: jest.fn().mockResolvedValue(blob),
      };
      global.fetch = jest.fn().mockResolvedValue(response);

      const OriginalImage = global.Image;
      global.Image = class MockImage {
        constructor() {
          setTimeout(() => {
            if (this.onerror) this.onerror(new Event('error'));
          }, 0);
        }

        get src() { return this._src; }

        set src(v) { this._src = v; }
      };

      const result = await uploadImageFromUrlHelper(
        'https://example.com/bad.jpg',
        formatMessage,
        messages,
        uploadAssetFn,
        fileNamePrefix,
        maxSize
      );

      global.Image = OriginalImage;

      expect(result.success).toBe(false);
      expect(result.error).toBe('imageLoadError');
      expect(formatMessage).toHaveBeenCalledWith(messages.imageLoadError);
      expect(uploadAssetFn).not.toHaveBeenCalled();
    });

    it('returns error on fetch failure and uses imageLoadError message', async () => {
      global.fetch = jest.fn().mockRejectedValue(new Error('CORS'));

      const result = await uploadImageFromUrlHelper(
        'https://example.com/image.jpg',
        formatMessage,
        messages,
        uploadAssetFn,
        fileNamePrefix,
        maxSize
      );

      expect(result).toEqual({ success: false, error: 'imageLoadError' });
      expect(formatMessage).toHaveBeenCalledWith(messages.imageLoadError);
      expect(uploadAssetFn).not.toHaveBeenCalled();
    });

    it('trims url before processing', async () => {
      const response = {
        ok: true,
        headers: new Headers({ 'Content-Type': 'image/jpeg' }),
        blob: jest.fn().mockResolvedValue(new Blob()),
      };
      global.fetch = jest.fn().mockResolvedValue(response);

      await uploadImageFromUrlHelper(
        '  https://example.com/img.jpg  ',
        formatMessage,
        messages,
        uploadAssetFn,
        fileNamePrefix,
        maxSize
      );

      expect(global.fetch).toHaveBeenCalledWith(
        'https://example.com/img.jpg',
        expect.any(Object)
      );
    });

    it('calls uploadAssetFn with file, type and fileParams on success', async () => {
      const blob = new Blob([new ArrayBuffer(100)], { type: 'image/png' });
      const response = {
        ok: true,
        headers: new Headers({ 'Content-Type': 'image/png' }),
        blob: jest.fn().mockResolvedValue(blob),
      };
      global.fetch = jest.fn().mockResolvedValue(response);

      const OriginalImage = global.Image;
      global.Image = class MockImage {
        constructor() {
          this.width = 100;
          this.height = 80;
          setTimeout(() => {
            if (this.onload) this.onload();
          }, 0);
        }

        get src() { return this._src; }

        set src(v) { this._src = v; }
      };

      const result = await uploadImageFromUrlHelper(
        'https://example.com/valid.png',
        formatMessage,
        messages,
        uploadAssetFn,
        fileNamePrefix,
        maxSize
      );

      global.Image = OriginalImage;

      expect(result).toEqual({ success: true, error: '' });
      expect(uploadAssetFn).toHaveBeenCalledTimes(1);
      const [file, type, fileParams] = uploadAssetFn.mock.calls[0];
      expect(file).toBeInstanceOf(File);
      expect(file.name).toMatch(new RegExp(`^${fileNamePrefix}\\.png$`));
      expect(type).toBe('image');
      expect(fileParams).toEqual(
        expect.objectContaining({
          width: 100,
          height: 80,
          error: false,
        })
      );
    });
  });
});
