import { describe, it, expect, vi, beforeEach } from 'vitest';
import { DashboardService } from './DashboardService';
import { AnalyticsApiClient } from '../../api/analytics/AnalyticsApiClient';
import { AnalyticsDashboardPresenter } from '../../presenters/AnalyticsDashboardPresenter';
import { AnalyticsMetricsPresenter } from '../../presenters/AnalyticsMetricsPresenter';
import type { AnalyticsDashboardDto, AnalyticsMetricsDto } from '../../dtos';
import { AnalyticsDashboardViewModel, AnalyticsMetricsViewModel } from '../../view-models';

describe('DashboardService', () => {
  let mockApiClient: AnalyticsApiClient;
  let mockDashboardPresenter: AnalyticsDashboardPresenter;
  let mockMetricsPresenter: AnalyticsMetricsPresenter;
  let service: DashboardService;

  beforeEach(() => {
    mockApiClient = {
      recordPageView: vi.fn(),
      recordEngagement: vi.fn(),
      getDashboardData: vi.fn(),
      getAnalyticsMetrics: vi.fn(),
    } as unknown as AnalyticsApiClient;

    mockDashboardPresenter = {
      present: vi.fn(),
    } as unknown as AnalyticsDashboardPresenter;

    mockMetricsPresenter = {
      present: vi.fn(),
    } as unknown as AnalyticsMetricsPresenter;

    service = new DashboardService(mockApiClient, mockDashboardPresenter, mockMetricsPresenter);
  });

  describe('getDashboardData', () => {
    it('should fetch dashboard data from API and transform via presenter', async () => {
      // Arrange
      const mockDto: AnalyticsDashboardDto = {
        totalUsers: 1000,
        activeUsers: 750,
        totalRaces: 50,
        totalLeagues: 25,
      };

      const mockViewModel: AnalyticsDashboardViewModel = new AnalyticsDashboardViewModel(mockDto);

      vi.mocked(mockApiClient.getDashboardData).mockResolvedValue(mockDto);
      vi.mocked(mockDashboardPresenter.present).mockReturnValue(mockViewModel);

      // Act
      const result = await service.getDashboardData();

      // Assert
      expect(mockApiClient.getDashboardData).toHaveBeenCalledTimes(1);
      expect(mockDashboardPresenter.present).toHaveBeenCalledWith(mockDto);
      expect(mockDashboardPresenter.present).toHaveBeenCalledTimes(1);
      expect(result).toBe(mockViewModel);
    });

    it('should propagate API client errors', async () => {
      // Arrange
      const error = new Error('API Error: Failed to fetch dashboard data');
      vi.mocked(mockApiClient.getDashboardData).mockRejectedValue(error);

      // Act & Assert
      await expect(service.getDashboardData()).rejects.toThrow(
        'API Error: Failed to fetch dashboard data'
      );

      expect(mockApiClient.getDashboardData).toHaveBeenCalledTimes(1);
      expect(mockDashboardPresenter.present).not.toHaveBeenCalled();
    });

    it('should propagate presenter errors', async () => {
      // Arrange
      const mockDto: AnalyticsDashboardDto = {
        totalUsers: 500,
        activeUsers: 300,
        totalRaces: 20,
        totalLeagues: 10,
      };

      const error = new Error('Presenter Error: Invalid DTO structure');
      vi.mocked(mockApiClient.getDashboardData).mockResolvedValue(mockDto);
      vi.mocked(mockDashboardPresenter.present).mockImplementation(() => {
        throw error;
      });

      // Act & Assert
      await expect(service.getDashboardData()).rejects.toThrow(
        'Presenter Error: Invalid DTO structure'
      );

      expect(mockApiClient.getDashboardData).toHaveBeenCalledTimes(1);
      expect(mockDashboardPresenter.present).toHaveBeenCalledWith(mockDto);
    });
  });

  describe('getAnalyticsMetrics', () => {
    it('should fetch analytics metrics from API and transform via presenter', async () => {
      // Arrange
      const mockDto: AnalyticsMetricsDto = {
        pageViews: 5000,
        uniqueVisitors: 1200,
        averageSessionDuration: 180,
        bounceRate: 35.5,
      };

      const mockViewModel: AnalyticsMetricsViewModel = new AnalyticsMetricsViewModel(mockDto);

      vi.mocked(mockApiClient.getAnalyticsMetrics).mockResolvedValue(mockDto);
      vi.mocked(mockMetricsPresenter.present).mockReturnValue(mockViewModel);

      // Act
      const result = await service.getAnalyticsMetrics();

      // Assert
      expect(mockApiClient.getAnalyticsMetrics).toHaveBeenCalledTimes(1);
      expect(mockMetricsPresenter.present).toHaveBeenCalledWith(mockDto);
      expect(mockMetricsPresenter.present).toHaveBeenCalledTimes(1);
      expect(result).toBe(mockViewModel);
    });

    it('should propagate API client errors', async () => {
      // Arrange
      const error = new Error('API Error: Failed to fetch analytics metrics');
      vi.mocked(mockApiClient.getAnalyticsMetrics).mockRejectedValue(error);

      // Act & Assert
      await expect(service.getAnalyticsMetrics()).rejects.toThrow(
        'API Error: Failed to fetch analytics metrics'
      );

      expect(mockApiClient.getAnalyticsMetrics).toHaveBeenCalledTimes(1);
      expect(mockMetricsPresenter.present).not.toHaveBeenCalled();
    });

    it('should propagate presenter errors', async () => {
      // Arrange
      const mockDto: AnalyticsMetricsDto = {
        pageViews: 2500,
        uniqueVisitors: 600,
        averageSessionDuration: 120,
        bounceRate: 45.2,
      };

      const error = new Error('Presenter Error: Invalid metrics data');
      vi.mocked(mockApiClient.getAnalyticsMetrics).mockResolvedValue(mockDto);
      vi.mocked(mockMetricsPresenter.present).mockImplementation(() => {
        throw error;
      });

      // Act & Assert
      await expect(service.getAnalyticsMetrics()).rejects.toThrow(
        'Presenter Error: Invalid metrics data'
      );

      expect(mockApiClient.getAnalyticsMetrics).toHaveBeenCalledTimes(1);
      expect(mockMetricsPresenter.present).toHaveBeenCalledWith(mockDto);
    });
  });

  describe('getDashboardOverview', () => {
    it('should delegate to getDashboardData for backward compatibility', async () => {
      // Arrange
      const mockDto: AnalyticsDashboardDto = {
        totalUsers: 800,
        activeUsers: 600,
        totalRaces: 40,
        totalLeagues: 20,
      };

      const mockViewModel: AnalyticsDashboardViewModel = new AnalyticsDashboardViewModel(mockDto);

      vi.mocked(mockApiClient.getDashboardData).mockResolvedValue(mockDto);
      vi.mocked(mockDashboardPresenter.present).mockReturnValue(mockViewModel);

      // Act
      const result = await service.getDashboardOverview();

      // Assert
      expect(mockApiClient.getDashboardData).toHaveBeenCalledTimes(1);
      expect(mockDashboardPresenter.present).toHaveBeenCalledWith(mockDto);
      expect(result).toBe(mockViewModel);
    });
  });

  describe('Constructor Dependency Injection', () => {
    it('should require apiClient, dashboardPresenter, and metricsPresenter', () => {
      // This test verifies the constructor signature
      expect(() => {
        new DashboardService(mockApiClient, mockDashboardPresenter, mockMetricsPresenter);
      }).not.toThrow();
    });

    it('should use injected dependencies', async () => {
      // Arrange
      const customApiClient = {
        recordPageView: vi.fn(),
        recordEngagement: vi.fn(),
        getDashboardData: vi.fn().mockResolvedValue({
          totalUsers: 100,
          activeUsers: 80,
          totalRaces: 5,
          totalLeagues: 3,
        }),
        getAnalyticsMetrics: vi.fn(),
      } as unknown as AnalyticsApiClient;

      const customDashboardPresenter = {
        present: vi.fn().mockReturnValue({} as AnalyticsDashboardViewModel),
      } as unknown as AnalyticsDashboardPresenter;

      const customMetricsPresenter = {
        present: vi.fn(),
      } as unknown as AnalyticsMetricsPresenter;

      const customService = new DashboardService(customApiClient, customDashboardPresenter, customMetricsPresenter);

      // Act
      await customService.getDashboardData();

      // Assert
      expect(customApiClient.getDashboardData).toHaveBeenCalledTimes(1);
      expect(customDashboardPresenter.present).toHaveBeenCalled();
    });
  });
});