import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { PrismaService } from '../../common/prisma.service';

export interface FraudRuleConfig {
  type: 'VELOCITY' | 'HIGH_VALUE' | 'FAILED_PAYMENTS' | 'MULTIPLE_METHODS';
  enabled: boolean;
  threshold: number;
  timeWindowMinutes?: number;
  blockDurationMinutes?: number;
}

@Injectable()
export class FraudService {
  private readonly logger = new Logger(FraudService.name);

  constructor(
    private prisma: PrismaService,
    @InjectDataSource() private dataSource: DataSource,
  ) {}

  /**
   * Evaluate fraud rules and create alerts if triggered
   */
  async evaluateRules(
    passengerId: string,
    eventType: 'booking.created' | 'payment.failed' | 'auth.login.failed',
    context: Record<string, unknown>,
  ): Promise<{ triggered: boolean; rules: string[] }> {
    const triggeredRules: string[] = [];

    if (eventType === 'booking.created') {
      const velocityTriggered = await this.checkVelocityRule(passengerId);
      if (velocityTriggered) triggeredRules.push('VELOCITY');

      const amount = (context.amountMinor as number) || 0;
      const highValueTriggered = await this.checkHighValueRule(amount);
      if (highValueTriggered) triggeredRules.push('HIGH_VALUE');
    }

    if (eventType === 'payment.failed') {
      const failedPaymentTriggered = await this.checkFailedPaymentRule(passengerId);
      if (failedPaymentTriggered) triggeredRules.push('FAILED_PAYMENTS');
    }

    if (triggeredRules.length > 0) {
      await this.createFraudAlert(passengerId, eventType, triggeredRules, context);
      return { triggered: true, rules: triggeredRules };
    }

    return { triggered: false, rules: [] };
  }

  /**
   * Check velocity rule: X bookings in Y minutes
   */
  private async checkVelocityRule(passengerId: string): Promise<boolean> {
    const rule = await this.prisma.fraudRule.findFirst({
      where: { type: 'VELOCITY', enabled: true },
    });

    if (!rule) return false;

    const timeWindowMinutes = (rule.config as any)?.timeWindowMinutes || 30;
    const bookingCount = await this.prisma.booking.count({
      where: {
        passengerId,
        createdAt: { gte: new Date(Date.now() - timeWindowMinutes * 60 * 1000) },
      },
    });

    return bookingCount > rule.threshold;
  }

  /**
   * Check high-value booking rule
   */
  private async checkHighValueRule(amountMinor: number): Promise<boolean> {
    const rule = await this.prisma.fraudRule.findFirst({
      where: { type: 'HIGH_VALUE', enabled: true },
    });

    if (!rule) return false;

    // threshold is in ETB (convert minor units to ETB)
    const amountEtb = amountMinor / 100;
    return amountEtb > rule.threshold;
  }

  /**
   * Check failed payment rule: X failed attempts in Y minutes
   */
  private async checkFailedPaymentRule(passengerId: string): Promise<boolean> {
    const rule = await this.prisma.fraudRule.findFirst({
      where: { type: 'FAILED_PAYMENTS', enabled: true },
    });

    if (!rule) return false;

    const timeWindowMinutes = (rule.config as any)?.timeWindowMinutes || 60;
    const failedCount = await this.prisma.paymentIntent.count({
      where: {
        booking: { passengerId },
        status: 'FAILED',
        updatedAt: { gte: new Date(Date.now() - timeWindowMinutes * 60 * 1000) },
      },
    });

    return failedCount > rule.threshold;
  }

