import { describe, it, expect, vi, beforeEach } from 'vitest';
import { AxiosError } from 'axios';
import {
  recordPlay,
  getTrackStats,
  getTopTracks,
  getPlaysOverTime,
  getUserStats,
  TrackServiceError as TrackUploadError,
  type TrackStats,
  type TopTrack,
  type PlayTimePoint,
  type UserStats,
} from './analyticsService';
import { TrackServiceError } from '../errors/trackErrors';
import { apiClient } from '@/services/api/client';

// Mock apiClient
vi.mock('@/services/api/client', () => ({
  apiClient: {
    post: vi.fn(),
    get: vi.fn(),
  },
}));

describe('Track Analytics Service', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('recordPlay', () => {
    it('should record a play successfully', async () => {
      const mockPost = vi.mocked(apiClient.post);
      mockPost.mockResolvedValue({ data: { message: 'play recorded' } });

      await recordPlay(1, 120, 'Chrome');

      expect(mockPost).toHaveBeenCalledWith('/tracks/1/play', {
        duration: 120,
        device: 'Chrome',
      });
    });

    it('should use navigator.userAgent if device is not provided', async () => {
      const mockPost = vi.mocked(apiClient.post);
      mockPost.mockResolvedValue({ data: { message: 'play recorded' } });

      // Mock navigator
      Object.defineProperty(global, 'navigator', {
        value: { userAgent: 'Mozilla/5.0' },
        writable: true,
      });

      await recordPlay(1, 60);

      expect(mockPost).toHaveBeenCalledWith('/tracks/1/play', {
        duration: 60,
        device: 'Mozilla/5.0',
      });
    });

    it('should throw TrackUploadError on 404', async () => {
      const mockPost = vi.mocked(apiClient.post);
      const error = new AxiosError('Not found');
      error.response = {
        status: 404,
        data: { error: 'Track introuvable' },
      } as any;
      mockPost.mockRejectedValue(error);

      await expect(recordPlay(999, 120)).rejects.toThrow(TrackUploadError);
      await expect(recordPlay(999, 120)).rejects.toThrow('Track introuvable');
    });

    it('should throw TrackUploadError on network error', async () => {
      const mockPost = vi.mocked(apiClient.post);
      const error = new AxiosError('Network error');
      error.code = 'ECONNABORTED';
      mockPost.mockRejectedValue(error);

      await expect(recordPlay(1, 120)).rejects.toThrow(TrackUploadError);
    });
  });

  describe('getTrackStats', () => {
    it('should get track stats successfully', async () => {
      const mockStats: TrackStats = {
        total_plays: 100,
        unique_listeners: 50,
        average_duration: 120.5,
        completion_rate: 75.0,
      };

      const mockGet = vi.mocked(apiClient.get);
      mockGet.mockResolvedValue({ data: { stats: mockStats } });

      const result = await getTrackStats(1);

      expect(mockGet).toHaveBeenCalledWith('/tracks/1/stats');
      expect(result).toEqual(mockStats);
    });

    it('should throw TrackUploadError on 404', async () => {
      const mockGet = vi.mocked(apiClient.get);
      const error = new AxiosError('Not found');
      error.response = {
        status: 404,
        data: { error: 'Track introuvable' },
      } as any;
      mockGet.mockRejectedValue(error);

      await expect(getTrackStats(999)).rejects.toThrow(TrackUploadError);
    });

    it('should throw TrackUploadError on server error', async () => {
      const mockGet = vi.mocked(apiClient.get);
      const error = new AxiosError('Server error');
      error.response = {
        status: 500,
        data: { error: 'Internal server error' },
      } as any;
      mockGet.mockRejectedValue(error);

      await expect(getTrackStats(1)).rejects.toThrow(TrackUploadError);
    });
  });

  describe('getTopTracks', () => {
    it('should get top tracks successfully', async () => {
      const mockTracks: TopTrack[] = [
        {
          track_id: 1,
          title: 'Track 1',
          artist: 'Artist 1',
          total_plays: 1000,
          unique_listeners: 500,
          average_duration: 180.0,
        },
        {
          track_id: 2,
          title: 'Track 2',
          artist: 'Artist 2',
          total_plays: 800,
          unique_listeners: 400,
          average_duration: 160.0,
        },
      ];

      const mockGet = vi.mocked(apiClient.get);
      mockGet.mockResolvedValue({ data: { tracks: mockTracks } });

      const result = await getTopTracks(10);

      expect(mockGet).toHaveBeenCalledWith('/analytics/top-tracks?limit=10');
      expect(result).toEqual(mockTracks);
    });

    it('should get top tracks with date filters', async () => {
      const mockTracks: TopTrack[] = [];
      const mockGet = vi.mocked(apiClient.get);
      mockGet.mockResolvedValue({ data: { tracks: mockTracks } });

      const startDate = '2024-01-01T00:00:00Z';
      const endDate = '2024-01-31T23:59:59Z';

      await getTopTracks(20, startDate, endDate);

      // URLSearchParams encode automatiquement les caractères spéciaux
      const params = new URLSearchParams({
        limit: '20',
        start_date: startDate,
        end_date: endDate,
      });
      expect(mockGet).toHaveBeenCalledWith(
        `/analytics/top-tracks?${params.toString()}`,
      );
    });

    it('should throw TrackUploadError on 400', async () => {
      const mockGet = vi.mocked(apiClient.get);
      const error = new AxiosError('Bad request');
      error.response = {
        status: 400,
        data: { error: 'Paramètres invalides' },
      } as any;
      mockGet.mockRejectedValue(error);

      await expect(getTopTracks(200)).rejects.toThrow(TrackUploadError);
    });
  });

  describe('getPlaysOverTime', () => {
    it('should get plays over time successfully', async () => {
      const mockPoints: PlayTimePoint[] = [
        { date: '2024-01-01', count: 10 },
        { date: '2024-01-02', count: 15 },
        { date: '2024-01-03', count: 20 },
      ];

      const mockGet = vi.mocked(apiClient.get);
      mockGet.mockResolvedValue({ data: { points: mockPoints } });

      const result = await getPlaysOverTime(1, undefined, undefined, 'day');

      expect(mockGet).toHaveBeenCalledWith(
        '/tracks/1/plays-over-time?interval=day',
      );
      expect(result).toEqual(mockPoints);
    });

    it('should get plays over time with date filters', async () => {
      const mockPoints: PlayTimePoint[] = [];
      const mockGet = vi.mocked(apiClient.get);
      mockGet.mockResolvedValue({ data: { points: mockPoints } });

      const startDate = '2024-01-01T00:00:00Z';
      const endDate = '2024-01-31T23:59:59Z';

      await getPlaysOverTime(1, startDate, endDate, 'week');

      // URLSearchParams encode automatiquement les caractères spéciaux
      const params = new URLSearchParams({
        start_date: startDate,
        end_date: endDate,
        interval: 'week',
      });
      expect(mockGet).toHaveBeenCalledWith(
        `/tracks/1/plays-over-time?${params.toString()}`,
      );
    });

    it('should throw TrackUploadError on 404', async () => {
      const mockGet = vi.mocked(apiClient.get);
      const error = new AxiosError('Not found');
      error.response = {
        status: 404,
        data: { error: 'Track introuvable' },
      } as any;
      mockGet.mockRejectedValue(error);

      await expect(getPlaysOverTime(999)).rejects.toThrow(TrackUploadError);
    });

    it('should throw TrackUploadError on 400', async () => {
      const mockGet = vi.mocked(apiClient.get);
      const error = new AxiosError('Bad request');
      error.response = {
        status: 400,
        data: { error: 'Paramètres invalides' },
      } as any;
      mockGet.mockRejectedValue(error);

      await expect(
        getPlaysOverTime(1, undefined, undefined, 'invalid' as any),
      ).rejects.toThrow(TrackUploadError);
    });
  });

  describe('getUserStats', () => {
    it('should get user stats successfully', async () => {
      const mockStats: UserStats = {
        total_plays: 500,
        unique_tracks: 100,
        total_duration: 36000,
        average_duration: 72.0,
      };

      const mockGet = vi.mocked(apiClient.get);
      mockGet.mockResolvedValue({ data: { stats: mockStats } });

      const result = await getUserStats(123);

      expect(mockGet).toHaveBeenCalledWith('/users/123/stats');
      expect(result).toEqual(mockStats);
    });

    it('should throw TrackUploadError on 401', async () => {
      const mockGet = vi.mocked(apiClient.get);
      const error = new AxiosError('Unauthorized');
      error.response = {
        status: 401,
        data: { error: 'Non autorisé' },
      } as any;
      mockGet.mockRejectedValue(error);

      await expect(getUserStats(123)).rejects.toThrow(TrackUploadError);
    });

    it('should throw TrackUploadError on 403', async () => {
      const mockGet = vi.mocked(apiClient.get);
      const error = new AxiosError('Forbidden');
      error.response = {
        status: 403,
        data: { error: 'Accès refusé' },
      } as any;
      mockGet.mockRejectedValue(error);

      await expect(getUserStats(456)).rejects.toThrow(TrackUploadError);
    });

    it('should throw TrackUploadError on 404', async () => {
      const mockGet = vi.mocked(apiClient.get);
      const error = new AxiosError('Not found');
      error.response = {
        status: 404,
        data: { error: 'Utilisateur introuvable' },
      } as any;
      mockGet.mockRejectedValue(error);

      await expect(getUserStats(999)).rejects.toThrow(TrackUploadError);
    });
  });
});
