import { SubscriptionEntity } from '../../domain/entities/subscription.entity';
import { type ISubscriptionRepository } from '../../domain/repositories/subscription.repository';
import { CheckQuotaHandler } from '../queries/check-quota/check-quota.handler';
import { CheckQuotaQuery } from '../queries/check-quota/check-quota.query';

describe('CheckQuotaHandler', () => {
  let handler: CheckQuotaHandler;
  let mockRepo: { [K in keyof ISubscriptionRepository]: ReturnType<typeof vi.fn> };
  let mockPrisma: any;
  let mockCache: { getOrSet: ReturnType<typeof vi.fn>; invalidate: ReturnType<typeof vi.fn>; invalidateByPrefix: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    mockRepo = {
      findById: vi.fn(),
      findByUserId: vi.fn(),
      save: vi.fn(),
      update: vi.fn(),
    };

    mockPrisma = {
      plan: {
        findFirst: vi.fn(),
        findUnique: vi.fn(),
      },
      usageRecord: {
        findFirst: vi.fn(),
        findUnique: vi.fn(),
      },
    };

    mockCache = {
      getOrSet: vi.fn().mockImplementation(async (_key: string, fn: () => Promise<unknown>) => fn()),
      invalidate: vi.fn().mockResolvedValue(undefined),
      invalidateByPrefix: vi.fn().mockResolvedValue(undefined),
    };

    const mockLogger = { log: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), verbose: vi.fn() };

    handler = new CheckQuotaHandler(mockRepo as any, mockPrisma, mockCache as any, mockLogger as any);
  });

  it('returns quota for active subscription', async () => {
    const sub = SubscriptionEntity.createNew(
      'sub-1', 'user-1', 'plan-1', 'AGENT_PRO',
      new Date('2026-01-01'), new Date('2026-02-01'),
    );
    sub.clearDomainEvents();
    mockRepo.findByUserId.mockResolvedValue(sub);
    mockPrisma.plan.findUnique.mockResolvedValue({
      id: 'plan-1',
      maxListings: 50,
      maxSavedSearches: 10,
    });
    mockPrisma.usageRecord.findUnique.mockResolvedValue({ count: 15 });

    const query = new CheckQuotaQuery('user-1', 'listings_created');
    const result = await handler.execute(query);

    expect(result.metric).toBe('listings_created');
    expect(result.limit).toBe(50);
    expect(result.used).toBe(15);
    expect(result.remaining).toBe(35);
    expect(result.allowed).toBe(true);
  });

  it('returns not allowed when quota exceeded', async () => {
    const sub = SubscriptionEntity.createNew(
      'sub-1', 'user-1', 'plan-1', 'FREE',
      new Date('2026-01-01'), new Date('2026-02-01'),
    );
    sub.clearDomainEvents();
    mockRepo.findByUserId.mockResolvedValue(sub);
    mockPrisma.plan.findUnique.mockResolvedValue({
      id: 'plan-1',
      maxListings: 5,
    });
    mockPrisma.usageRecord.findUnique.mockResolvedValue({ count: 5 });

    const query = new CheckQuotaQuery('user-1', 'listings_created');
    const result = await handler.execute(query);

    expect(result.remaining).toBe(0);
    expect(result.allowed).toBe(false);
  });

  it('falls back to FREE tier when no subscription', async () => {
    mockRepo.findByUserId.mockResolvedValue(null);
    mockPrisma.plan.findFirst.mockResolvedValue({
      id: 'free-plan',
      maxListings: 3,
      maxSavedSearches: 1,
    });

    const query = new CheckQuotaQuery('user-1', 'listings_created');
    const result = await handler.execute(query);

    expect(result.limit).toBe(3);
    expect(result.used).toBe(0);
    expect(result.allowed).toBe(true);
  });

  it('returns unlimited for unknown metric', async () => {
    const sub = SubscriptionEntity.createNew(
      'sub-1', 'user-1', 'plan-1', 'AGENT_PRO',
      new Date('2026-01-01'), new Date('2026-02-01'),
    );
    sub.clearDomainEvents();
    mockRepo.findByUserId.mockResolvedValue(sub);
    mockPrisma.plan.findUnique.mockResolvedValue({ id: 'plan-1' });

    const query = new CheckQuotaQuery('user-1', 'unknown_metric');
    const result = await handler.execute(query);

    expect(result.limit).toBeNull();
    expect(result.allowed).toBe(true);
  });

  it('returns zero quota when no free plan found', async () => {
    mockRepo.findByUserId.mockResolvedValue(null);
    mockPrisma.plan.findFirst.mockResolvedValue(null);

    const query = new CheckQuotaQuery('user-1', 'listings_created');
    const result = await handler.execute(query);

    expect(result.limit).toBe(0);
    expect(result.allowed).toBe(false);
  });

  it('uses cache with correct key and TTL', async () => {
    mockRepo.findByUserId.mockResolvedValue(null);
    mockPrisma.plan.findFirst.mockResolvedValue(null);

    const query = new CheckQuotaQuery('user-1', 'listings_created');
    await handler.execute(query);

    expect(mockCache.getOrSet).toHaveBeenCalledWith(
      expect.stringContaining('user-1'),
      expect.any(Function),
      60,
      'quota',
    );
  });
});