  /**
   * Create a fraud alert
   */
  private async createFraudAlert(
    passengerId: string,
    eventType: string,
    triggeredRules: string[],
    context: Record<string, unknown>,
  ): Promise<void> {
    const passenger = await this.prisma.passenger.findUnique({
      where: { id: passengerId },
      select: { iamUserId: true },
    });
    const alert = await this.prisma.fraudAlert.create({
      data: {
        iamUserId: passenger?.iamUserId ?? passengerId,
        eventType,
        triggeredRules,
        context: context as any,
        severity: triggeredRules.length > 1 ? 'HIGH' : 'MEDIUM',
      },
    });

    this.logger.warn(`Fraud alert created: ${alert.id} for passenger ${passengerId} - rules: ${triggeredRules.join(', ')}`);

    if (triggeredRules.includes('HIGH_VALUE') || triggeredRules.length > 1) {
      if (passenger?.iamUserId) await this.blockUserTemporarily(passenger.iamUserId, 30);
    }
  }

  /**
   * Block user temporarily
   */
  async blockUserTemporarily(iamUserId: string, durationMinutes: number): Promise<void> {
    const blockedUntil = new Date(Date.now() + durationMinutes * 60 * 1000);
    await this.prisma.passenger.updateMany({
      where: { iamUserId },
      data: { blockedUntil },
    });
    this.logger.warn(`Passenger (iamUserId=${iamUserId}) blocked until ${blockedUntil.toISOString()}`);
  }

  /**
   * Unblock user
   */
  async unblockUser(iamUserId: string): Promise<void> {
    await this.prisma.passenger.updateMany({
      where: { iamUserId },
      data: { blockedUntil: null },
    });
    this.logger.log(`Passenger (iamUserId=${iamUserId}) unblocked`);
  }

  /**
   * Acknowledge a fraud alert
   */
  async acknowledgeAlert(id: string) {
    return this.prisma.fraudAlert.update({
      where: { id },
      data: { acknowledged: true, acknowledgedAt: new Date() },
    });
  }

  /**
   * Get all fraud alerts
   */
  async getAlerts(userId?: string, limit = 100, offset = 0) {
    return this.prisma.fraudAlert.findMany({
      where: userId ? { iamUserId: userId } : {},
      orderBy: { createdAt: 'desc' },
      take: limit,
      skip: offset,
    });
  }

  /**
   * Create or update a fraud rule
   */
  async upsertRule(
    type: string,
    config: FraudRuleConfig,
  ) {
    return this.prisma.fraudRule.upsert({
      where: { type: type as any },
      update: {
        enabled: config.enabled,
        threshold: config.threshold,
        config: config as any,
      },
      create: {
        type: type as any,
        enabled: config.enabled,
        threshold: config.threshold,
        config: config as any,
      },
    });
  }

  /**
   * Get all fraud rules
   */
  async getRules() {
    return this.prisma.fraudRule.findMany();
  }

  /**
   * Event listener for booking created
   */
  @OnEvent('booking.created')
  async onBookingCreated(payload: { booking: any }) {
    await this.evaluateRules(payload.booking.passengerId, 'booking.created', {
      bookingId: payload.booking.id,
      amountMinor: payload.booking.totalMinor,
    });
  }

  /**
   * Event listener for payment failed
   */
  @OnEvent('payment.failed')
  async onPaymentFailed(payload: { booking: { passengerId: string; id: string } }) {
    if (!payload.booking?.passengerId) return;
    await this.evaluateRules(payload.booking.passengerId, 'payment.failed', {
      bookingId: payload.booking.id,
    });
  }

  /**
   * Event listener for auth login failed
   */
  @OnEvent('auth.login.failed')
  async onLoginFailed(payload: { email: string }) {
    if (!payload.email) return;
    const iamRows = await this.dataSource.query<{ id: string }[]>(
      `SELECT id FROM iam.users WHERE email = $1 LIMIT 1`,
      [payload.email],
    );
    if (!iamRows.length) return;
    const passenger = await this.prisma.passenger.findUnique({
      where: { iamUserId: iamRows[0].id },
      select: { id: true },
    });
    if (!passenger) return;
    await this.evaluateRules(passenger.id, 'auth.login.failed', { email: payload.email });
  }
}
