import {
  formatBytes,
  getFileExtensionFromUrl,
  getFileExtensionFromMime,
  getDocumentBadgeLabel,
  countLines,
  shouldUseDarkFileCard,
  fetchLinkPreview,
  getContentSize,
  isValidUrl,
  normalizeUrl,
  getImageDisplaySource,
  FILE_EXTENSIONS_DARK_CARD,
  FILE_MIME_TYPES_DARK_CARD,
  TEXT_FILE_EXTENSIONS,
  IMAGE_MIME_TYPES,
  FALLBACK_IMAGE_BASE64,
} from './MediaItemWidget.utils';
import type { Medium } from '@memori.ai/memori-api-client/dist/types';

describe('MediaItemWidget.utils', () => {
  describe('formatBytes', () => {
    it('returns "0 Bytes" for undefined or 0', () => {
      expect(formatBytes(undefined)).toBe('0 Bytes');
      expect(formatBytes(0)).toBe('0 Bytes');
    });

    it('formats bytes correctly', () => {
      expect(formatBytes(1)).toBe('1 Bytes');
      expect(formatBytes(1024)).toBe('1 KB');
      expect(formatBytes(1536)).toBe('1.5 KB');
      expect(formatBytes(1024 * 1024)).toBe('1 MB');
      expect(formatBytes(1024 * 1024 * 1024)).toBe('1 GB');
    });
  });

  describe('getFileExtensionFromUrl', () => {
    it('returns null for undefined or empty url', () => {
      expect(getFileExtensionFromUrl(undefined)).toBeNull();
      expect(getFileExtensionFromUrl('')).toBeNull();
    });

    it('extracts extension from url', () => {
      expect(getFileExtensionFromUrl('https://example.com/file.pdf')).toBe('PDF');
      expect(getFileExtensionFromUrl('https://example.com/doc.xlsx?token=abc')).toBe('XLSX');
      expect(getFileExtensionFromUrl('image.png')).toBe('PNG');
      expect(getFileExtensionFromUrl('file.Md')).toBe('MD');
    });

    it('returns null when no extension', () => {
      expect(getFileExtensionFromUrl('https://example.com/path')).toBeNull();
    });
  });

  describe('getFileExtensionFromMime', () => {
    it('maps known mime types to extensions', () => {
      expect(getFileExtensionFromMime('application/pdf')).toBe('PDF');
      expect(getFileExtensionFromMime('text/html')).toBe('HTML');
      expect(getFileExtensionFromMime('text/plain')).toBe('TXT');
      expect(getFileExtensionFromMime('application/json')).toBe('JSON');
      expect(getFileExtensionFromMime('application/vnd.ms-excel')).toBe('Excel');
      expect(
        getFileExtensionFromMime(
          'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        )
      ).toBe('Excel');
    });

    it('maps office mime types with parameters to short labels', () => {
      expect(
        getFileExtensionFromMime(
          'application/vnd.openxmlformats-officedocument.wordprocessingml.document; charset=binary'
        )
      ).toBe('Word');
      expect(
        getFileExtensionFromMime(
          'application/vnd.openxmlformats-officedocument.spreadsheetml.template'
        )
      ).toBe('Excel');
    });

    it('uses subtype when not in MIME_TO_EXT map', () => {
      expect(getFileExtensionFromMime('application/octet-stream')).toBe('OCTET-STREAM');
      expect(getFileExtensionFromMime('image/jpeg')).toBe('JPEG');
    });

    it('falls back to FILE when mime has no subtype', () => {
      expect(getFileExtensionFromMime('unknown')).toBe('FILE');
    });
  });

  describe('getDocumentBadgeLabel', () => {
    it('prefers URL extension, then filename, then mime type', () => {
      expect(
        getDocumentBadgeLabel(
          'application/pdf',
          'report.docx',
          'https://example.com/file.pdf'
        )
      ).toBe('PDF');
      expect(
        getDocumentBadgeLabel(
          'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
          'report.docx',
          'https://example.com/asset/123'
        )
      ).toBe('Word');
      expect(
        getDocumentBadgeLabel(
          'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
          'budget.xlsx'
        )
      ).toBe('Excel');
    });
  });

  describe('countLines', () => {
    it('returns 0 for undefined or empty', () => {
      expect(countLines(undefined)).toBe(0);
      expect(countLines('')).toBe(0);
    });

    it('counts lines with \\n', () => {
      expect(countLines('a')).toBe(1);
      expect(countLines('a\nb')).toBe(2);
      expect(countLines('a\nb\nc')).toBe(3);
    });

    it('counts lines with \\r\\n', () => {
      expect(countLines('a\r\nb')).toBe(2);
    });

    it('counts lines with \\r', () => {
      expect(countLines('a\rb')).toBe(2);
    });
  });

  describe('shouldUseDarkFileCard', () => {
    const dummyItem: Medium = {
      mediumID: 'id',
      mimeType: 'text/plain',
      title: 'File',
      url: 'https://example.com/file.txt',
    };

    it('returns true when file extension is in FILE_EXTENSIONS_DARK_CARD', () => {
      (FILE_EXTENSIONS_DARK_CARD as readonly string[]).forEach((ext) => {
        expect(shouldUseDarkFileCard(dummyItem, ext, 'application/octet-stream')).toBe(true);
      });
    });

    it('returns true when mime type is in FILE_MIME_TYPES_DARK_CARD', () => {
      (FILE_MIME_TYPES_DARK_CARD as readonly string[]).forEach((mime) => {
        expect(shouldUseDarkFileCard(dummyItem, null, mime)).toBe(true);
      });
    });

    it('returns false for unknown extension and mime', () => {
      expect(shouldUseDarkFileCard(dummyItem, null, 'application/octet-stream')).toBe(false);
      expect(shouldUseDarkFileCard(dummyItem, 'XYZ', 'application/octet-stream')).toBe(false);
    });
  });

  describe('fetchLinkPreview', () => {
    const originalFetch = globalThis.fetch;

    afterEach(() => {
      globalThis.fetch = originalFetch;
    });

    it('returns link preview when fetch succeeds', async () => {
      const mockData = {
        title: 'Example',
        description: 'A site',
        image: 'https://example.com/og.png',
      };
      globalThis.fetch = jest.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(mockData),
      });

      const result = await fetchLinkPreview('https://example.com');
      expect(result).toEqual(mockData);
      expect(globalThis.fetch).toHaveBeenCalledWith(
        expect.stringContaining('/api/linkpreview/')
      );
    });

    it('uses provided baseUrl', async () => {
      globalThis.fetch = jest.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({}) });
      await fetchLinkPreview('https://example.com', 'https://custom.api');
      expect(globalThis.fetch).toHaveBeenCalledWith(
        expect.stringMatching(/^https:\/\/custom\.api\/api\/linkpreview\//)
      );
    });

    it('returns null when fetch fails', async () => {
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
      globalThis.fetch = jest.fn().mockRejectedValue(new Error('Network error'));

      const result = await fetchLinkPreview('https://example.com');
      expect(result).toBeNull();
      consoleSpy.mockRestore();
    });
  });

  describe('getContentSize', () => {
    it('returns blob size when content is present', () => {
      const item: Medium = {
        mediumID: 'id',
        mimeType: 'text/plain',
        title: 'File',
        content: 'hello',
      };
      expect(getContentSize(item)).toBe(5);
    });

    it('returns properties.size when no content', () => {
      const item: Medium = {
        mediumID: 'id',
        mimeType: 'application/pdf',
        title: 'Doc',
        url: 'https://example.com/doc.pdf',
        properties: { size: 1024 },
      };
      expect(getContentSize(item)).toBe(1024);
    });

    it('returns undefined when no content and no size property', () => {
      const item: Medium = {
        mediumID: 'id',
        mimeType: 'text/plain',
        title: 'File',
      };
      expect(getContentSize(item)).toBeUndefined();
    });
  });

  describe('isValidUrl', () => {
    it('returns false for undefined or empty', () => {
      expect(isValidUrl(undefined)).toBe(false);
      expect(isValidUrl('')).toBe(false);
    });

    it('returns true for valid URLs', () => {
      expect(isValidUrl('https://example.com')).toBe(true);
      expect(isValidUrl('http://localhost:3000')).toBe(true);
      expect(isValidUrl('https://memori.ai/path')).toBe(true);
    });

    it('returns false for invalid URLs', () => {
      expect(isValidUrl('not a url')).toBe(false);
      expect(isValidUrl('ftp://')).toBe(false);
    });
  });

  describe('normalizeUrl', () => {
    it('returns undefined for undefined or empty', () => {
      expect(normalizeUrl(undefined)).toBeUndefined();
      expect(normalizeUrl('')).toBe('');
    });

    it('leaves http URLs unchanged', () => {
      expect(normalizeUrl('https://example.com')).toBe('https://example.com');
      expect(normalizeUrl('http://example.com')).toBe('http://example.com');
    });

    it('prepends https when no protocol', () => {
      expect(normalizeUrl('example.com')).toBe('https://example.com');
      expect(normalizeUrl('memori.ai/path')).toBe('https://memori.ai/path');
    });
  });

  describe('getImageDisplaySource', () => {
    it('returns resource URL when resourceUrl is valid', () => {
      const item: Medium & { type?: string } = {
        mediumID: 'id',
        mimeType: 'image/png',
        title: 'Image',
        url: 'https://example.com/img.png',
      };
      const resourceUrl = 'https://cdn.example.com/img.png';
      expect(getImageDisplaySource(item, resourceUrl)).toEqual({
        src: resourceUrl,
        isRgb: false,
      });
    });

    it('returns item.url when resourceUrl is empty but item.url is valid', () => {
      const item: Medium & { type?: string } = {
        mediumID: 'id',
        mimeType: 'image/jpeg',
        title: 'Image',
        url: 'https://example.com/photo.jpg',
      };
      expect(getImageDisplaySource(item, '')).toEqual({
        src: 'https://example.com/photo.jpg',
        isRgb: false,
      });
    });

    it('returns rgb/rgba as src and isRgb true', () => {
      const item: Medium & { type?: string } = {
        mediumID: 'id',
        mimeType: 'image/png',
        title: 'Swatch',
        url: 'rgb(255, 0, 0)',
      };
      expect(getImageDisplaySource(item, '')).toEqual({
        src: 'rgb(255, 0, 0)',
        isRgb: true,
      });
      const itemRgba: Medium & { type?: string } = {
        ...item,
        url: 'rgba(0, 128, 255, 0.5)',
      };
      expect(getImageDisplaySource(itemRgba, '')).toEqual({
        src: 'rgba(0, 128, 255, 0.5)',
        isRgb: true,
      });
    });

    it('returns base64 data URL when content is present and no valid URL', () => {
      const item: Medium & { type?: string } = {
        mediumID: 'id',
        mimeType: 'image/png',
        title: 'Inline',
        content: 'abc123',
      };
      expect(getImageDisplaySource(item, '')).toEqual({
        src: 'data:image/png;base64,abc123',
        isRgb: false,
      });
    });

    it('returns undefined src when no valid URL, no rgb, no content', () => {
      const item: Medium & { type?: string } = {
        mediumID: 'id',
        mimeType: 'image/png',
        title: 'Missing',
      };
      expect(getImageDisplaySource(item, '')).toEqual({
        src: undefined,
        isRgb: false,
      });
    });
  });

  describe('constants', () => {
    it('FALLBACK_IMAGE_BASE64 is a data URL', () => {
      expect(FALLBACK_IMAGE_BASE64).toMatch(/^data:image\/svg\+xml;base64,/);
    });

    it('TEXT_FILE_EXTENSIONS includes expected extensions', () => {
      expect(TEXT_FILE_EXTENSIONS).toContain('TXT');
      expect(TEXT_FILE_EXTENSIONS).toContain('HTML');
      expect(TEXT_FILE_EXTENSIONS).toContain('MD');
      expect(TEXT_FILE_EXTENSIONS).toContain('JSON');
    });

    it('IMAGE_MIME_TYPES includes image types', () => {
      expect(IMAGE_MIME_TYPES).toContain('image/jpeg');
      expect(IMAGE_MIME_TYPES).toContain('image/png');
      expect(IMAGE_MIME_TYPES).toContain('image/gif');
    });
  });
});
