// src/utils/checksum.test.ts
/**
 * @vitest-environment jsdom
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { generateFileChecksum } from './checksum';
import { logger } from '../services/logger.client';

describe('generateFileChecksum', () => {
  it('should generate the correct SHA-256 checksum for a simple text file', async () => {
    // Arrange
    const fileContent = 'Hello, world!';
    const file = new File([fileContent], 'hello.txt', { type: 'text/plain' });
    // The known SHA-256 hash for "Hello, world!"
    const expectedChecksum = '315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3';

    // Act
    const checksum = await generateFileChecksum(file);

    // Assert
    expect(checksum).toBe(expectedChecksum);
  });

  it('should generate the correct SHA-256 checksum for an empty file', async () => {
    // Arrange
    const file = new File([], 'empty.txt', { type: 'text/plain' });
    // The known SHA-256 hash for an empty input
    const expectedChecksum = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';

    // Act
    const checksum = await generateFileChecksum(file);

    // Assert
    expect(checksum).toBe(expectedChecksum);
  });

  it('should generate a consistent checksum for a file with binary data', async () => {
    // Arrange: Create a file with some arbitrary binary data
    const binaryData = new Uint8Array([0, 1, 2, 3, 255, 254, 128]);
    const file = new File([binaryData], 'data.bin', { type: 'application/octet-stream' });
    // SHA-256 hash for [0, 1, 2, 3, 255, 254, 128]
    const expectedChecksum = '654b06382635b60c931f187f4c16fc222a6a276dec429a56bfb5fe3b313de1c4';

    // Act
    const checksum = await generateFileChecksum(file);

    // Assert
    expect(checksum).toBe(expectedChecksum);
  });

  describe('with fallback mechanisms', () => {
    beforeEach(() => {
      // Clear mock history before each test to ensure isolation
      vi.clearAllMocks();
      // Mock logger.warn to prevent logs from appearing in test output
      vi.spyOn(logger, 'warn').mockImplementation(() => {});
    });

    afterEach(() => {
      // Restore any mocked globals
      vi.restoreAllMocks();
    });

    it('should use FileReader fallback if file.arrayBuffer is not a function', async () => {
      const fileContent = 'fallback test';
      const file = new File([new TextEncoder().encode(fileContent)], 'test.txt', {
        type: 'text/plain',
      });
      Object.defineProperty(file, 'arrayBuffer', { value: undefined });

      const checksum = await generateFileChecksum(file);
      // Correct SHA-256 for "fallback test" (without a trailing newline).
      expect(checksum).toBe('c4c388111e0b74ec6f9b609d240d1f2e5df5270999118b442d6db6ce3cac6cce');
      expect(logger.warn).toHaveBeenCalledWith(
        expect.stringContaining('file.arrayBuffer is not a function'),
      );
    });

    it('should use FileReader fallback if file.arrayBuffer throws an error', async () => {
      const fileContent = 'error fallback';
      const file = new File([new TextEncoder().encode(fileContent)], 'test.txt', {
        type: 'text/plain',
      });
      vi.spyOn(file, 'arrayBuffer').mockRejectedValue(new Error('Simulated error'));

      const checksum = await generateFileChecksum(file);
      // Correct SHA-256 for "error fallback" (without a trailing newline).
      expect(checksum).toBe('786aaab58cd94f0d74de6895575dcfa7ace2733c9713121e1fac197eb02ec1c2');
      expect(logger.warn).toHaveBeenCalledWith(
        expect.stringContaining('file.arrayBuffer() threw an error'),
        expect.any(Object),
      );
    });

    it('should throw an error if crypto.subtle is not available', async () => {
      const file = new File(['test'], 'test.txt', { type: 'text/plain' });

      vi.stubGlobal('crypto', { ...global.crypto, subtle: undefined });

      await expect(generateFileChecksum(file)).rejects.toThrow();
    });

    it('should throw an error if FileReader fails', async () => {
      const file = new File(['test'], 'test.txt', { type: 'text/plain' });
      // Force FileReader path
      Object.defineProperty(file, 'arrayBuffer', { value: undefined });

      // Mock FileReader to simulate an error
      interface MockFileReader {
        readAsArrayBuffer: () => void;
        onerror: (() => void) | null;
        error: { message: string };
      }
      const MockErrorReader = vi.fn(function (this: MockFileReader) {
        this.readAsArrayBuffer = () => {
          if (this.onerror) {
            this.onerror();
          }
        };
        this.error = { message: 'Simulated read error' };
      });
      vi.stubGlobal('FileReader', MockErrorReader);

      await expect(generateFileChecksum(file)).rejects.toThrow(
        'FileReader error: Simulated read error',
      );
    });

    it('should throw an error if FileReader result is not an ArrayBuffer', async () => {
      const file = new File(['test'], 'test.txt', { type: 'text/plain' });
      // Force FileReader path
      Object.defineProperty(file, 'arrayBuffer', { value: undefined });

      // Mock FileReader to return a string
      interface MockFileReader {
        readAsArrayBuffer: () => void;
        onload: (() => void) | null;
        result: string | ArrayBuffer;
      }
      const MockBadResultReader = vi.fn(function (this: MockFileReader) {
        this.result = 'this is not an array buffer';
        this.readAsArrayBuffer = () => {
          if (this.onload) this.onload();
        };
      });
      vi.stubGlobal('FileReader', MockBadResultReader);

      await expect(generateFileChecksum(file)).rejects.toThrow(
        'FileReader result was not an ArrayBuffer',
      );
    });
  });
});
