import { Test, TestingModule } from '@nestjs/testing';
import { EffectiveQuotaService } from './effective-quota.service';
import { EffectiveMembershipService } from './effective-membership.service';
import { PrismaService } from '../../infrastructure/database/prisma.service';

describe('EffectiveQuotaService', () => {
  let svc: EffectiveQuotaService;
  let prisma: any;

  beforeEach(async () => {
    prisma = {
      membershipPlan: { findUnique: jest.fn().mockResolvedValue({ code:'premium', planQuotas:[{ quotaType:'quiz_generation', limit:30, unit:'count', windowType:'daily', isUnlimited:false }] }) },
      quotaUsage: { findMany: jest.fn().mockResolvedValue([]), findFirst: jest.fn().mockResolvedValue(null), create: jest.fn().mockResolvedValue({ id:'r1' }), findUnique: jest.fn(), deleteMany: jest.fn(), updateMany: jest.fn() },
    };
    const mockMembership = { compute: jest.fn().mockResolvedValue({ effectivePlan:'premium', grants:[], appAccountToken:null }) };
    const mod: TestingModule = await Test.createTestingModule({
      providers: [EffectiveQuotaService, { provide: PrismaService, useValue: prisma }, { provide: EffectiveMembershipService, useValue: mockMembership }],
    }).compile();
    svc = mod.get(EffectiveQuotaService);
    svc.setMode('shadow');
  });

  it('computeEffectiveQuota returns plan limits', async () => {
    const q = await svc.computeEffectiveQuota('u1');
    expect(q.planCode).toBe('premium');
    expect(q.limits).toHaveLength(1);
    expect(q.limits[0].quotaType).toBe('quiz_generation');
  });

  it('reserve returns allowed=true when under limit', async () => {
    const r = await svc.reserve('u1', 'quiz_generation', 'op1');
    expect(r.allowed).toBe(true);
  });

  it('reserve returns allowed=true in shadow mode even when over limit', async () => {
    prisma.quotaUsage.findMany.mockResolvedValue([{ amount:31 }]);
    const r = await svc.reserve('u1', 'quiz_generation', 'op1');
    expect(r.allowed).toBe(true); // shadow mode doesn't block
  });

  it('reserve returns allowed=false in enforced mode when over limit', async () => {
    svc.setMode('enforced');
    prisma.quotaUsage.findMany.mockResolvedValue([{ amount:31 }]);
    const r = await svc.reserve('u1', 'quiz_generation', 'op1');
    expect(r.allowed).toBe(false);
  });

  it('reserve creates record and returns reservationId', async () => {
    svc.setMode('enforced');
    const r = await svc.reserve('u1', 'quiz_generation', 'op_new');
    expect(prisma.quotaUsage.create).toHaveBeenCalled();
    expect(r.reservationId).toBe('r1');
  });

  it('reserve with P2002 returns existing record', async () => {
    svc.setMode('enforced');
    prisma.quotaUsage.create.mockRejectedValue({ code:'P2002' });
    prisma.quotaUsage.findUnique.mockResolvedValue({ id:'existing' });
    const r = await svc.reserve('u1', 'quiz_generation', 'op_p2002');
    expect(r.reservationId).toBe('existing');
  });

  it('unlimited quota always allowed', async () => {
    prisma.membershipPlan.findUnique.mockResolvedValue({ code:'premium', planQuotas:[{ quotaType:'ai_chat_messages', limit:0, unit:'count', windowType:'monthly', isUnlimited:true }] });
    const r = await svc.reserve('u1', 'ai_chat_messages', 'op1');
    expect(r.allowed).toBe(true);
    expect(r.remaining).toBe(-1);
  });

  it('missing quotaType returns allowed', async () => {
    const r = await svc.reserve('u1', 'unknown_type', 'op1');
    expect(r.allowed).toBe(true);
  });

  it('legacy mode returns allowed always', async () => {
    svc.setMode('legacy');
    prisma.quotaUsage.findMany.mockResolvedValue([{ amount:999 }]);
    const r = await svc.reserve('u1', 'quiz_generation', 'op1');
    expect(r.allowed).toBe(true);
  });

  it('commit succeeds', async () => { await svc.commit('op1'); });
  it('release calls deleteMany', async () => { await svc.release('op1'); expect(prisma.quotaUsage.deleteMany).toHaveBeenCalled(); });
});
