import { Test } from '@nestjs/testing';
import { DataSource } from 'typeorm';
import { AudienceService } from './audience.service';
import { createMockDataSource, MockDataSource } from '@test/mocks';
import {
  createAudienceOverviewResult,
  createDemographicsResults,
  createDeviceResults,
  createBrowserResults,
  createOSResults,
  createGeoResults,
  createLanguageResults,
  createNewVsReturningResults,
} from '@test/fixtures';
import { GeoGranularity } from './dto/audience-query.dto';

describe('AudienceService', () => {
  let service: AudienceService;
  let mockDataSource: MockDataSource;

  beforeEach(async () => {
    mockDataSource = createMockDataSource();

    const module = await Test.createTestingModule({
      providers: [
        AudienceService,
        {
          provide: DataSource,
          useValue: mockDataSource,
        },
      ],
    }).compile();

    service = module.get<AudienceService>(AudienceService);
  });

  describe('getOverview', () => {
    it('returns overview with top countries and devices', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
        limit: 5,
      };

      const overviewData = createAudienceOverviewResult();
      const geoData = createGeoResults().slice(0, 2);
      const deviceData = createDeviceResults().slice(0, 2);

      mockDataSource.query
        .mockResolvedValueOnce(overviewData)
        .mockResolvedValueOnce(geoData)
        .mockResolvedValueOnce(deviceData);

      const result = await service.getOverview(query);

      expect(result).toEqual({
        totalUsers: 900,
        newUsers: 350,
        returningUsers: 550,
        newUserRate: 350 / 900,
        avgSessionsPerUser: 1.67,
        topCountries: [
          {
            location: 'Iceland',
            sessions: 400,
            users: 250,
            percentage: 0.27,
            engagementRate: 0.82,
            conversionRate: 0.09,
          },
          {
            location: 'United States',
            sessions: 350,
            users: 220,
            percentage: 0.23,
            engagementRate: 0.68,
            conversionRate: 0.05,
          },
        ],
        topDevices: [
          {
            deviceType: 'desktop',
            sessions: 700,
            users: 450,
            percentage: 0.47,
            engagementRate: 0.72,
            conversionRate: 0.06,
          },
          {
            deviceType: 'mobile',
            sessions: 550,
            users: 350,
            percentage: 0.37,
            engagementRate: 0.58,
            conversionRate: 0.03,
          },
        ],
      });

      expect(mockDataSource.query).toHaveBeenCalledTimes(3);
    });

    it('handles empty results with zero defaults', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
        limit: 5,
      };

      mockDataSource.query
        .mockResolvedValueOnce([])
        .mockResolvedValueOnce([])
        .mockResolvedValueOnce([]);

      const result = await service.getOverview(query);

      expect(result).toEqual({
        totalUsers: 0,
        newUsers: 0,
        returningUsers: 0,
        newUserRate: 0,
        avgSessionsPerUser: 0,
        topCountries: [],
        topDevices: [],
      });
    });

    it('calculates newUserRate correctly', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
        limit: 5,
      };

      const customOverview = createAudienceOverviewResult({
        total_users: '1000',
        new_users: '250',
        returning_users: '750',
      });

      mockDataSource.query
        .mockResolvedValueOnce(customOverview)
        .mockResolvedValueOnce([])
        .mockResolvedValueOnce([]);

      const result = await service.getOverview(query);

      expect(result.totalUsers).toBe(1000);
      expect(result.newUsers).toBe(250);
      expect(result.returningUsers).toBe(750);
      expect(result.newUserRate).toBe(0.25);
    });
  });

  describe('getDemographics', () => {
    it('returns new and returning user demographics', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
      };

      const demographicsData = createDemographicsResults();
      mockDataSource.query.mockResolvedValueOnce(demographicsData);

      const result = await service.getDemographics(query);

      expect(result).toEqual([
        {
          userType: 'new',
          users: 350,
          sessions: 400,
          engagementRate: 0.65,
          conversionRate: 0.03,
          revenuePerUser: 4.28,
        },
        {
          userType: 'returning',
          users: 550,
          sessions: 1100,
          engagementRate: 0.78,
          conversionRate: 0.08,
          revenuePerUser: 12.50,
        },
      ]);

      expect(mockDataSource.query).toHaveBeenCalledWith(
        expect.stringContaining('WITH user_sessions AS'),
        [query.startDate, query.endDate],
      );
    });

    it('handles empty results', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
      };

      mockDataSource.query.mockResolvedValueOnce([]);

      const result = await service.getDemographics(query);

      expect(result).toEqual([]);
    });
  });

  describe('getDevices', () => {
    it('returns device breakdown', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
        limit: 20,
      };

      const deviceData = createDeviceResults();
      mockDataSource.query.mockResolvedValueOnce(deviceData);

      const result = await service.getDevices(query);

      expect(result).toEqual([
        {
          deviceType: 'desktop',
          sessions: 700,
          users: 450,
          percentage: 0.47,
          engagementRate: 0.72,
          conversionRate: 0.06,
        },
        {
          deviceType: 'mobile',
          sessions: 550,
          users: 350,
          percentage: 0.37,
          engagementRate: 0.58,
          conversionRate: 0.03,
        },
        {
          deviceType: 'tablet',
          sessions: 250,
          users: 150,
          percentage: 0.16,
          engagementRate: 0.64,
          conversionRate: 0.04,
        },
      ]);

      expect(mockDataSource.query).toHaveBeenCalledWith(
        expect.stringContaining('WITH session_data AS'),
        [query.startDate, query.endDate, query.limit],
      );
    });

    it('handles empty results', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
      };

      mockDataSource.query.mockResolvedValueOnce([]);

      const result = await service.getDevices(query);

      expect(result).toEqual([]);
    });
  });

  describe('getBrowsers', () => {
    it('returns browser breakdown', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
        limit: 20,
      };

      const browserData = createBrowserResults();
      mockDataSource.query.mockResolvedValueOnce(browserData);

      const result = await service.getBrowsers(query);

      expect(result).toEqual([
        {
          browser: 'Chrome',
          version: '120',
          sessions: 600,
          users: 400,
          percentage: 0.40,
        },
        {
          browser: 'Safari',
          version: '17',
          sessions: 400,
          users: 280,
          percentage: 0.27,
        },
        {
          browser: 'Firefox',
          version: '121',
          sessions: 200,
          users: 150,
          percentage: 0.13,
        },
      ]);

      expect(mockDataSource.query).toHaveBeenCalledWith(
        expect.stringContaining('WITH session_data AS'),
        [query.startDate, query.endDate, query.limit],
      );
    });

    it('handles empty results', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
      };

      mockDataSource.query.mockResolvedValueOnce([]);

      const result = await service.getBrowsers(query);

      expect(result).toEqual([]);
    });
  });

  describe('getOperatingSystems', () => {
    it('returns OS breakdown', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
        limit: 20,
      };

      const osData = createOSResults();
      mockDataSource.query.mockResolvedValueOnce(osData);

      const result = await service.getOperatingSystems(query);

      expect(result).toEqual([
        {
          os: 'Windows',
          version: '11',
          sessions: 500,
          users: 350,
          percentage: 0.33,
        },
        {
          os: 'macOS',
          version: '14',
          sessions: 400,
          users: 280,
          percentage: 0.27,
        },
        {
          os: 'iOS',
          version: '17',
          sessions: 350,
          users: 250,
          percentage: 0.23,
        },
      ]);

      expect(mockDataSource.query).toHaveBeenCalledWith(
        expect.stringContaining('WITH session_data AS'),
        [query.startDate, query.endDate, query.limit],
      );
    });

    it('handles empty results', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
      };

      mockDataSource.query.mockResolvedValueOnce([]);

      const result = await service.getOperatingSystems(query);

      expect(result).toEqual([]);
    });
  });

  describe('getGeography', () => {
    it('returns geography breakdown', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
        granularity: GeoGranularity.COUNTRY,
        limit: 20,
      };

      const geoData = createGeoResults();
      mockDataSource.query.mockResolvedValueOnce(geoData);

      const result = await service.getGeography(query);

      expect(result).toEqual([
        {
          location: 'Iceland',
          sessions: 400,
          users: 250,
          percentage: 0.27,
          engagementRate: 0.82,
          conversionRate: 0.09,
        },
        {
          location: 'United States',
          sessions: 350,
          users: 220,
          percentage: 0.23,
          engagementRate: 0.68,
          conversionRate: 0.05,
        },
      ]);

      expect(mockDataSource.query).toHaveBeenCalledWith(
        expect.stringContaining('COALESCE(sf.country, \'unknown\') as location'),
        [query.startDate, query.endDate, query.limit],
      );
    });

    it('handles empty results', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
        granularity: GeoGranularity.COUNTRY,
      };

      mockDataSource.query.mockResolvedValueOnce([]);

      const result = await service.getGeography(query);

      expect(result).toEqual([]);
    });
  });

  describe('getLanguages', () => {
    it('returns language breakdown', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
        limit: 20,
      };

      const languageData = createLanguageResults();
      mockDataSource.query.mockResolvedValueOnce(languageData);

      const result = await service.getLanguages(query);

      expect(result).toEqual([
        {
          language: 'en',
          sessions: 800,
          users: 500,
          percentage: 0.53,
        },
        {
          language: 'is',
          sessions: 300,
          users: 200,
          percentage: 0.20,
        },
      ]);

      expect(mockDataSource.query).toHaveBeenCalledWith(
        expect.stringContaining('WITH session_data AS'),
        [query.startDate, query.endDate, query.limit],
      );
    });

    it('handles empty results', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-22',
      };

      mockDataSource.query.mockResolvedValueOnce([]);

      const result = await service.getLanguages(query);

      expect(result).toEqual([]);
    });
  });

  describe('getNewVsReturning', () => {
    it('returns new vs returning users over time', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-17',
      };

      const trendData = createNewVsReturningResults();
      mockDataSource.query.mockResolvedValueOnce(trendData);

      const result = await service.getNewVsReturning(query);

      expect(result).toEqual([
        {
          period: '2026-01-15',
          newUsers: 50,
          returningUsers: 80,
          newUserRate: 50 / 130,
        },
        {
          period: '2026-01-16',
          newUsers: 45,
          returningUsers: 85,
          newUserRate: 45 / 130,
        },
        {
          period: '2026-01-17',
          newUsers: 55,
          returningUsers: 75,
          newUserRate: 55 / 130,
        },
      ]);

      expect(mockDataSource.query).toHaveBeenCalledWith(
        expect.stringContaining('WITH daily_sessions AS'),
        [query.startDate, query.endDate],
      );
    });

    it('handles empty results', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-17',
      };

      mockDataSource.query.mockResolvedValueOnce([]);

      const result = await service.getNewVsReturning(query);

      expect(result).toEqual([]);
    });

    it('calculates newUserRate per period correctly', async () => {
      const query = {
        startDate: '2026-01-15',
        endDate: '2026-01-15',
      };

      const customData = [
        { period: new Date('2026-01-15'), new_users: '100', returning_users: '0' },
      ];
      mockDataSource.query.mockResolvedValueOnce(customData);

      const result = await service.getNewVsReturning(query);

      expect(result).toEqual([
        {
          period: '2026-01-15',
          newUsers: 100,
          returningUsers: 0,
          newUserRate: 1.0,
        },
      ]);
    });
  });
});
