import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { downloadTrack, TrackDownloadError } from './trackDownloadService';
import { apiClient } from '@/services/api/client';

// Mock apiClient
vi.mock('@/services/api/client', () => ({
  apiClient: {
    defaults: {
      baseURL: 'http://localhost:8080/api/v1',
    },
  },
}));

// Mock localStorage
const localStorageMock = {
  getItem: vi.fn(),
  setItem: vi.fn(),
  removeItem: vi.fn(),
  clear: vi.fn(),
};

Object.defineProperty(window, 'localStorage', {
  value: localStorageMock,
});

// Mock fetch
global.fetch = vi.fn();

describe('trackDownloadService', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    localStorageMock.getItem.mockReturnValue('test-token');
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('downloadTrack', () => {
    it('should download track successfully', async () => {
      const mockBlob = new Blob(['fake audio content'], { type: 'audio/mpeg' });
      const mockResponse = {
        ok: true,
        headers: new Headers({
          'content-type': 'audio/mpeg',
          'content-disposition': 'attachment; filename="test-track.mp3"',
        }),
        blob: vi.fn().mockResolvedValue(mockBlob),
        body: null,
      };

      vi.mocked(fetch).mockResolvedValue(mockResponse as any);

      // Mock createObjectURL and revokeObjectURL
      global.URL.createObjectURL = vi.fn(() => 'blob:http://localhost/test');
      global.URL.revokeObjectURL = vi.fn();

      // Mock document.createElement and click
      const mockLink = {
        href: '',
        download: '',
        click: vi.fn(),
      };
      const createElementSpy = vi
        .spyOn(document, 'createElement')
        .mockReturnValue(mockLink as any);
      const appendChildSpy = vi
        .spyOn(document.body, 'appendChild')
        .mockImplementation(() => mockLink as any);
      const removeChildSpy = vi
        .spyOn(document.body, 'removeChild')
        .mockImplementation(() => mockLink as any);

      await downloadTrack(123);

      expect(fetch).toHaveBeenCalledWith(
        'http://localhost:8080/api/v1/tracks/123/download',
        expect.objectContaining({
          method: 'GET',
          headers: expect.objectContaining({
            Authorization: 'Bearer test-token',
          }),
        }),
      );
      expect(mockResponse.blob).toHaveBeenCalled();
      expect(createElementSpy).toHaveBeenCalledWith('a');
      expect(mockLink.click).toHaveBeenCalled();
      expect(appendChildSpy).toHaveBeenCalled();
      expect(removeChildSpy).toHaveBeenCalled();
    });

    it('should download track with share token', async () => {
      const mockBlob = new Blob(['fake audio content'], { type: 'audio/mpeg' });
      const mockResponse = {
        ok: true,
        headers: new Headers({
          'content-type': 'audio/mpeg',
          'content-disposition': 'attachment; filename="test-track.mp3"',
        }),
        blob: vi.fn().mockResolvedValue(mockBlob),
        body: null,
      };

      vi.mocked(fetch).mockResolvedValue(mockResponse as any);

      global.URL.createObjectURL = vi.fn(() => 'blob:http://localhost/test');
      global.URL.revokeObjectURL = vi.fn();

      const mockLink = {
        href: '',
        download: '',
        click: vi.fn(),
      };
      vi.spyOn(document, 'createElement').mockReturnValue(mockLink as any);
      vi.spyOn(document.body, 'appendChild').mockImplementation(
        () => mockLink as any,
      );
      vi.spyOn(document.body, 'removeChild').mockImplementation(
        () => mockLink as any,
      );

      await downloadTrack(123, { shareToken: 'test-share-token' });

      expect(fetch).toHaveBeenCalledWith(
        'http://localhost:8080/api/v1/tracks/123/download?share_token=test-share-token',
        expect.any(Object),
      );
    });

    it('should download track with custom filename', async () => {
      const mockBlob = new Blob(['fake audio content'], { type: 'audio/mpeg' });
      const mockResponse = {
        ok: true,
        headers: new Headers({
          'content-type': 'audio/mpeg',
        }),
        blob: vi.fn().mockResolvedValue(mockBlob),
        body: null,
      };

      vi.mocked(fetch).mockResolvedValue(mockResponse as any);

      global.URL.createObjectURL = vi.fn(() => 'blob:http://localhost/test');
      global.URL.revokeObjectURL = vi.fn();

      const mockLink = {
        href: '',
        download: '',
        click: vi.fn(),
      };
      vi.spyOn(document, 'createElement').mockReturnValue(mockLink as any);
      vi.spyOn(document.body, 'appendChild').mockImplementation(
        () => mockLink as any,
      );
      vi.spyOn(document.body, 'removeChild').mockImplementation(
        () => mockLink as any,
      );

      await downloadTrack(123, { filename: 'custom-name.mp3' });

      expect(mockLink.download).toBe('custom-name.mp3');
    });

    it('should handle 403 forbidden error', async () => {
      const mockResponse = {
        ok: false,
        status: 403,
        json: vi.fn().mockResolvedValue({ error: 'forbidden' }),
      };

      vi.mocked(fetch).mockResolvedValue(mockResponse as any);

      await expect(downloadTrack(123)).rejects.toThrow(TrackDownloadError);
      await expect(downloadTrack(123)).rejects.toThrow('Accès refusé');
    });

    it('should handle 404 not found error', async () => {
      const mockResponse = {
        ok: false,
        status: 404,
        json: vi.fn().mockResolvedValue({ error: 'track not found' }),
      };

      vi.mocked(fetch).mockResolvedValue(mockResponse as any);

      await expect(downloadTrack(999)).rejects.toThrow(TrackDownloadError);
      await expect(downloadTrack(999)).rejects.toThrow('Track introuvable');
    });

    it('should handle network errors', async () => {
      vi.mocked(fetch).mockRejectedValue(new TypeError('Network error'));

      await expect(downloadTrack(123)).rejects.toThrow(TrackDownloadError);
      await expect(downloadTrack(123)).rejects.toThrow('Erreur réseau');
    });

    it('should download with progress tracking', async () => {
      const chunks = [new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6])];
      let chunkIndex = 0;

      const mockReader = {
        read: vi.fn().mockImplementation(() => {
          if (chunkIndex < chunks.length) {
            return Promise.resolve({
              done: false,
              value: chunks[chunkIndex++],
            });
          }
          return Promise.resolve({ done: true, value: undefined });
        }),
      };

      const mockResponse = {
        ok: true,
        headers: new Headers({
          'content-type': 'audio/mpeg',
          'content-length': '6',
          'content-disposition': 'attachment; filename="test.mp3"',
        }),
        body: {
          getReader: vi.fn().mockReturnValue(mockReader),
        },
      };

      vi.mocked(fetch).mockResolvedValue(mockResponse as any);

      global.URL.createObjectURL = vi.fn(() => 'blob:http://localhost/test');
      global.URL.revokeObjectURL = vi.fn();

      const mockLink = {
        href: '',
        download: '',
        click: vi.fn(),
      };
      vi.spyOn(document, 'createElement').mockReturnValue(mockLink as any);
      vi.spyOn(document.body, 'appendChild').mockImplementation(
        () => mockLink as any,
      );
      vi.spyOn(document.body, 'removeChild').mockImplementation(
        () => mockLink as any,
      );

      const progressCallback = vi.fn();

      await downloadTrack(123, { onProgress: progressCallback });

      // Vérifier que le callback de progression a été appelé
      expect(progressCallback).toHaveBeenCalled();
    });

    it('should extract filename from content-disposition header', async () => {
      const mockBlob = new Blob(['fake audio content'], { type: 'audio/mpeg' });
      const mockResponse = {
        ok: true,
        headers: new Headers({
          'content-type': 'audio/mpeg',
          'content-disposition': 'attachment; filename="My Track.mp3"',
        }),
        blob: vi.fn().mockResolvedValue(mockBlob),
        body: null,
      };

      vi.mocked(fetch).mockResolvedValue(mockResponse as any);

      global.URL.createObjectURL = vi.fn(() => 'blob:http://localhost/test');
      global.URL.revokeObjectURL = vi.fn();

      const mockLink = {
        href: '',
        download: '',
        click: vi.fn(),
      };
      vi.spyOn(document, 'createElement').mockReturnValue(mockLink as any);
      vi.spyOn(document.body, 'appendChild').mockImplementation(
        () => mockLink as any,
      );
      vi.spyOn(document.body, 'removeChild').mockImplementation(
        () => mockLink as any,
      );

      await downloadTrack(123);

      expect(mockLink.download).toBe('My Track.mp3');
    });

    it('should use default filename if not in headers', async () => {
      const mockBlob = new Blob(['fake audio content'], { type: 'audio/mpeg' });
      const mockResponse = {
        ok: true,
        headers: new Headers({
          'content-type': 'audio/mpeg',
        }),
        blob: vi.fn().mockResolvedValue(mockBlob),
        body: null,
      };

      vi.mocked(fetch).mockResolvedValue(mockResponse as any);

      global.URL.createObjectURL = vi.fn(() => 'blob:http://localhost/test');
      global.URL.revokeObjectURL = vi.fn();

      const mockLink = {
        href: '',
        download: '',
        click: vi.fn(),
      };
      vi.spyOn(document, 'createElement').mockReturnValue(mockLink as any);
      vi.spyOn(document.body, 'appendChild').mockImplementation(
        () => mockLink as any,
      );
      vi.spyOn(document.body, 'removeChild').mockImplementation(
        () => mockLink as any,
      );

      await downloadTrack(123);

      expect(mockLink.download).toBe('track');
    });
  });
});
