import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { TwitchEventService } from './twitch-event.service';
import { ProcessedEvent } from './entities/processed-event.entity';
import { TetardCoinService } from '../economy/tetard-coin.service';
import { ConversionService } from '../economy/conversion.service';

const makeProcessedRepo = () => ({
  findOne: jest.fn(),
  create: jest.fn((dto) => ({ ...dto })),
  save: jest.fn(async (entity) => entity),
});

const makeTcService = () => ({
  earn: jest.fn(async () => 0),
});

describe('TwitchEventService', () => {
  let service: TwitchEventService;
  let processedRepo: ReturnType<typeof makeProcessedRepo>;
  let tcService: ReturnType<typeof makeTcService>;

  beforeEach(async () => {
    processedRepo = makeProcessedRepo();
    tcService = makeTcService();

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        TwitchEventService,
        ConversionService,
        { provide: getRepositoryToken(ProcessedEvent), useValue: processedRepo },
        { provide: TetardCoinService, useValue: tcService },
      ],
    }).compile();

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

  // ─── Cheer tests ────────────────────────────────────────────────────────

  describe('handleCheer()', () => {
    it('cheer 100 Bits → earn 10 TC (taux 10:1, pas de prime)', async () => {
      processedRepo.findOne.mockResolvedValue(null);
      await service.handleCheer('msg-1', 'user1', 100);
      expect(tcService.earn).toHaveBeenCalledWith('user1', 10, 'cheer:100bits');
    });

    it('cheer 500 Bits → earn 55 TC (50 base + 5 prime seuil)', async () => {
      processedRepo.findOne.mockResolvedValue(null);
      await service.handleCheer('msg-2', 'user1', 500);
      expect(tcService.earn).toHaveBeenCalledWith('user1', 55, 'cheer:500bits');
    });

    it('cheer 1000 Bits → earn 115 TC (100 base + 15 prime seuil)', async () => {
      processedRepo.findOne.mockResolvedValue(null);
      await service.handleCheer('msg-3', 'user1', 1000);
      expect(tcService.earn).toHaveBeenCalledWith('user1', 115, 'cheer:1000bits');
    });

    it('cheer 5000 Bits → earn 575 TC (500 base + 75 prime seuil)', async () => {
      processedRepo.findOne.mockResolvedValue(null);
      await service.handleCheer('msg-4', 'user1', 5000);
      expect(tcService.earn).toHaveBeenCalledWith('user1', 575, 'cheer:5000bits');
    });
  });

  // ─── Idempotence tests ───────────────────────────────────────────────────

  describe('idempotence', () => {
    it('event déjà traité → pas de double crédit (cheer)', async () => {
      processedRepo.findOne.mockResolvedValue({ id: 'msg-dup', processedAt: new Date() });
      await service.handleCheer('msg-dup', 'user1', 100);
      expect(tcService.earn).not.toHaveBeenCalled();
      // Must not insert again
      expect(processedRepo.save).not.toHaveBeenCalled();
    });

    it('event déjà traité → pas de double crédit (subscription)', async () => {
      processedRepo.findOne.mockResolvedValue({ id: 'msg-sub-dup', processedAt: new Date() });
      await service.handleSubscription('msg-sub-dup', 'user1', '1000', false);
      expect(tcService.earn).not.toHaveBeenCalled();
    });

    it('premier event → inséré dans ProcessedEvent', async () => {
      processedRepo.findOne.mockResolvedValue(null);
      await service.handleCheer('msg-new', 'user1', 100);
      expect(processedRepo.save).toHaveBeenCalledTimes(1);
    });
  });

  // ─── Subscription tests ──────────────────────────────────────────────────

  describe('handleSubscription()', () => {
    it('T1 subscribe → earn 50 TC', async () => {
      processedRepo.findOne.mockResolvedValue(null);
      await service.handleSubscription('msg-s1', 'user1', '1000', false);
      expect(tcService.earn).toHaveBeenCalledWith('user1', 50, 'subscribe:tier1000');
    });

    it('T2 subscribe → earn 120 TC', async () => {
      processedRepo.findOne.mockResolvedValue(null);
      await service.handleSubscription('msg-s2', 'user1', '2000', false);
      expect(tcService.earn).toHaveBeenCalledWith('user1', 120, 'subscribe:tier2000');
    });

    it('T3 subscribe → earn 350 TC', async () => {
      processedRepo.findOne.mockResolvedValue(null);
      await service.handleSubscription('msg-s3', 'user1', '3000', false);
      expect(tcService.earn).toHaveBeenCalledWith('user1', 350, 'subscribe:tier3000');
    });

    it('Prime subscribe → earn 30 TC', async () => {
      processedRepo.findOne.mockResolvedValue(null);
      await service.handleSubscription('msg-sp', 'user1', 'prime', false);
      expect(tcService.earn).toHaveBeenCalledWith('user1', 30, 'subscribe:tierprime');
    });

    it('gift sub → gifter earns TC (gifterUserId)', async () => {
      processedRepo.findOne.mockResolvedValue(null);
      await service.handleSubscription('msg-gift', 'recipient', '1000', true, 'gifter1');
      expect(tcService.earn).toHaveBeenCalledWith('gifter1', 50, 'gift-sub:tier1000');
    });
  });

  // ─── Threshold bonus unit tests ──────────────────────────────────────────

  describe('getThresholdBonus()', () => {
    it('< 500 Bits → bonus 0', () => {
      expect(service.getThresholdBonus(100)).toBe(0);
      expect(service.getThresholdBonus(499)).toBe(0);
    });

    it('500 Bits → bonus +5', () => {
      expect(service.getThresholdBonus(500)).toBe(5);
      expect(service.getThresholdBonus(999)).toBe(5);
    });

    it('1000 Bits → bonus +15', () => {
      expect(service.getThresholdBonus(1000)).toBe(15);
      expect(service.getThresholdBonus(4999)).toBe(15);
    });

    it('5000 Bits → bonus +75', () => {
      expect(service.getThresholdBonus(5000)).toBe(75);
      expect(service.getThresholdBonus(10000)).toBe(75);
    });
  });
});
