import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import request from 'supertest';
import { DataSource, Repository } from 'typeorm';
import { getRepositoryToken } from '@nestjs/typeorm';
import { AppModule } from '../src/app.module';
import { Segment } from '../src/segments/segment.entity';
import { AggregatedMetric } from '../src/entities/aggregated-metric.entity';
import {
  createMockDataSource,
  createMockRepository,
  type MockDataSource,
  type MockRepository,
} from './mocks';
import {
  createAcquisitionOverviewResult,
  createChannelResults,
  createSourceResults,
  createCampaignResults,
  createReferrerResults,
  createEngagementOverviewResult,
  createUniqueViewsResult,
  createPageResults,
  createEventResults,
  createScrollDepthResults,
  createUserFlowResults,
  createAudienceOverviewResult,
  createDemographicsResults,
  createDeviceResults,
  createBrowserResults,
  createOSResults,
  createGeoResults,
  createLanguageResults,
  createNewVsReturningResults,
  createSessionListResults,
  createSessionCountResult,
  createSessionMetricsResult,
  createSegmentMetricsResult,
  createAggregatedMetricSeries,
} from './fixtures/aggregated-metric.fixture';
import { SegmentOperator, ConditionOperator } from '../src/segments/dto/segment.dto';

describe('Analytics API (E2E)', () => {
  let app: INestApplication;
  let mockDataSource: MockDataSource;
  let mockSegmentRepo: MockRepository<Segment>;
  let mockMetricRepo: MockRepository<AggregatedMetric>;

  beforeAll(async () => {
    mockDataSource = createMockDataSource();
    mockSegmentRepo = createMockRepository<Segment>();
    mockMetricRepo = createMockRepository<AggregatedMetric>();

    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    })
      .overrideProvider(DataSource)
      .useValue(mockDataSource)
      .overrideProvider(getRepositoryToken(Segment))
      .useValue(mockSegmentRepo)
      .overrideProvider(getRepositoryToken(AggregatedMetric))
      .useValue(mockMetricRepo)
      .compile();

    app = moduleFixture.createNestApplication();
    app.useGlobalPipes(
      new ValidationPipe({
        transform: true,
        whitelist: true,
        forbidNonWhitelisted: true,
      }),
    );
    await app.init();
  });

  afterAll(async () => {
    await app.close();
  });

  describe('Health', () => {
    it('should return health status', async () => {
      const response = await request(app.getHttpServer())
        .get('/health')
        .expect(200);

      expect(response.body).toHaveProperty('status');
    });
  });

  describe('Acquisition', () => {
    beforeEach(() => {
      mockDataSource.query.mockReset();
    });

    describe('GET /acquisition/overview', () => {
      it('should return acquisition overview', async () => {
        mockDataSource.query.mockResolvedValue(createAcquisitionOverviewResult());

        const response = await request(app.getHttpServer())
          .get('/acquisition/overview')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(response.body).toHaveProperty('totalSessions');
        expect(response.body).toHaveProperty('totalUsers');
        expect(response.body).toHaveProperty('newUsers');
        expect(response.body).toHaveProperty('returningUsers');
      });

      it('should reject missing startDate', async () => {
        await request(app.getHttpServer())
          .get('/acquisition/overview')
          .query({ endDate: '2026-01-28' })
          .expect(400);
      });

      it('should reject missing endDate', async () => {
        await request(app.getHttpServer())
          .get('/acquisition/overview')
          .query({ startDate: '2026-01-01' })
          .expect(400);
      });

      it('should reject invalid date format', async () => {
        await request(app.getHttpServer())
          .get('/acquisition/overview')
          .query({ startDate: 'invalid', endDate: '2026-01-28' })
          .expect(400);
      });
    });

    describe('GET /acquisition/channels', () => {
      it('should return channel breakdown', async () => {
        mockDataSource.query.mockResolvedValue(createChannelResults());

        const response = await request(app.getHttpServer())
          .get('/acquisition/channels')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
        expect(response.body.length).toBeGreaterThan(0);
        expect(response.body[0]).toHaveProperty('channel');
        expect(response.body[0]).toHaveProperty('sessions');
        expect(response.body[0]).toHaveProperty('users');
      });
    });

    describe('GET /acquisition/sources', () => {
      it('should return source breakdown', async () => {
        mockDataSource.query.mockResolvedValue(createSourceResults());

        const response = await request(app.getHttpServer())
          .get('/acquisition/sources')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
        expect(response.body.length).toBeGreaterThan(0);
        expect(response.body[0]).toHaveProperty('source');
        expect(response.body[0]).toHaveProperty('medium');
      });

      it('should filter by channel', async () => {
        mockDataSource.query.mockResolvedValue(createSourceResults());

        const response = await request(app.getHttpServer())
          .get('/acquisition/sources')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
            channel: 'organic',
          })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });
    });

    describe('GET /acquisition/campaigns', () => {
      it('should return campaign metrics', async () => {
        mockDataSource.query.mockResolvedValue(createCampaignResults());

        const response = await request(app.getHttpServer())
          .get('/acquisition/campaigns')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
        if (response.body.length > 0) {
          expect(response.body[0]).toHaveProperty('campaign');
          expect(response.body[0]).toHaveProperty('source');
          expect(response.body[0]).toHaveProperty('medium');
        }
      });
    });

    describe('GET /acquisition/referrers', () => {
      it('should return top referrers', async () => {
        mockDataSource.query.mockResolvedValue(createReferrerResults());

        const response = await request(app.getHttpServer())
          .get('/acquisition/referrers')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });

      it('should respect limit parameter', async () => {
        mockDataSource.query.mockResolvedValue(createReferrerResults());

        const response = await request(app.getHttpServer())
          .get('/acquisition/referrers')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
            limit: 5,
          })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });
    });

    describe('GET /acquisition/compare', () => {
      it('should compare two periods', async () => {
        mockDataSource.query
          .mockResolvedValueOnce(createAcquisitionOverviewResult())
          .mockResolvedValueOnce(createAcquisitionOverviewResult({
            total_sessions: '1200',
            total_users: '750',
          }));

        const response = await request(app.getHttpServer())
          .get('/acquisition/compare')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
            compareStartDate: '2025-12-01',
            compareEndDate: '2025-12-28',
          })
          .expect(200);

        expect(response.body).toHaveProperty('current');
        expect(response.body).toHaveProperty('previous');
        expect(response.body).toHaveProperty('change');
      });

      it('should reject missing compare dates', async () => {
        await request(app.getHttpServer())
          .get('/acquisition/compare')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
          })
          .expect(400);
      });
    });
  });

  describe('Engagement', () => {
    beforeEach(() => {
      mockDataSource.query.mockReset();
    });

    describe('GET /engagement/overview', () => {
      it('should return engagement overview', async () => {
        mockDataSource.query.mockResolvedValue(createEngagementOverviewResult());

        const response = await request(app.getHttpServer())
          .get('/engagement/overview')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(response.body).toHaveProperty('totalSessions');
        expect(response.body).toHaveProperty('engagedSessions');
        expect(response.body).toHaveProperty('avgDuration');
      });
    });

    describe('GET /engagement/pages', () => {
      it('should return page metrics', async () => {
        mockDataSource.query
          .mockResolvedValueOnce(createPageResults())
          .mockResolvedValueOnce(createUniqueViewsResult());

        const response = await request(app.getHttpServer())
          .get('/engagement/pages')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
        if (response.body.length > 0) {
          expect(response.body[0]).toHaveProperty('path');
          expect(response.body[0]).toHaveProperty('views');
        }
      });

      it('should accept sortBy parameter', async () => {
        mockDataSource.query
          .mockResolvedValueOnce(createPageResults())
          .mockResolvedValueOnce(createUniqueViewsResult());

        await request(app.getHttpServer())
          .get('/engagement/pages')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
            sortBy: 'views',
          })
          .expect(200);
      });
    });

    describe('GET /engagement/events', () => {
      it('should return event metrics', async () => {
        mockDataSource.query.mockResolvedValue(createEventResults());

        const response = await request(app.getHttpServer())
          .get('/engagement/events')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });

      it('should filter by category', async () => {
        mockDataSource.query.mockResolvedValue(createEventResults());

        await request(app.getHttpServer())
          .get('/engagement/events')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
            category: 'click',
          })
          .expect(200);
      });
    });

    describe('GET /engagement/scroll-depth', () => {
      it('should return scroll depth metrics', async () => {
        mockDataSource.query.mockResolvedValue(createScrollDepthResults());

        const response = await request(app.getHttpServer())
          .get('/engagement/scroll-depth')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });

      it('should filter by page', async () => {
        mockDataSource.query.mockResolvedValue(createScrollDepthResults());

        await request(app.getHttpServer())
          .get('/engagement/scroll-depth')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
            page: '/',
          })
          .expect(200);
      });
    });

    describe('GET /engagement/user-flow', () => {
      it('should return user flow data', async () => {
        mockDataSource.query.mockResolvedValue(createUserFlowResults());

        const response = await request(app.getHttpServer())
          .get('/engagement/user-flow')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });

      it('should filter by start page', async () => {
        mockDataSource.query.mockResolvedValue(createUserFlowResults());

        await request(app.getHttpServer())
          .get('/engagement/user-flow')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
            startPage: '/',
          })
          .expect(200);
      });
    });
  });

  describe('Audience', () => {
    beforeEach(() => {
      mockDataSource.query.mockReset();
    });

    describe('GET /audience/overview', () => {
      it('should return audience overview', async () => {
        mockDataSource.query.mockResolvedValue(createAudienceOverviewResult());

        const response = await request(app.getHttpServer())
          .get('/audience/overview')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(response.body).toHaveProperty('totalUsers');
        expect(response.body).toHaveProperty('newUsers');
        expect(response.body).toHaveProperty('returningUsers');
      });
    });

    describe('GET /audience/demographics', () => {
      it('should return demographic breakdown', async () => {
        mockDataSource.query.mockResolvedValue(createDemographicsResults());

        const response = await request(app.getHttpServer())
          .get('/audience/demographics')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });
    });

    describe('GET /audience/devices', () => {
      it('should return device breakdown', async () => {
        mockDataSource.query.mockResolvedValue(createDeviceResults());

        const response = await request(app.getHttpServer())
          .get('/audience/devices')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
        if (response.body.length > 0) {
          expect(response.body[0]).toHaveProperty('deviceType');
        }
      });
    });

    describe('GET /audience/browsers', () => {
      it('should return browser breakdown', async () => {
        mockDataSource.query.mockResolvedValue(createBrowserResults());

        const response = await request(app.getHttpServer())
          .get('/audience/browsers')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
        if (response.body.length > 0) {
          expect(response.body[0]).toHaveProperty('browser');
        }
      });
    });

    describe('GET /audience/operating-systems', () => {
      it('should return OS breakdown', async () => {
        mockDataSource.query.mockResolvedValue(createOSResults());

        const response = await request(app.getHttpServer())
          .get('/audience/operating-systems')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
        if (response.body.length > 0) {
          expect(response.body[0]).toHaveProperty('os');
        }
      });
    });

    describe('GET /audience/geography', () => {
      it('should return geographic breakdown', async () => {
        mockDataSource.query.mockResolvedValue(createGeoResults());

        const response = await request(app.getHttpServer())
          .get('/audience/geography')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });

      it('should accept granularity parameter', async () => {
        mockDataSource.query.mockResolvedValue(createGeoResults());

        await request(app.getHttpServer())
          .get('/audience/geography')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
            granularity: 'country',
          })
          .expect(200);
      });
    });

    describe('GET /audience/languages', () => {
      it('should return language breakdown', async () => {
        mockDataSource.query.mockResolvedValue(createLanguageResults());

        const response = await request(app.getHttpServer())
          .get('/audience/languages')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });
    });

    describe('GET /audience/new-vs-returning', () => {
      it('should return new vs returning user trend', async () => {
        mockDataSource.query.mockResolvedValue(createNewVsReturningResults());

        const response = await request(app.getHttpServer())
          .get('/audience/new-vs-returning')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });
    });
  });

  describe('Sessions', () => {
    beforeEach(() => {
      mockDataSource.query.mockReset();
    });

    describe('GET /sessions', () => {
      it('should return paginated session list', async () => {
        mockDataSource.query
          .mockResolvedValueOnce(createSessionListResults())
          .mockResolvedValueOnce(createSessionCountResult(150));

        const response = await request(app.getHttpServer())
          .get('/sessions')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
            limit: 10,
            offset: 0,
          })
          .expect(200);

        expect(response.body).toHaveProperty('sessions');
        expect(response.body).toHaveProperty('total');
        expect(Array.isArray(response.body.sessions)).toBe(true);
      });

      it('should accept pagination parameters', async () => {
        mockDataSource.query
          .mockResolvedValueOnce(createSessionListResults())
          .mockResolvedValueOnce(createSessionCountResult(150));

        await request(app.getHttpServer())
          .get('/sessions')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
            limit: 20,
            offset: 40,
          })
          .expect(200);
      });
    });

    describe('GET /sessions/metrics', () => {
      it('should return session metrics', async () => {
        mockDataSource.query.mockResolvedValue(createSessionMetricsResult());

        const response = await request(app.getHttpServer())
          .get('/sessions/metrics')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(response.body).toHaveProperty('totalSessions');
        expect(response.body).toHaveProperty('avgDuration');
        expect(response.body).toHaveProperty('bounceRate');
      });
    });

    describe('GET /sessions/:sessionId', () => {
      it('should return single session details', async () => {
        mockDataSource.query.mockResolvedValue(createSessionListResults().slice(0, 1));

        const response = await request(app.getHttpServer())
          .get('/sessions/sess-001')
          .expect(200);

        expect(response.body).toHaveProperty('sessionId');
      });

      it('should return 404 for non-existent session', async () => {
        mockDataSource.query.mockResolvedValue([]);

        await request(app.getHttpServer())
          .get('/sessions/non-existent-id')
          .expect(404);
      });
    });
  });

  describe('Segments', () => {
    beforeEach(() => {
      mockSegmentRepo.find.mockReset();
      mockSegmentRepo.findOne.mockReset();
      mockSegmentRepo.findOneBy.mockReset();
      mockSegmentRepo.save.mockReset();
      mockSegmentRepo.create.mockReset();
      mockSegmentRepo.remove.mockReset();
      mockDataSource.query.mockReset();
    });

    const mockSegment: Segment = {
      id: 'segment-001',
      name: 'Desktop Users',
      description: 'Users on desktop devices',
      conditions: [
        {
          dimension: 'deviceType',
          operator: ConditionOperator.EQUALS,
          values: ['desktop'],
        },
      ],
      operator: SegmentOperator.AND,
      isBuiltIn: false,
      createdBy: null,
      createdAt: new Date('2026-01-15T10:00:00Z'),
      updatedAt: new Date('2026-01-15T10:00:00Z'),
    };

    describe('GET /segments', () => {
      it('should return all segments', async () => {
        mockSegmentRepo.find.mockResolvedValue([mockSegment]);

        const response = await request(app.getHttpServer())
          .get('/segments')
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
        if (response.body.length > 0) {
          expect(response.body[0]).toHaveProperty('id');
          expect(response.body[0]).toHaveProperty('name');
          expect(response.body[0]).toHaveProperty('conditions');
        }
      });
    });

    describe('GET /segments/:id', () => {
      it('should return single segment', async () => {
        mockSegmentRepo.findOneBy.mockResolvedValue(mockSegment);

        const response = await request(app.getHttpServer())
          .get('/segments/segment-001')
          .expect(200);

        expect(response.body).toHaveProperty('id');
        expect(response.body).toHaveProperty('name');
      });

      it('should return 404 for non-existent segment', async () => {
        mockSegmentRepo.findOneBy.mockResolvedValue(null);

        await request(app.getHttpServer())
          .get('/segments/non-existent')
          .expect(404);
      });
    });

    describe('POST /segments', () => {
      it('should create new segment', async () => {
        const newSegment = {
          name: 'Mobile Users',
          description: 'Users on mobile devices',
          conditions: [
            {
              dimension: 'deviceType',
              operator: ConditionOperator.EQUALS,
              values: ['mobile'],
            },
          ],
          operator: SegmentOperator.AND,
        };

        mockSegmentRepo.create.mockReturnValue(mockSegment);
        mockSegmentRepo.save.mockResolvedValue(mockSegment);

        const response = await request(app.getHttpServer())
          .post('/segments')
          .send(newSegment)
          .expect(201);

        expect(response.body).toHaveProperty('id');
        expect(response.body).toHaveProperty('name');
      });

      it('should reject invalid segment data', async () => {
        await request(app.getHttpServer())
          .post('/segments')
          .send({ name: 'Missing conditions' })
          .expect(400);
      });

      it('should reject invalid condition operator', async () => {
        await request(app.getHttpServer())
          .post('/segments')
          .send({
            name: 'Test',
            conditions: [
              {
                dimension: 'deviceType',
                operator: 'invalid_operator',
                values: ['mobile'],
              },
            ],
          })
          .expect(400);
      });
    });

    describe('PUT /segments/:id', () => {
      it('should update segment', async () => {
        const updatedSegment = { ...mockSegment, name: 'Updated Name' };
        mockSegmentRepo.findOneBy.mockResolvedValue(mockSegment);
        mockSegmentRepo.save.mockResolvedValue(updatedSegment);

        const response = await request(app.getHttpServer())
          .put('/segments/segment-001')
          .send({ name: 'Updated Name' })
          .expect(200);

        expect(response.body).toHaveProperty('name', 'Updated Name');
      });

      it('should return 404 for non-existent segment', async () => {
        mockSegmentRepo.findOneBy.mockResolvedValue(null);

        await request(app.getHttpServer())
          .put('/segments/non-existent')
          .send({ name: 'Updated' })
          .expect(404);
      });
    });

    describe('DELETE /segments/:id', () => {
      it('should delete segment', async () => {
        mockSegmentRepo.findOneBy.mockResolvedValue(mockSegment);
        mockSegmentRepo.remove.mockResolvedValue(mockSegment);

        await request(app.getHttpServer())
          .delete('/segments/segment-001')
          .expect(200);
      });

      it('should return 404 for non-existent segment', async () => {
        mockSegmentRepo.findOneBy.mockResolvedValue(null);

        await request(app.getHttpServer())
          .delete('/segments/non-existent')
          .expect(404);
      });
    });

    describe('GET /segments/:id/apply', () => {
      it('should apply segment and return metrics', async () => {
        mockSegmentRepo.findOneBy.mockResolvedValue(mockSegment);
        mockDataSource.query.mockResolvedValue(createSegmentMetricsResult());

        const response = await request(app.getHttpServer())
          .get('/segments/segment-001/apply')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(response.body).toHaveProperty('sessions');
        expect(response.body).toHaveProperty('users');
      });

      it('should reject missing date range', async () => {
        await request(app.getHttpServer())
          .get('/segments/segment-001/apply')
          .expect(400);
      });
    });

    describe('GET /segments/compare', () => {
      it('should compare multiple segments', async () => {
        mockSegmentRepo.findOneBy
          .mockResolvedValueOnce(mockSegment)
          .mockResolvedValueOnce({ ...mockSegment, id: 'segment-002' });
        mockDataSource.query
          .mockResolvedValueOnce(createSegmentMetricsResult())
          .mockResolvedValueOnce(createSegmentMetricsResult());

        const response = await request(app.getHttpServer())
          .get('/segments/compare')
          .query({
            segmentIds: ['segment-001', 'segment-002'],
            startDate: '2026-01-01',
            endDate: '2026-01-28',
          })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });

      it('should reject empty segment IDs', async () => {
        await request(app.getHttpServer())
          .get('/segments/compare')
          .query({
            segmentIds: [],
            startDate: '2026-01-01',
            endDate: '2026-01-28',
          })
          .expect(400);
      });
    });
  });

  describe('Trends', () => {
    beforeEach(() => {
      mockMetricRepo.queryBuilder.getRawMany.mockReset();
    });

    describe('GET /trends', () => {
      it('should return trend data', async () => {
        mockMetricRepo.queryBuilder.getRawMany.mockResolvedValue(
          createAggregatedMetricSeries(7).map(m => ({
            period: m.timestamp,
            value: String(m.value),
          })),
        );

        const response = await request(app.getHttpServer())
          .get('/trends')
          .query({
            metric: 'page_views',
            startDate: '2026-01-15',
            endDate: '2026-01-22',
            granularity: 'day',
          })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
        if (response.body.length > 0) {
          expect(response.body[0]).toHaveProperty('period');
          expect(response.body[0]).toHaveProperty('value');
        }
      });

      it('should reject invalid metric', async () => {
        await request(app.getHttpServer())
          .get('/trends')
          .query({
            metric: 'invalid_metric',
            startDate: '2026-01-15',
            endDate: '2026-01-22',
            granularity: 'day',
          })
          .expect(400);
      });

      it('should reject missing required params', async () => {
        await request(app.getHttpServer())
          .get('/trends')
          .query({ metric: 'page_views' })
          .expect(400);
      });
    });

    describe('GET /trends/compare', () => {
      it('should compare trends between periods', async () => {
        mockMetricRepo.queryBuilder.getRawMany
          .mockResolvedValueOnce(
            createAggregatedMetricSeries(7).map(m => ({
              period: m.timestamp,
              value: String(m.value),
            })),
          )
          .mockResolvedValueOnce(
            createAggregatedMetricSeries(7).map(m => ({
              period: m.timestamp,
              value: String(m.value - 10),
            })),
          );

        const response = await request(app.getHttpServer())
          .get('/trends/compare')
          .query({
            metric: 'page_views',
            startDate: '2026-01-15',
            endDate: '2026-01-22',
            compareStartDate: '2026-01-08',
            compareEndDate: '2026-01-15',
            granularity: 'day',
          })
          .expect(200);

        expect(response.body).toHaveProperty('current');
        expect(response.body).toHaveProperty('previous');
        expect(Array.isArray(response.body.current)).toBe(true);
        expect(Array.isArray(response.body.previous)).toBe(true);
      });
    });
  });

  describe('Funnels', () => {
    beforeEach(() => {
      mockDataSource.query.mockReset();
    });

    describe('POST /funnels/analyze', () => {
      it('should analyze funnel conversion', async () => {
        mockDataSource.query.mockResolvedValue([
          { step: 1, step_name: 'Landing', users: '1000', conversion_rate: '1.00' },
          { step: 2, step_name: 'Profile', users: '600', conversion_rate: '0.60' },
          { step: 3, step_name: 'Contact', users: '200', conversion_rate: '0.20' },
        ]);

        const response = await request(app.getHttpServer())
          .post('/funnels/analyze')
          .send({
            steps: [
              { name: 'Landing', eventType: 'pageview', filter: { pageUrl: '/' } },
              { name: 'Profile', eventType: 'pageview', filter: { pageUrl: '/profiles' } },
              { name: 'Contact', eventType: 'click', filter: { metadata: { action: 'contact' } } },
            ],
            startDate: '2026-01-01',
            endDate: '2026-01-28',
          })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
        if (response.body.length > 0) {
          expect(response.body[0]).toHaveProperty('step');
          expect(response.body[0]).toHaveProperty('stepName');
          expect(response.body[0]).toHaveProperty('users');
        }
      });

      it('should reject invalid funnel steps', async () => {
        await request(app.getHttpServer())
          .post('/funnels/analyze')
          .send({
            steps: [],
            startDate: '2026-01-01',
            endDate: '2026-01-28',
          })
          .expect(400);
      });

      it('should reject missing date range', async () => {
        await request(app.getHttpServer())
          .post('/funnels/analyze')
          .send({
            steps: [
              { name: 'Landing', eventType: 'pageview', filter: { pageUrl: '/' } },
            ],
          })
          .expect(400);
      });
    });

    describe('GET /funnels/presets', () => {
      it('should return preset funnels', async () => {
        const response = await request(app.getHttpServer())
          .get('/funnels/presets')
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });
    });
  });

  describe('Cohorts', () => {
    beforeEach(() => {
      mockDataSource.query.mockReset();
    });

    describe('GET /cohorts/retention', () => {
      it('should return retention cohort analysis', async () => {
        mockDataSource.query.mockResolvedValue([
          { cohort: '2026-01-15', day_0: '100', day_1: '60', day_7: '40', day_30: '25' },
          { cohort: '2026-01-16', day_0: '110', day_1: '65', day_7: '42', day_30: null },
        ]);

        const response = await request(app.getHttpServer())
          .get('/cohorts/retention')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
            granularity: 'day',
          })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });

      it('should reject invalid granularity', async () => {
        await request(app.getHttpServer())
          .get('/cohorts/retention')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
            granularity: 'invalid',
          })
          .expect(400);
      });
    });

    describe('GET /cohorts/behavioral', () => {
      it('should return behavioral cohorts', async () => {
        mockDataSource.query.mockResolvedValue([
          { segment: 'desktop', users: '450', avg_sessions: '2.5', retention_rate: '0.65' },
          { segment: 'mobile', users: '350', avg_sessions: '1.8', retention_rate: '0.52' },
        ]);

        const response = await request(app.getHttpServer())
          .get('/cohorts/behavioral')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
            segmentBy: 'device',
          })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });

      it('should reject missing segmentBy', async () => {
        await request(app.getHttpServer())
          .get('/cohorts/behavioral')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
          })
          .expect(400);
      });
    });
  });

  describe('Revenue', () => {
    beforeEach(() => {
      mockDataSource.query.mockReset();
    });

    describe('GET /revenue/summary', () => {
      it('should return revenue summary', async () => {
        mockDataSource.query.mockResolvedValue([{
          total_revenue: '25000.00',
          total_transactions: '500',
          avg_order_value: '50.00',
          revenue_per_user: '27.78',
        }]);

        const response = await request(app.getHttpServer())
          .get('/revenue/summary')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(response.body).toHaveProperty('totalRevenue');
        expect(response.body).toHaveProperty('totalTransactions');
        expect(response.body).toHaveProperty('avgOrderValue');
      });
    });

    describe('GET /revenue/ltv', () => {
      it('should return lifetime value analysis', async () => {
        mockDataSource.query.mockResolvedValue([
          { cohort: '2026-01', ltv: '150.00', users: '200' },
          { cohort: '2025-12', ltv: '280.00', users: '180' },
        ]);

        const response = await request(app.getHttpServer())
          .get('/revenue/ltv')
          .query({ startDate: '2026-01-01', endDate: '2026-01-28' })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });
    });

    describe('GET /revenue/arpu', () => {
      it('should return average revenue per user', async () => {
        mockDataSource.query.mockResolvedValue([
          { period: new Date('2026-01-15'), arpu: '25.50' },
          { period: new Date('2026-01-16'), arpu: '27.80' },
        ]);

        const response = await request(app.getHttpServer())
          .get('/revenue/arpu')
          .query({
            startDate: '2026-01-01',
            endDate: '2026-01-28',
            granularity: 'day',
          })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });
    });

    describe('GET /revenue/mrr', () => {
      it('should return monthly recurring revenue', async () => {
        mockDataSource.query.mockResolvedValue([{
          mrr: '15000.00',
          subscribers: '300',
          churn_rate: '0.05',
        }]);

        const response = await request(app.getHttpServer())
          .get('/revenue/mrr')
          .query({ month: '2026-01' })
          .expect(200);

        expect(response.body).toHaveProperty('mrr');
        expect(response.body).toHaveProperty('subscribers');
      });

      it('should reject invalid month format', async () => {
        await request(app.getHttpServer())
          .get('/revenue/mrr')
          .query({ month: 'invalid' })
          .expect(400);
      });
    });
  });

  describe('GDPR', () => {
    beforeEach(() => {
      mockDataSource.query.mockReset();
    });

    describe('POST /gdpr/export/user/:userId', () => {
      it('should export user data', async () => {
        mockDataSource.query
          .mockResolvedValueOnce([{ sessionId: 'sess-001', userId: 'user-001' }])
          .mockResolvedValueOnce([{ eventType: 'pageview', timestamp: '2026-01-15T10:00:00Z' }]);

        const response = await request(app.getHttpServer())
          .post('/gdpr/export/user/user-001')
          .expect(200);

        expect(response.body).toHaveProperty('userId');
        expect(response.body).toHaveProperty('sessions');
        expect(response.body).toHaveProperty('events');
      });
    });

    describe('POST /gdpr/export/session/:sessionId', () => {
      it('should export session data', async () => {
        mockDataSource.query
          .mockResolvedValueOnce([{ sessionId: 'sess-001' }])
          .mockResolvedValueOnce([{ eventType: 'pageview' }]);

        const response = await request(app.getHttpServer())
          .post('/gdpr/export/session/sess-001')
          .expect(200);

        expect(response.body).toHaveProperty('sessionId');
        expect(response.body).toHaveProperty('events');
      });
    });

    describe('DELETE /gdpr/erase/user/:userId', () => {
      it('should erase user data', async () => {
        const queryRunner = {
          connect: vi.fn(),
          startTransaction: vi.fn(),
          commitTransaction: vi.fn(),
          rollbackTransaction: vi.fn(),
          release: vi.fn(),
          manager: {
            delete: vi.fn().mockResolvedValue({ affected: 1 }),
          },
        };

        mockDataSource.createQueryRunner.mockReturnValue(queryRunner);

        await request(app.getHttpServer())
          .delete('/gdpr/erase/user/user-001')
          .expect(200);
      });
    });

    describe('DELETE /gdpr/erase/session/:sessionId', () => {
      it('should erase session data', async () => {
        const queryRunner = {
          connect: vi.fn(),
          startTransaction: vi.fn(),
          commitTransaction: vi.fn(),
          rollbackTransaction: vi.fn(),
          release: vi.fn(),
          manager: {
            delete: vi.fn().mockResolvedValue({ affected: 1 }),
          },
        };

        mockDataSource.createQueryRunner.mockReturnValue(queryRunner);

        await request(app.getHttpServer())
          .delete('/gdpr/erase/session/sess-001')
          .expect(200);
      });
    });

    describe('GET /gdpr/retention-policy', () => {
      it('should return retention policy', async () => {
        const response = await request(app.getHttpServer())
          .get('/gdpr/retention-policy')
          .expect(200);

        expect(response.body).toHaveProperty('rawEvents');
        expect(response.body).toHaveProperty('sessions');
        expect(response.body).toHaveProperty('aggregatedMetrics');
      });
    });

    describe('POST /gdpr/retention-policy', () => {
      it('should update retention policy', async () => {
        const response = await request(app.getHttpServer())
          .post('/gdpr/retention-policy')
          .send({
            rawEvents: 90,
            sessions: 365,
            aggregatedMetrics: 730,
          })
          .expect(200);

        expect(response.body).toHaveProperty('rawEvents', 90);
      });

      it('should reject invalid retention days', async () => {
        await request(app.getHttpServer())
          .post('/gdpr/retention-policy')
          .send({
            rawEvents: -1,
          })
          .expect(400);
      });
    });

    describe('POST /gdpr/cleanup/execute', () => {
      it('should execute cleanup', async () => {
        const queryRunner = {
          connect: vi.fn(),
          startTransaction: vi.fn(),
          commitTransaction: vi.fn(),
          rollbackTransaction: vi.fn(),
          release: vi.fn(),
          manager: {
            delete: vi.fn().mockResolvedValue({ affected: 100 }),
          },
        };

        mockDataSource.createQueryRunner.mockReturnValue(queryRunner);

        const response = await request(app.getHttpServer())
          .post('/gdpr/cleanup/execute')
          .expect(200);

        expect(response.body).toHaveProperty('deletedRecords');
      });
    });

    describe('GET /gdpr/cleanup/preview', () => {
      it('should preview cleanup', async () => {
        mockDataSource.query.mockResolvedValue([{ count: '150' }]);

        const response = await request(app.getHttpServer())
          .get('/gdpr/cleanup/preview')
          .expect(200);

        expect(response.body).toHaveProperty('rawEventsToDelete');
        expect(response.body).toHaveProperty('sessionsToDelete');
      });
    });

    describe('GET /gdpr/audit-log', () => {
      it('should return audit log', async () => {
        mockDataSource.query.mockResolvedValue([
          {
            id: 'log-001',
            action: 'EXPORT',
            targetType: 'USER',
            targetId: 'user-001',
            timestamp: '2026-01-15T10:00:00Z',
          },
        ]);

        const response = await request(app.getHttpServer())
          .get('/gdpr/audit-log')
          .query({ limit: 50, offset: 0 })
          .expect(200);

        expect(Array.isArray(response.body)).toBe(true);
      });
    });

    describe('GET /gdpr/consent/:userId', () => {
      it('should return user consent status', async () => {
        mockDataSource.query.mockResolvedValue([
          {
            userId: 'user-001',
            analytics: true,
            marketing: false,
            lastUpdated: '2026-01-15T10:00:00Z',
          },
        ]);

        const response = await request(app.getHttpServer())
          .get('/gdpr/consent/user-001')
          .expect(200);

        expect(response.body).toHaveProperty('userId');
        expect(response.body).toHaveProperty('analytics');
      });
    });

    describe('POST /gdpr/consent/:userId', () => {
      it('should update user consent', async () => {
        mockDataSource.query.mockResolvedValue({ affected: 1 });

        const response = await request(app.getHttpServer())
          .post('/gdpr/consent/user-001')
          .send({
            analytics: true,
            marketing: false,
          })
          .expect(200);

        expect(response.body).toHaveProperty('userId');
        expect(response.body).toHaveProperty('analytics', true);
      });

      it('should reject invalid consent data', async () => {
        await request(app.getHttpServer())
          .post('/gdpr/consent/user-001')
          .send({
            analytics: 'not-a-boolean',
          })
          .expect(400);
      });
    });
  });
});
