import { describe, it, expect, vi, beforeEach } from 'vitest';
import { DashboardService } from '@/lib/services/analytics/DashboardService';
import { DashboardApiClient } from '@/lib/api/dashboard/DashboardApiClient';
import { AnalyticsApiClient } from '@/lib/api/analytics/AnalyticsApiClient';
import { ApiError } from '@/lib/api/base/ApiError';
import type { DashboardOverviewDTO } from '@/lib/types/generated/DashboardOverviewDTO';
import type { GetAnalyticsMetricsOutputDTO } from '@/lib/types/generated/GetAnalyticsMetricsOutputDTO';

// Mock the API clients
vi.mock('@/lib/api/dashboard/DashboardApiClient');
vi.mock('@/lib/api/analytics/AnalyticsApiClient');

describe('DashboardService', () => {
  let service: DashboardService;
  let mockDashboardApiClient: any;
  let mockAnalyticsApiClient: any;

  beforeEach(() => {
    vi.clearAllMocks();
    service = new DashboardService();
    mockDashboardApiClient = (service as any).apiClient;
    mockAnalyticsApiClient = (service as any).analyticsApiClient;
  });

  describe('happy paths', () => {
    it('should successfully fetch dashboard overview', async () => {
      const mockOverview: DashboardOverviewDTO = {
        currentDriver: {
          id: 'driver-123',
          name: 'Test Driver',
          avatarUrl: 'https://example.com/avatar.jpg',
          rating: 1500,
          rank: 10,
        },
        myUpcomingRaces: [
          {
            id: 'race-1',
            name: 'Race 1',
            date: '2024-01-15T10:00:00.000Z',
            track: 'Track 1',
            league: 'League 1',
          },
        ],
        otherUpcomingRaces: [
          {
            id: 'race-2',
            name: 'Race 2',
            date: '2024-01-16T10:00:00.000Z',
            track: 'Track 2',
            league: 'League 2',
          },
        ],
        upcomingRaces: [
          {
            id: 'race-1',
            name: 'Race 1',
            date: '2024-01-15T10:00:00.000Z',
            track: 'Track 1',
            league: 'League 1',
          },
          {
            id: 'race-2',
            name: 'Race 2',
            date: '2024-01-16T10:00:00.000Z',
            track: 'Track 2',
            league: 'League 2',
          },
        ],
        activeLeaguesCount: 3,
        nextRace: {
          id: 'race-1',
          name: 'Race 1',
          date: '2024-01-15T10:00:00.000Z',
          track: 'Track 1',
          league: 'League 1',
        },
        recentResults: [
          {
            raceId: 'race-0',
            position: 5,
            points: 15,
            date: '2024-01-10T10:00:00.000Z',
          },
        ],
        leagueStandingsSummaries: [
          {
            leagueId: 'league-1',
            leagueName: 'League 1',
            position: 3,
            points: 150,
          },
        ],
        feedSummary: {
          unreadCount: 5,
          latestPosts: [
            {
              id: 'post-1',
              title: 'New Season Announcement',
              date: '2024-01-14T10:00:00.000Z',
            },
          ],
        },
        friends: [
          {
            id: 'friend-1',
            name: 'Friend 1',
            avatarUrl: 'https://example.com/friend1.jpg',
            status: 'online',
          },
        ],
      };

      mockDashboardApiClient.getDashboardOverview.mockResolvedValue(mockOverview);

      const result = await service.getDashboardOverview();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toEqual(mockOverview);
      expect(mockDashboardApiClient.getDashboardOverview).toHaveBeenCalledTimes(1);
    });

    it('should successfully fetch analytics metrics', async () => {
      const mockMetrics: GetAnalyticsMetricsOutputDTO = {
        pageViews: 15000,
        uniqueVisitors: 8500,
        averageSessionDuration: 180,
        bounceRate: 0.35,
      };

      mockAnalyticsApiClient.getAnalyticsMetrics.mockResolvedValue(mockMetrics);

      const result = await service.getAnalyticsMetrics();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toEqual(mockMetrics);
      expect(mockAnalyticsApiClient.getAnalyticsMetrics).toHaveBeenCalledTimes(1);
    });
  });

  describe('failure modes', () => {
    it('should handle not found errors', async () => {
      const error = new ApiError(
        'Dashboard not found',
        'NOT_FOUND',
        {
          endpoint: '/dashboard/overview',
          method: 'GET',
          timestamp: new Date().toISOString(),
          statusCode: 404,
        }
      );

      mockDashboardApiClient.getDashboardOverview.mockRejectedValue(error);

      const result = await service.getDashboardOverview();

      expect(result.isErr()).toBe(true);
      expect(result.getError()).toEqual({
        type: 'notFound',
        message: 'Dashboard not found',
      });
    });

    it('should handle unauthorized errors', async () => {
      const error = new ApiError(
        'Unauthorized access',
        'AUTH_ERROR',
        {
          endpoint: '/dashboard/overview',
          method: 'GET',
          timestamp: new Date().toISOString(),
          statusCode: 401,
        }
      );

      mockDashboardApiClient.getDashboardOverview.mockRejectedValue(error);

      const result = await service.getDashboardOverview();

      expect(result.isErr()).toBe(true);
      expect(result.getError()).toEqual({
        type: 'unauthorized',
        message: 'Unauthorized access',
      });
    });

    it('should handle server errors', async () => {
      const error = new ApiError(
        'Internal server error',
        'SERVER_ERROR',
        {
          endpoint: '/dashboard/overview',
          method: 'GET',
          timestamp: new Date().toISOString(),
          statusCode: 500,
        }
      );

      mockDashboardApiClient.getDashboardOverview.mockRejectedValue(error);

      const result = await service.getDashboardOverview();

      expect(result.isErr()).toBe(true);
      expect(result.getError()).toEqual({
        type: 'serverError',
        message: 'Internal server error',
      });
    });

    it('should handle network errors', async () => {
      const error = new ApiError(
        'Network error: Unable to reach the API server',
        'NETWORK_ERROR',
        {
          endpoint: '/dashboard/overview',
          method: 'GET',
          timestamp: new Date().toISOString(),
        }
      );

      mockDashboardApiClient.getDashboardOverview.mockRejectedValue(error);

      const result = await service.getDashboardOverview();

      expect(result.isErr()).toBe(true);
      expect(result.getError()).toEqual({
        type: 'networkError',
        message: 'Network error: Unable to reach the API server',
      });
    });

    it('should handle timeout errors', async () => {
      const error = new ApiError(
        'Request timed out after 30 seconds',
        'TIMEOUT_ERROR',
        {
          endpoint: '/dashboard/overview',
          method: 'GET',
          timestamp: new Date().toISOString(),
        }
      );

      mockDashboardApiClient.getDashboardOverview.mockRejectedValue(error);

      const result = await service.getDashboardOverview();

      expect(result.isErr()).toBe(true);
      expect(result.getError()).toEqual({
        type: 'networkError',
        message: 'Request timed out after 30 seconds',
      });
    });

    it('should handle unknown errors', async () => {
      const error = new Error('Something went wrong');

      mockDashboardApiClient.getDashboardOverview.mockRejectedValue(error);

      const result = await service.getDashboardOverview();

      expect(result.isErr()).toBe(true);
      expect(result.getError()).toEqual({
        type: 'unknown',
        message: 'Something went wrong',
      });
    });
  });

  describe('retries', () => {
    it('should retry on network failure', async () => {
      const mockOverview: DashboardOverviewDTO = {
        currentDriver: undefined,
        myUpcomingRaces: [],
        otherUpcomingRaces: [],
        upcomingRaces: [],
        activeLeaguesCount: 0,
        recentResults: [],
        leagueStandingsSummaries: [],
        feedSummary: { unreadCount: 0, latestPosts: [] },
        friends: [],
      };

      const error = new ApiError(
        'Network error',
        'NETWORK_ERROR',
        {
          endpoint: '/dashboard/overview',
          method: 'GET',
          timestamp: new Date().toISOString(),
        }
      );

      // First call fails, second succeeds
      mockDashboardApiClient.getDashboardOverview
        .mockRejectedValueOnce(error)
        .mockResolvedValueOnce(mockOverview);

      const result = await service.getDashboardOverview();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toEqual(mockOverview);
      expect(mockDashboardApiClient.getDashboardOverview).toHaveBeenCalledTimes(2);
    });

    it('should retry on timeout', async () => {
      const mockOverview: DashboardOverviewDTO = {
        currentDriver: undefined,
        myUpcomingRaces: [],
        otherUpcomingRaces: [],
        upcomingRaces: [],
        activeLeaguesCount: 0,
        recentResults: [],
        leagueStandingsSummaries: [],
        feedSummary: { unreadCount: 0, latestPosts: [] },
        friends: [],
      };

      const error = new ApiError(
        'Request timed out after 30 seconds',
        'TIMEOUT_ERROR',
        {
          endpoint: '/dashboard/overview',
          method: 'GET',
          timestamp: new Date().toISOString(),
        }
      );

      // First call times out, second succeeds
      mockDashboardApiClient.getDashboardOverview
        .mockRejectedValueOnce(error)
        .mockResolvedValueOnce(mockOverview);

      const result = await service.getDashboardOverview();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toEqual(mockOverview);
      expect(mockDashboardApiClient.getDashboardOverview).toHaveBeenCalledTimes(2);
    });
  });

  describe('fallback logic', () => {
    it('should use fallback when primary API fails', async () => {
      const error = new ApiError(
        'Unable to connect to server',
        'NETWORK_ERROR',
        {
          endpoint: '/dashboard/overview',
          method: 'GET',
          timestamp: new Date().toISOString(),
        }
      );

      mockDashboardApiClient.getDashboardOverview.mockRejectedValue(error);

      const result = await service.getDashboardOverview();

      // The service should return an error result, not fallback data
      expect(result.isErr()).toBe(true);
      expect(result.getError()).toEqual({
        type: 'networkError',
        message: 'Unable to connect to server',
      });
    });

    it('should handle partial data gracefully', async () => {
      const mockOverview: DashboardOverviewDTO = {
        currentDriver: undefined, // Missing driver data
        myUpcomingRaces: [],
        otherUpcomingRaces: [],
        upcomingRaces: [],
        activeLeaguesCount: 0,
        recentResults: [],
        leagueStandingsSummaries: [],
        feedSummary: { unreadCount: 0, latestPosts: [] },
        friends: [],
      };

      mockDashboardApiClient.getDashboardOverview.mockResolvedValue(mockOverview);

      const result = await service.getDashboardOverview();

      expect(result.isOk()).toBe(true);
      const overview = result.unwrap();
      expect(overview.currentDriver).toBeUndefined();
      expect(overview.myUpcomingRaces).toHaveLength(0);
      expect(overview.activeLeaguesCount).toBe(0);
    });
  });

  describe('aggregation logic', () => {
    it('should aggregate dashboard data correctly', async () => {
      const mockOverview: DashboardOverviewDTO = {
        currentDriver: {
          id: 'driver-123',
          name: 'Test Driver',
          avatarUrl: 'https://example.com/avatar.jpg',
          rating: 1500,
          rank: 10,
        },
        myUpcomingRaces: [
          {
            id: 'race-1',
            name: 'Race 1',
            date: '2024-01-15T10:00:00.000Z',
            track: 'Track 1',
            league: 'League 1',
          },
          {
            id: 'race-2',
            name: 'Race 2',
            date: '2024-01-16T10:00:00.000Z',
            track: 'Track 2',
            league: 'League 2',
          },
        ],
        otherUpcomingRaces: [
          {
            id: 'race-3',
            name: 'Race 3',
            date: '2024-01-17T10:00:00.000Z',
            track: 'Track 3',
            league: 'League 3',
          },
        ],
        upcomingRaces: [
          {
            id: 'race-1',
            name: 'Race 1',
            date: '2024-01-15T10:00:00.000Z',
            track: 'Track 1',
            league: 'League 1',
          },
          {
            id: 'race-2',
            name: 'Race 2',
            date: '2024-01-16T10:00:00.000Z',
            track: 'Track 2',
            league: 'League 2',
          },
          {
            id: 'race-3',
            name: 'Race 3',
            date: '2024-01-17T10:00:00.000Z',
            track: 'Track 3',
            league: 'League 3',
          },
        ],
        activeLeaguesCount: 3,
        nextRace: {
          id: 'race-1',
          name: 'Race 1',
          date: '2024-01-15T10:00:00.000Z',
          track: 'Track 1',
          league: 'League 1',
        },
        recentResults: [
          {
            raceId: 'race-0',
            position: 5,
            points: 15,
            date: '2024-01-10T10:00:00.000Z',
          },
          {
            raceId: 'race--1',
            position: 3,
            points: 20,
            date: '2024-01-09T10:00:00.000Z',
          },
        ],
        leagueStandingsSummaries: [
          {
            leagueId: 'league-1',
            leagueName: 'League 1',
            position: 3,
            points: 150,
          },
          {
            leagueId: 'league-2',
            leagueName: 'League 2',
            position: 1,
            points: 200,
          },
        ],
        feedSummary: {
          unreadCount: 5,
          latestPosts: [
            {
              id: 'post-1',
              title: 'New Season Announcement',
              date: '2024-01-14T10:00:00.000Z',
            },
            {
              id: 'post-2',
              title: 'Race Results Published',
              date: '2024-01-13T10:00:00.000Z',
            },
          ],
        },
        friends: [
          {
            id: 'friend-1',
            name: 'Friend 1',
            avatarUrl: 'https://example.com/friend1.jpg',
            status: 'online',
          },
          {
            id: 'friend-2',
            name: 'Friend 2',
            avatarUrl: 'https://example.com/friend2.jpg',
            status: 'offline',
          },
        ],
      };

      mockDashboardApiClient.getDashboardOverview.mockResolvedValue(mockOverview);

      const result = await service.getDashboardOverview();

      expect(result.isOk()).toBe(true);
      const overview = result.unwrap();

      // Verify aggregation
      expect(overview.currentDriver).toBeDefined();
      expect(overview.currentDriver.id).toBe('driver-123');
      expect(overview.currentDriver.rating).toBe(1500);

      // Verify race aggregation
      expect(overview.myUpcomingRaces).toHaveLength(2);
      expect(overview.otherUpcomingRaces).toHaveLength(1);
      expect(overview.upcomingRaces).toHaveLength(3);

      // Verify league aggregation
      expect(overview.activeLeaguesCount).toBe(3);
      expect(overview.leagueStandingsSummaries).toHaveLength(2);

      // Verify results aggregation
      expect(overview.recentResults).toHaveLength(2);
      const totalPoints = overview.recentResults.reduce((sum, r) => sum + r.points, 0);
      expect(totalPoints).toBe(35);

      // Verify feed aggregation
      expect(overview.feedSummary.unreadCount).toBe(5);
      expect(overview.feedSummary.latestPosts).toHaveLength(2);

      // Verify friends aggregation
      expect(overview.friends).toHaveLength(2);
      const onlineFriends = overview.friends.filter(f => f.status === 'online').length;
      expect(onlineFriends).toBe(1);
    });

    it('should combine analytics metrics from multiple sources', async () => {
      const mockMetrics: GetAnalyticsMetricsOutputDTO = {
        pageViews: 15000,
        uniqueVisitors: 8500,
        averageSessionDuration: 180,
        bounceRate: 0.35,
      };

      mockAnalyticsApiClient.getAnalyticsMetrics.mockResolvedValue(mockMetrics);

      const result = await service.getAnalyticsMetrics();

      expect(result.isOk()).toBe(true);
      const metrics = result.unwrap();

      // Verify metrics are returned correctly
      expect(metrics.pageViews).toBe(15000);
      expect(metrics.uniqueVisitors).toBe(8500);
      expect(metrics.averageSessionDuration).toBe(180);
      expect(metrics.bounceRate).toBe(0.35);

      // Verify derived metrics
      const pageViewsPerVisitor = metrics.pageViews / metrics.uniqueVisitors;
      expect(pageViewsPerVisitor).toBeCloseTo(1.76, 2);

      const bounceRatePercentage = metrics.bounceRate * 100;
      expect(bounceRatePercentage).toBe(35);
    });
  });

  describe('decision branches', () => {
    it('should handle different error types correctly', async () => {
      const errorTypes = [
        { type: 'NOT_FOUND', expectedErrorType: 'notFound' },
        { type: 'AUTH_ERROR', expectedErrorType: 'unauthorized' },
        { type: 'SERVER_ERROR', expectedErrorType: 'serverError' },
        { type: 'NETWORK_ERROR', expectedErrorType: 'networkError' },
        { type: 'TIMEOUT_ERROR', expectedErrorType: 'networkError' },
      ];

      for (const { type, expectedErrorType } of errorTypes) {
        const error = new ApiError(
          `Error of type ${type}`,
          type as any,
          {
            endpoint: '/dashboard/overview',
            method: 'GET',
            timestamp: new Date().toISOString(),
          }
        );

        mockDashboardApiClient.getDashboardOverview.mockRejectedValue(error);

        const result = await service.getDashboardOverview();

        expect(result.isErr()).toBe(true);
        expect(result.getError()).toEqual({
          type: expectedErrorType,
          message: `Error of type ${type}`,
        });
      }
    });

    it('should handle different API response formats', async () => {
      // Test with minimal response
      const minimalOverview: DashboardOverviewDTO = {
        currentDriver: undefined,
        myUpcomingRaces: [],
        otherUpcomingRaces: [],
        upcomingRaces: [],
        activeLeaguesCount: 0,
        recentResults: [],
        leagueStandingsSummaries: [],
        feedSummary: { unreadCount: 0, latestPosts: [] },
        friends: [],
      };

      mockDashboardApiClient.getDashboardOverview.mockResolvedValue(minimalOverview);

      const result = await service.getDashboardOverview();

      expect(result.isOk()).toBe(true);
      const overview = result.unwrap();
      expect(overview.activeLeaguesCount).toBe(0);
      expect(overview.upcomingRaces).toHaveLength(0);

      // Test with full response
      const fullOverview: DashboardOverviewDTO = {
        currentDriver: {
          id: 'driver-123',
          name: 'Test Driver',
          avatarUrl: 'https://example.com/avatar.jpg',
          rating: 1500,
          rank: 10,
        },
        myUpcomingRaces: [
          {
            id: 'race-1',
            name: 'Race 1',
            date: '2024-01-15T10:00:00.000Z',
            track: 'Track 1',
            league: 'League 1',
          },
        ],
        otherUpcomingRaces: [],
        upcomingRaces: [
          {
            id: 'race-1',
            name: 'Race 1',
            date: '2024-01-15T10:00:00.000Z',
            track: 'Track 1',
            league: 'League 1',
          },
        ],
        activeLeaguesCount: 1,
        nextRace: {
          id: 'race-1',
          name: 'Race 1',
          date: '2024-01-15T10:00:00.000Z',
          track: 'Track 1',
          league: 'League 1',
        },
        recentResults: [],
        leagueStandingsSummaries: [],
        feedSummary: { unreadCount: 0, latestPosts: [] },
        friends: [],
      };

      mockDashboardApiClient.getDashboardOverview.mockResolvedValue(fullOverview);

      const result2 = await service.getDashboardOverview();

      expect(result2.isOk()).toBe(true);
      const overview2 = result2.unwrap();
      expect(overview2.currentDriver).toBeDefined();
      expect(overview2.currentDriver.id).toBe('driver-123');
      expect(overview2.activeLeaguesCount).toBe(1);
    });

    it('should handle different user permission levels', async () => {
      // Test with driver data (normal user)
      const driverOverview: DashboardOverviewDTO = {
        currentDriver: {
          id: 'driver-123',
          name: 'Test Driver',
          avatarUrl: 'https://example.com/avatar.jpg',
          rating: 1500,
          rank: 10,
        },
        myUpcomingRaces: [],
        otherUpcomingRaces: [],
        upcomingRaces: [],
        activeLeaguesCount: 0,
        recentResults: [],
        leagueStandingsSummaries: [],
        feedSummary: { unreadCount: 0, latestPosts: [] },
        friends: [],
      };

      mockDashboardApiClient.getDashboardOverview.mockResolvedValue(driverOverview);

      const result = await service.getDashboardOverview();

      expect(result.isOk()).toBe(true);
      const overview = result.unwrap();
      expect(overview.currentDriver).toBeDefined();
      expect(overview.currentDriver.id).toBe('driver-123');

      // Test without driver data (guest user or no driver assigned)
      const guestOverview: DashboardOverviewDTO = {
        currentDriver: undefined,
        myUpcomingRaces: [],
        otherUpcomingRaces: [],
        upcomingRaces: [],
        activeLeaguesCount: 0,
        recentResults: [],
        leagueStandingsSummaries: [],
        feedSummary: { unreadCount: 0, latestPosts: [] },
        friends: [],
      };

      mockDashboardApiClient.getDashboardOverview.mockResolvedValue(guestOverview);

      const result2 = await service.getDashboardOverview();

      expect(result2.isOk()).toBe(true);
      const overview2 = result2.unwrap();
      expect(overview2.currentDriver).toBeUndefined();
    });
  });
});
