import { type KycApprovedEvent } from '../../domain/events/kyc-approved.event';
import { type KycRejectedEvent } from '../../domain/events/kyc-rejected.event';
import { type ListingApprovedEvent } from '../../domain/events/listing-approved.event';
import { type ListingRejectedEvent } from '../../domain/events/listing-rejected.event';
import { type SubscriptionAdjustedEvent } from '../../domain/events/subscription-adjusted.event';
import { type UserBannedEvent } from '../../domain/events/user-banned.event';
import { type UserUnbannedEvent } from '../../domain/events/user-unbanned.event';
import { AdminAuditListener } from '../listeners/admin-audit.listener';

describe('AdminAuditListener', () => {
  let listener: AdminAuditListener;
  let mockAuditRepo: { create: ReturnType<typeof vi.fn> };
  let mockLogger: {
    log: ReturnType<typeof vi.fn>;
    error: ReturnType<typeof vi.fn>;
  };

  beforeEach(() => {
    mockAuditRepo = {
      create: vi.fn().mockResolvedValue({
        id: 'audit-1',
        action: 'LISTING_APPROVED',
        actorId: 'admin-1',
        targetId: 'listing-1',
        targetType: 'LISTING',
        metadata: null,
        ipAddress: null,
        userAgent: null,
        createdAt: new Date(),
      }),
    };
    mockLogger = { log: vi.fn(), error: vi.fn() };

    listener = new AdminAuditListener(
      mockAuditRepo as any,
      mockLogger as any,
    );
  });

  // ── Listing Events ──

  it('logs listing approval', async () => {
    const event: ListingApprovedEvent = {
      aggregateId: 'listing-1',
      adminId: 'admin-1',
      moderationNotes: 'Looks good',
      eventName: 'listing.approved_by_admin',
      occurredAt: new Date(),
    };

    await listener.onListingApproved(event);

    expect(mockAuditRepo.create).toHaveBeenCalledWith({
      action: 'LISTING_APPROVED',
      actorId: 'admin-1',
      targetId: 'listing-1',
      targetType: 'LISTING',
      metadata: { moderationNotes: 'Looks good' },
    });
  });

  it('logs listing rejection', async () => {
    const event: ListingRejectedEvent = {
      aggregateId: 'listing-2',
      adminId: 'admin-1',
      reason: 'Vi phạm nội dung',
      eventName: 'listing.rejected_by_admin',
      occurredAt: new Date(),
    };

    await listener.onListingRejected(event);

    expect(mockAuditRepo.create).toHaveBeenCalledWith({
      action: 'LISTING_REJECTED',
      actorId: 'admin-1',
      targetId: 'listing-2',
      targetType: 'LISTING',
      metadata: { reason: 'Vi phạm nội dung' },
    });
  });

  // ── User Events ──

  it('logs user ban', async () => {
    const event: UserBannedEvent = {
      aggregateId: 'user-1',
      adminId: 'admin-1',
      reason: 'Spam',
      eventName: 'user.banned',
      occurredAt: new Date(),
    };

    await listener.onUserBanned(event);

    expect(mockAuditRepo.create).toHaveBeenCalledWith({
      action: 'USER_BANNED',
      actorId: 'admin-1',
      targetId: 'user-1',
      targetType: 'USER',
      metadata: { reason: 'Spam' },
    });
  });

  it('logs user unban', async () => {
    const event: UserUnbannedEvent = {
      aggregateId: 'user-1',
      adminId: 'admin-1',
      eventName: 'user.unbanned',
      occurredAt: new Date(),
    };

    await listener.onUserUnbanned(event);

    expect(mockAuditRepo.create).toHaveBeenCalledWith({
      action: 'USER_UNBANNED',
      actorId: 'admin-1',
      targetId: 'user-1',
      targetType: 'USER',
      metadata: undefined,
    });
  });

  // ── KYC Events ──

  it('logs KYC approval', async () => {
    const event: KycApprovedEvent = {
      aggregateId: 'user-2',
      adminId: 'admin-1',
      comments: 'Documents verified',
      eventName: 'kyc.approved',
      occurredAt: new Date(),
    };

    await listener.onKycApproved(event);

    expect(mockAuditRepo.create).toHaveBeenCalledWith({
      action: 'KYC_APPROVED',
      actorId: 'admin-1',
      targetId: 'user-2',
      targetType: 'USER',
      metadata: { comments: 'Documents verified' },
    });
  });

  it('logs KYC rejection', async () => {
    const event: KycRejectedEvent = {
      aggregateId: 'user-3',
      adminId: 'admin-1',
      reason: 'Giấy tờ không hợp lệ',
      eventName: 'kyc.rejected',
      occurredAt: new Date(),
    };

    await listener.onKycRejected(event);

    expect(mockAuditRepo.create).toHaveBeenCalledWith({
      action: 'KYC_REJECTED',
      actorId: 'admin-1',
      targetId: 'user-3',
      targetType: 'USER',
      metadata: { reason: 'Giấy tờ không hợp lệ' },
    });
  });

  // ── Subscription Events ──

  it('logs subscription adjustment', async () => {
    const event: SubscriptionAdjustedEvent = {
      aggregateId: 'user-4',
      adminId: 'admin-1',
      newPlanId: 'plan-pro',
      reason: 'Customer request',
      eventName: 'subscription.adjusted_by_admin',
      occurredAt: new Date(),
    };

    await listener.onSubscriptionAdjusted(event);

    expect(mockAuditRepo.create).toHaveBeenCalledWith({
      action: 'SUBSCRIPTION_ADJUSTED',
      actorId: 'admin-1',
      targetId: 'user-4',
      targetType: 'SUBSCRIPTION',
      metadata: { newPlanId: 'plan-pro', reason: 'Customer request' },
    });
  });

  // ── Error Handling ──

  it('does not throw when audit logging fails', async () => {
    mockAuditRepo.create.mockRejectedValue(new Error('DB connection lost'));

    const event: ListingApprovedEvent = {
      aggregateId: 'listing-1',
      adminId: 'admin-1',
      eventName: 'listing.approved_by_admin',
      occurredAt: new Date(),
    };

    // Should not throw
    await expect(listener.onListingApproved(event)).resolves.toBeUndefined();

    expect(mockLogger.error).toHaveBeenCalledWith(
      expect.stringContaining('Failed to write audit log'),
      expect.any(String),
      'AdminAuditListener',
    );
  });

  it('logs success message after writing audit entry', async () => {
    const event: UserBannedEvent = {
      aggregateId: 'user-1',
      adminId: 'admin-1',
      reason: 'Violation',
      eventName: 'user.banned',
      occurredAt: new Date(),
    };

    await listener.onUserBanned(event);

    expect(mockLogger.log).toHaveBeenCalledWith(
      'Audit: USER_BANNED by admin-1 on USER:user-1',
      'AdminAuditListener',
    );
  });
});
