import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../../src/app.module';
import { PrismaService } from '../../src/infrastructure/database/prisma.service';
import { KafkaService } from '../../src/infrastructure/messaging/kafka.service';
import { RedisService } from '../../src/infrastructure/cache/redis.service';
import * as jwt from 'jsonwebtoken';

// Mock services to avoid real connections in E2E tests
class MockPrismaService {
  async onModuleInit() {}
  async onModuleDestroy() {}
  $connect = jest.fn();
  $disconnect = jest.fn();
  referralRelationship = {
    upsert: jest.fn(),
    findUnique: jest.fn(),
    findMany: jest.fn(),
    count: jest.fn(),
  };
  teamStatistics = {
    upsert: jest.fn(),
    create: jest.fn(),
    findUnique: jest.fn(),
    findMany: jest.fn(),
    count: jest.fn(),
    update: jest.fn(),
  };
  directReferral = {
    upsert: jest.fn(),
    findMany: jest.fn(),
  };
  $transaction = jest.fn((fn) => fn(this));
}

class MockKafkaService {
  async onModuleInit() {}
  async onModuleDestroy() {}
  publish = jest.fn();
  publishBatch = jest.fn();
  subscribe = jest.fn();
}

class MockRedisService {
  async onModuleInit() {}
  async onModuleDestroy() {}
  get = jest.fn().mockResolvedValue(null);
  set = jest.fn();
  del = jest.fn();
  zadd = jest.fn();
  zrevrank = jest.fn().mockResolvedValue(null);
  zrevrange = jest.fn().mockResolvedValue([]);
  zrevrangeWithScores = jest.fn().mockResolvedValue([]);
  zincrby = jest.fn();
}

