import { SubscriptionEntity } from '../../domain/entities/subscription.entity';
import { type ISubscriptionRepository } from '../../domain/repositories/subscription.repository';
import { UpgradeSubscriptionCommand } from '../commands/upgrade-subscription/upgrade-subscription.command';
import { UpgradeSubscriptionHandler } from '../commands/upgrade-subscription/upgrade-subscription.handler';

function createActiveSubscription(tier: 'FREE' | 'AGENT_PRO' | 'INVESTOR' | 'ENTERPRISE' = 'FREE'): SubscriptionEntity {
  const sub = SubscriptionEntity.createNew(
    'sub-1', 'user-1', 'plan-1', tier,
    new Date('2026-01-01'), new Date('2026-02-01'),
  );
  sub.clearDomainEvents();
  return sub;
}

describe('UpgradeSubscriptionHandler', () => {
  let handler: UpgradeSubscriptionHandler;
  let mockRepo: { [K in keyof ISubscriptionRepository]: ReturnType<typeof vi.fn> };
  let mockPrisma: any;
  let mockEventBus: { publish: ReturnType<typeof vi.fn> };
  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().mockResolvedValue(undefined),
    };

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

    mockEventBus = { publish: vi.fn() };

    mockCache = {
      getOrSet: vi.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 UpgradeSubscriptionHandler(
      mockRepo as any,
      mockPrisma,
      mockEventBus as any,
      mockCache as any,
      mockLogger as any,
    );
  });

  it('upgrades from FREE to AGENT_PRO', async () => {
    const subscription = createActiveSubscription('FREE');
    mockRepo.findByUserId.mockResolvedValue(subscription);
    mockPrisma.plan.findFirst.mockResolvedValue({ id: 'plan-2', tier: 'AGENT_PRO', isActive: true });

    const command = new UpgradeSubscriptionCommand('user-1', 'AGENT_PRO');
    const result = await handler.execute(command);

    expect(result.previousTier).toBe('FREE');
    expect(result.newTier).toBe('AGENT_PRO');
    expect(result.subscriptionId).toBe('sub-1');
    expect(mockRepo.update).toHaveBeenCalledTimes(1);
    expect(mockEventBus.publish).toHaveBeenCalled();
  });

  it('invalidates all quota caches on upgrade', async () => {
    const subscription = createActiveSubscription('FREE');
    mockRepo.findByUserId.mockResolvedValue(subscription);
    mockPrisma.plan.findFirst.mockResolvedValue({ id: 'plan-2', tier: 'AGENT_PRO', isActive: true });

    const command = new UpgradeSubscriptionCommand('user-1', 'AGENT_PRO');
    await handler.execute(command);

    expect(mockCache.invalidateByPrefix).toHaveBeenCalledWith(
      expect.stringContaining('user-1'),
    );
  });

  it('allows lateral switch between AGENT_PRO and INVESTOR', async () => {
    const subscription = createActiveSubscription('AGENT_PRO');
    mockRepo.findByUserId.mockResolvedValue(subscription);
    mockPrisma.plan.findFirst.mockResolvedValue({ id: 'plan-3', tier: 'INVESTOR', isActive: true });

    const command = new UpgradeSubscriptionCommand('user-1', 'INVESTOR');
    const result = await handler.execute(command);

    expect(result.previousTier).toBe('AGENT_PRO');
    expect(result.newTier).toBe('INVESTOR');
  });

  it('throws NotFoundException when no subscription found', async () => {
    mockRepo.findByUserId.mockResolvedValue(null);

    const command = new UpgradeSubscriptionCommand('user-99', 'AGENT_PRO');

    await expect(handler.execute(command)).rejects.toThrow('Subscription');
  });

  it('throws ValidationException when subscription is not active', async () => {
    const subscription = createActiveSubscription('FREE');
    subscription.cancel();
    subscription.clearDomainEvents();
    mockRepo.findByUserId.mockResolvedValue(subscription);

    const command = new UpgradeSubscriptionCommand('user-1', 'AGENT_PRO');

    await expect(handler.execute(command)).rejects.toThrow(/hoạt động/);
  });

  it('throws ValidationException when already on same tier', async () => {
    const subscription = createActiveSubscription('AGENT_PRO');
    mockRepo.findByUserId.mockResolvedValue(subscription);

    const command = new UpgradeSubscriptionCommand('user-1', 'AGENT_PRO');

    await expect(handler.execute(command)).rejects.toThrow(/gói này/);
  });

  it('throws ValidationException when downgrading', async () => {
    const subscription = createActiveSubscription('ENTERPRISE');
    mockRepo.findByUserId.mockResolvedValue(subscription);

    const command = new UpgradeSubscriptionCommand('user-1', 'FREE');

    await expect(handler.execute(command)).rejects.toThrow(/nâng cấp/);
  });

  it('throws NotFoundException when plan does not exist', async () => {
    const subscription = createActiveSubscription('FREE');
    mockRepo.findByUserId.mockResolvedValue(subscription);
    mockPrisma.plan.findFirst.mockResolvedValue(null);

    const command = new UpgradeSubscriptionCommand('user-1', 'AGENT_PRO');

    await expect(handler.execute(command)).rejects.toThrow('Plan');
  });
});
