/**
 * Storage Client Tests
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { StorageClient } from './storage';
import {
  S3Client,
  PutObjectCommand,
  GetObjectCommand,
  DeleteObjectCommand,
  HeadObjectCommand,
} from '@aws-sdk/client-s3';

vi.mock('@aws-sdk/client-s3');
vi.mock('@aws-sdk/s3-request-presigner');

describe('StorageClient', () => {
  let client: StorageClient;
  const config = {
    provider: 's3' as const,
    bucket: 'test-bucket',
    region: 'us-east-1',
    accessKeyId: 'test-access-key',
    secretAccessKey: 'test-secret-key',
  };

  beforeEach(() => {
    client = new StorageClient(config);
    vi.clearAllMocks();
  });

  describe('upload', () => {
    it('should upload object to S3', async () => {
      const object = {
        key: 'test-key',
        content: Buffer.from('test content'),
        contentType: 'text/plain',
      };

      const mockSend = vi.fn().mockResolvedValueOnce({});

      (S3Client as any).mockImplementation(() => ({
        send: mockSend,
      }));

      const result = await client.upload(object);

      expect(result).toBe(object.key);
      expect(mockSend).toHaveBeenCalledWith(
        expect.any(PutObjectCommand)
      );
    });

    it('should upload string content', async () => {
      const object = {
        key: 'test-key',
        content: 'test content',
      };

      const mockSend = vi.fn().mockResolvedValueOnce({});

      (S3Client as any).mockImplementation(() => ({
        send: mockSend,
      }));

      const result = await client.upload(object);

      expect(result).toBe(object.key);
    });
  });

  describe('download', () => {
    it('should download object from S3', async () => {
      const key = 'test-key';
      const content = Buffer.from('test content');

      const mockStream = {
        [Symbol.asyncIterator]: async function* () {
          yield content;
        },
      };

      const mockSend = vi.fn().mockResolvedValueOnce({
        Body: mockStream,
      });

      (S3Client as any).mockImplementation(() => ({
        send: mockSend,
      }));

      const result = await client.download(key);

      expect(result).toBeInstanceOf(Buffer);
      expect(mockSend).toHaveBeenCalledWith(
        expect.any(GetObjectCommand)
      );
    });

    it('should throw error if object not found', async () => {
      const key = 'non-existent-key';

      const mockSend = vi.fn().mockResolvedValueOnce({
        Body: undefined,
      });

      (S3Client as any).mockImplementation(() => ({
        send: mockSend,
      }));

      await expect(client.download(key)).rejects.toThrow(
        'Object non-existent-key not found or empty'
      );
    });
  });

  describe('delete', () => {
    it('should delete object from S3', async () => {
      const key = 'test-key';

      const mockSend = vi.fn().mockResolvedValueOnce({});

      (S3Client as any).mockImplementation(() => ({
        send: mockSend,
      }));

      await client.delete(key);

      expect(mockSend).toHaveBeenCalledWith(
        expect.any(DeleteObjectCommand)
      );
    });
  });

  describe('objectExists', () => {
    it('should return true if object exists', async () => {
      const key = 'test-key';

      const mockSend = vi.fn().mockResolvedValueOnce({});

      (S3Client as any).mockImplementation(() => ({
        send: mockSend,
      }));

      const result = await client.objectExists(key);

      expect(result).toBe(true);
      expect(mockSend).toHaveBeenCalledWith(
        expect.any(HeadObjectCommand)
      );
    });

    it('should return false if object does not exist', async () => {
      const key = 'non-existent-key';

      const mockSend = vi.fn().mockRejectedValueOnce({
        name: 'NotFound',
      });

      (S3Client as any).mockImplementation(() => ({
        send: mockSend,
      }));

      const result = await client.objectExists(key);

      expect(result).toBe(false);
    });
  });
});