describe('Referral Service (E2E)', () => {
  let app: INestApplication;
  let mockPrisma: MockPrismaService;
  const JWT_SECRET = 'test-jwt-secret-for-e2e-tests';

  const generateToken = (userId: bigint) => {
    return jwt.sign(
      { sub: `user-${userId}`, userId: userId.toString(), type: 'access' },
      JWT_SECRET,
      { expiresIn: '1h' },
    );
  };

  beforeAll(async () => {
    mockPrisma = new MockPrismaService();

    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    })
      .overrideProvider(PrismaService)
      .useValue(mockPrisma)
      .overrideProvider(KafkaService)
      .useClass(MockKafkaService)
      .overrideProvider(RedisService)
      .useClass(MockRedisService)
      .compile();

    app = moduleFixture.createNestApplication();
    app.useGlobalPipes(
      new ValidationPipe({
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true,
        transformOptions: { enableImplicitConversion: true },
      }),
    );
    app.setGlobalPrefix('api/v1');

    await app.init();
  });

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

  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('Health Endpoints', () => {
    it('GET /api/v1/health - should return health status', () => {
      return request(app.getHttpServer())
        .get('/api/v1/health')
        .expect(200)
        .expect((res) => {
          expect(res.body.status).toBe('ok');
          expect(res.body.service).toBe('referral-service');
        });
    });

    it('GET /api/v1/health/ready - should return ready status', () => {
      return request(app.getHttpServer())
        .get('/api/v1/health/ready')
        .expect(200)
        .expect((res) => {
          expect(res.body.status).toBe('ready');
        });
    });

    it('GET /api/v1/health/live - should return alive status', () => {
      return request(app.getHttpServer())
        .get('/api/v1/health/live')
        .expect(200)
        .expect((res) => {
          expect(res.body.status).toBe('alive');
        });
    });
  });

  describe('Referral Endpoints', () => {
    describe('GET /api/v1/referral/validate/:code', () => {
      it('should return valid=true for existing referral code', async () => {
        mockPrisma.referralRelationship.findUnique.mockResolvedValueOnce({
          id: 1n,
          userId: 100n,
          myReferralCode: 'RWATEST123',
          referrerId: null,
          ancestorPath: [],
          createdAt: new Date(),
          updatedAt: new Date(),
        });

        return request(app.getHttpServer())
          .get('/api/v1/referral/validate/RWATEST123')
          .expect(200)
          .expect((res) => {
            expect(res.body.valid).toBe(true);
            expect(res.body.referrerId).toBe('100');
          });
      });

      it('should return valid=false for non-existing referral code', async () => {
        mockPrisma.referralRelationship.findUnique.mockResolvedValueOnce(null);

        return request(app.getHttpServer())
          .get('/api/v1/referral/validate/NONEXISTENT')
          .expect(200)
          .expect((res) => {
            expect(res.body.valid).toBe(false);
          });
      });
    });

    describe('POST /api/v1/referral/validate', () => {
      it('should validate referral code via POST', async () => {
        mockPrisma.referralRelationship.findUnique.mockResolvedValueOnce({
          id: 1n,
          userId: 100n,
          myReferralCode: 'RWATEST123',
          referrerId: null,
          ancestorPath: [],
          createdAt: new Date(),
          updatedAt: new Date(),
        });

        return request(app.getHttpServer())
          .post('/api/v1/referral/validate')
          .send({ code: 'RWATEST123' })
          .expect(200)
          .expect((res) => {
            expect(res.body.valid).toBe(true);
          });
      });

      it('should reject invalid code format', () => {
        return request(app.getHttpServer())
          .post('/api/v1/referral/validate')
          .send({ code: 'abc' }) // Too short
          .expect(400);
      });
    });

    describe('GET /api/v1/referral/me (Protected)', () => {
      it('should return 401 without token', () => {
        return request(app.getHttpServer()).get('/api/v1/referral/me').expect(401);
      });

      it('should return user referral info with valid token', async () => {
        const userId = 100n;
        const token = generateToken(userId);

        mockPrisma.referralRelationship.findUnique.mockResolvedValueOnce({
          id: 1n,
          userId,
          myReferralCode: 'RWATEST123',
          referrerId: null,
          ancestorPath: [],
          createdAt: new Date(),
          updatedAt: new Date(),
        });

        mockPrisma.teamStatistics.findUnique.mockResolvedValueOnce({
          id: 1n,
          userId,
          directReferralCount: 5,
          totalTeamCount: 100,
          selfPlantingCount: 10,
          totalTeamPlantingCount: 90,
          effectivePlantingCountForRanking: 60,
          maxSingleTeamPlantingCount: 40,
          provinceCityDistribution: {},
          lastCalcAt: new Date(),
          createdAt: new Date(),
          updatedAt: new Date(),
        });

        mockPrisma.directReferral.findMany.mockResolvedValueOnce([]);

        return request(app.getHttpServer())
          .get('/api/v1/referral/me')
          .set('Authorization', `Bearer ${token}`)
          .expect(200)
          .expect((res) => {
            expect(res.body.userId).toBe('100');
            expect(res.body.referralCode).toBe('RWATEST123');
            expect(res.body.directReferralCount).toBe(5);
          });
      });
    });
  });

  describe('Leaderboard Endpoints', () => {
    describe('GET /api/v1/leaderboard', () => {
      it('should return leaderboard entries', async () => {
        mockPrisma.teamStatistics.findMany.mockResolvedValueOnce([
          {
            userId: 100n,
            effectivePlantingCountForRanking: 100,
            totalTeamPlantingCount: 150,
            directReferralCount: 10,
          },
          {
            userId: 101n,
            effectivePlantingCountForRanking: 80,
            totalTeamPlantingCount: 100,
            directReferralCount: 5,
          },
        ]);

        return request(app.getHttpServer())
          .get('/api/v1/leaderboard')
          .expect(200)
          .expect((res) => {
            expect(res.body.entries).toBeDefined();
            expect(res.body.entries.length).toBe(2);
            expect(res.body.entries[0].rank).toBe(1);
            expect(res.body.entries[0].score).toBe(100);
          });
      });

      it('should support pagination', async () => {
        mockPrisma.teamStatistics.findMany.mockResolvedValueOnce([]);

        return request(app.getHttpServer())
          .get('/api/v1/leaderboard?limit=10&offset=20')
          .expect(200)
          .expect((res) => {
            expect(res.body.entries).toBeDefined();
          });
      });
    });

    describe('GET /api/v1/leaderboard/me (Protected)', () => {
      it('should return user rank with valid token', async () => {
        const userId = 100n;
        const token = generateToken(userId);

        mockPrisma.teamStatistics.findUnique.mockResolvedValueOnce({
          effectivePlantingCountForRanking: 50,
        });
        mockPrisma.teamStatistics.count.mockResolvedValueOnce(5);
        mockPrisma.teamStatistics.findMany.mockResolvedValueOnce([
          { userId: 100n, effectivePlantingCountForRanking: 50 },
        ]);

        return request(app.getHttpServer())
          .get('/api/v1/leaderboard/me')
          .set('Authorization', `Bearer ${token}`)
          .expect(200)
          .expect((res) => {
            expect(res.body.userId).toBe('100');
            expect(res.body.rank).toBeDefined();
          });
      });
    });
  });

  describe('Team Statistics Endpoints', () => {
    describe('GET /api/v1/team-statistics/me/distribution (Protected)', () => {
      it('should return province/city distribution', async () => {
        const userId = 100n;
        const token = generateToken(userId);

        mockPrisma.teamStatistics.findUnique.mockResolvedValueOnce({
          id: 1n,
          userId,
          directReferralCount: 5,
          totalTeamCount: 100,
          selfPlantingCount: 10,
          totalTeamPlantingCount: 90,
          effectivePlantingCountForRanking: 60,
          maxSingleTeamPlantingCount: 40,
          provinceCityDistribution: {
            '110000': { '110100': 50, '110200': 30 },
            '120000': { '120100': 20 },
          },
          lastCalcAt: new Date(),
          createdAt: new Date(),
          updatedAt: new Date(),
        });

        mockPrisma.directReferral.findMany.mockResolvedValueOnce([]);

        return request(app.getHttpServer())
          .get('/api/v1/team-statistics/me/distribution')
          .set('Authorization', `Bearer ${token}`)
          .expect(200)
          .expect((res) => {
            expect(res.body.provinces).toBeDefined();
            expect(res.body.totalCount).toBeDefined();
          });
      });
    });
  });

  describe('Error Handling', () => {
    it('should return 404 for non-existent routes', () => {
      return request(app.getHttpServer()).get('/api/v1/nonexistent').expect(404);
    });

    it('should return 401 for protected routes without auth', () => {
      return request(app.getHttpServer()).get('/api/v1/referral/me').expect(401);
    });

    it('should return 401 for invalid token', () => {
      return request(app.getHttpServer())
        .get('/api/v1/referral/me')
        .set('Authorization', 'Bearer invalid-token')
        .expect(401);
    });
  });
});
