import { Inject, Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import {
  type EmailChangeRequestedEvent,
  type EmailChangedEvent,
  type PhoneChangeRequestedEvent,
  type PhoneChangedEvent,
} from '@modules/auth';
import { type ListingOwnershipTransferredEvent } from '@modules/listings';
import { LoggerService } from '@modules/shared';
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 {
  AUDIT_LOG_REPOSITORY,
  type IAuditLogRepository,
} from '../../domain/repositories/audit-log.repository';

@Injectable()
export class AdminAuditListener {
  constructor(
    @Inject(AUDIT_LOG_REPOSITORY) private readonly auditRepo: IAuditLogRepository,
    private readonly logger: LoggerService,
  ) {}

  @OnEvent('listing.approved_by_admin', { async: true })
  async onListingApproved(event: ListingApprovedEvent): Promise<void> {
    await this.log('LISTING_APPROVED', event.adminId, event.aggregateId, 'LISTING', {
      moderationNotes: event.moderationNotes,
    });
  }

  @OnEvent('listing.rejected_by_admin', { async: true })
  async onListingRejected(event: ListingRejectedEvent): Promise<void> {
    await this.log('LISTING_REJECTED', event.adminId, event.aggregateId, 'LISTING', {
      reason: event.reason,
    });
  }

  @OnEvent('user.banned', { async: true })
  async onUserBanned(event: UserBannedEvent): Promise<void> {
    await this.log('USER_BANNED', event.adminId, event.aggregateId, 'USER', {
      reason: event.reason,
    });
  }

  @OnEvent('user.unbanned', { async: true })
  async onUserUnbanned(event: UserUnbannedEvent): Promise<void> {
    await this.log('USER_UNBANNED', event.adminId, event.aggregateId, 'USER');
  }

  @OnEvent('kyc.approved', { async: true })
  async onKycApproved(event: KycApprovedEvent): Promise<void> {
    await this.log('KYC_APPROVED', event.adminId, event.aggregateId, 'USER', {
      comments: event.comments,
    });
  }

  @OnEvent('kyc.rejected', { async: true })
  async onKycRejected(event: KycRejectedEvent): Promise<void> {
    await this.log('KYC_REJECTED', event.adminId, event.aggregateId, 'USER', {
      reason: event.reason,
    });
  }

  @OnEvent('subscription.adjusted_by_admin', { async: true })
  async onSubscriptionAdjusted(event: SubscriptionAdjustedEvent): Promise<void> {
    await this.log('SUBSCRIPTION_ADJUSTED', event.adminId, event.aggregateId, 'SUBSCRIPTION', {
      newPlanId: event.newPlanId,
      reason: event.reason,
    });
  }

  // ── Listing ownership transfer (TEC-2928) ────────────────────────────

  @OnEvent('listing.ownership_transferred', { async: true })
  async onListingOwnershipTransferred(
    event: ListingOwnershipTransferredEvent,
  ): Promise<void> {
    await this.log(
      'LISTING_OWNERSHIP_TRANSFER',
      event.byUserId,
      event.aggregateId,
      'LISTING',
      {
        fromAgentId: event.fromAgentId,
        toAgentId: event.toAgentId,
        actorRole: event.byRole,
      },
    );
  }

  // ── Sensitive user profile field changes (OTP-gated) ─────────────────

  @OnEvent('user.email_change_requested', { async: true })
  async onEmailChangeRequested(event: EmailChangeRequestedEvent): Promise<void> {
    // Actor is the user themselves — they initiated the change.
    // Do NOT include the OTP code in the audit metadata.
    await this.log(
      'EMAIL_CHANGE_REQUESTED',
      event.aggregateId,
      event.aggregateId,
      'USER',
      { newEmail: event.newEmail },
    );
  }

  @OnEvent('user.phone_change_requested', { async: true })
  async onPhoneChangeRequested(event: PhoneChangeRequestedEvent): Promise<void> {
    await this.log(
      'PHONE_CHANGE_REQUESTED',
      event.aggregateId,
      event.aggregateId,
      'USER',
      { newPhone: event.newPhone },
    );
  }

  @OnEvent('user.email_changed', { async: true })
  async onEmailChanged(event: EmailChangedEvent): Promise<void> {
    await this.log(
      'EMAIL_CHANGED',
      event.aggregateId,
      event.aggregateId,
      'USER',
      { oldEmail: event.oldEmail, newEmail: event.newEmail },
    );
  }

  @OnEvent('user.phone_changed', { async: true })
  async onPhoneChanged(event: PhoneChangedEvent): Promise<void> {
    await this.log(
      'PHONE_CHANGED',
      event.aggregateId,
      event.aggregateId,
      'USER',
      { oldPhone: event.oldPhone, newPhone: event.newPhone },
    );
  }

  private async log(
    action: string,
    actorId: string,
    targetId: string,
    targetType: string,
    metadata?: Record<string, unknown>,
  ): Promise<void> {
    try {
      await this.auditRepo.create({ action, actorId, targetId, targetType, metadata });
      this.logger.log(
        `Audit: ${action} by ${actorId} on ${targetType}:${targetId}`,
        'AdminAuditListener',
      );
    } catch (error) {
      // Audit failures must not break the main flow — log and continue
      this.logger.error(
        `Failed to write audit log: ${action} by ${actorId} on ${targetType}:${targetId}`,
        error instanceof Error ? error.stack : String(error),
        'AdminAuditListener',
      );
    }
  }
}
