import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Redis from 'ioredis';
import { UserReferralRelationshipRepository } from '../../infrastructure/repositories/user-referral-relationship.repository';
import { UserPointRepository } from '../../infrastructure/repositories/user-point.repository';
import { ProcessedEventRepository } from '../../infrastructure/repositories/processed-event.repository';

/**
 * Listens to events:payment.received (consumer group: referral-user-service).
 * When a user pays, finds their user-level referral relationship and awards points
 * to their referrer (and level-2 referrer).
 *
 * Point award rules:
 *   First payment (Pro):        referrer +1500 pts, referred +500 pts
 *   First payment (Enterprise): referrer +5000 pts, referred +2000 pts
 *   Recurring (≤12 months):     referrer + 10% of payment_cents/10 pts
 *   Level-2 (≤6 months):        l2-referrer + 5% of payment_cents/10 pts
 *
 * (Convention: 1 USD = 100 pts, so payment_cents / 100 * pts_per_dollar)
 */

interface PaymentPayload {
  tenantId: string;
  userId?: string;       // present in new events; may be absent in legacy
  invoiceId: string;
  paymentId: string;
  amount: number;        // USD cents
  currency: string;
  planName?: string;
}

const FIRST_PAYMENT_POINTS: Record<string, { referrer: number; referred: number }> = {
  pro:        { referrer: 1500, referred: 500  },
  enterprise: { referrer: 5000, referred: 2000 },
  free:       { referrer: 0,    referred: 0    },
};
const RECURRING_PCT    = 0.10;   // 10% of amount_cents → pts (1 USD = 100 pts)
const RECURRING_MAX    = 12;
const L2_PCT           = 0.05;   // 5%
const L2_MAX           = 6;

const STREAM     = 'events:payment.received';
const GROUP      = 'referral-user-service';    // separate group from tenant referral consumer
const CONSUMER   = `referral-user-${process.pid}`;

@Injectable()
export class ConsumeUserPaymentUseCase implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(ConsumeUserPaymentUseCase.name);
  private client: Redis;
  private running = false;

  constructor(
    private readonly relRepo: UserReferralRelationshipRepository,
    private readonly pointRepo: UserPointRepository,
    private readonly processedRepo: ProcessedEventRepository,
    private readonly config: ConfigService,
  ) {}

  async onModuleInit() {
    const url = this.config.get<string>('REDIS_URL', 'redis://localhost:6379');
    this.client = new Redis(url);
    try {
      await this.client.xgroup('CREATE', STREAM, GROUP, '0', 'MKSTREAM');
    } catch { /* group exists */ }
    this.running = true;
    this.loop().catch((e) => this.logger.error(`User payment loop error: ${e.message}`));
  }

  async onModuleDestroy() {
    this.running = false;
    this.client.disconnect();
  }

  private async loop() {
    while (this.running) {
      try {
        const res = await this.client.xreadgroup(
          'GROUP', GROUP, CONSUMER,
          'COUNT', '10', 'BLOCK', '5000',
          'STREAMS', STREAM, '>',
        ) as Array<[string, Array<[string, string[]]>]> | null;

        if (!res) continue;

        for (const [, messages] of res) {
          for (const [id, fields] of messages) {
            const record: Record<string, string> = {};
            for (let i = 0; i < fields.length; i += 2) record[fields[i]] = fields[i + 1];
            await this.process(id, record);
            await this.client.xack(STREAM, GROUP, id);
          }
        }
      } catch (e: any) {
        if (this.running) {
          this.logger.error(`Redis error: ${e.message}`);
          await new Promise((r) => setTimeout(r, 5000));
        }
      }
    }
  }

  private async process(msgId: string, record: Record<string, string>) {
    try {
      const payload: PaymentPayload = JSON.parse(record['data'] ?? '{}');
      const userId = payload.userId;
      if (!userId) return; // legacy event without userId — skip user-level processing

      const eventId = `user-payment:${payload.paymentId ?? msgId}`;
      if (await this.processedRepo.hasProcessed(eventId)) return;

      // Find direct (level-1) referral relationship for this user
      const level1 = await this.relRepo.findByReferredUserId(userId);
      if (!level1) {
        await this.processedRepo.markProcessed(eventId, 'payment.received');
        return;
      }

      const isFirst = level1.status === 'PENDING';
      const plan = (payload.planName ?? 'pro').toLowerCase();

      if (isFirst) {
        await this.relRepo.updateStatus(level1.id, 'ACTIVE', { activatedAt: new Date() });

        const rules = FIRST_PAYMENT_POINTS[plan] ?? FIRST_PAYMENT_POINTS['pro'];

        if (rules.referrer > 0) {
          await this.pointRepo.addTransaction(
            level1.referrerUserId,
            rules.referrer,
            'REFERRAL_FIRST_PAYMENT',
            level1.id,
            `First payment by referred user (${plan})`,
          );
        }
        if (rules.referred > 0) {
          await this.pointRepo.addTransaction(
            userId,
            rules.referred,
            'REFERRAL_FIRST_PAYMENT',
            level1.id,
            `Bonus for subscribing via referral (${plan})`,
          );
        }

        await this.relRepo.updateStatus(level1.id, 'REWARDED', { rewardedAt: new Date() });
        this.logger.log(`User referral first-payment: referrer=${level1.referrerUserId} +${rules.referrer} pts`);

      } else if (level1.status === 'ACTIVE' || level1.status === 'REWARDED') {
        const recurringCount = await this.relRepo.countRecurringRewards(level1.id);
        if (recurringCount < RECURRING_MAX) {
          const pts = Math.floor(payload.amount * RECURRING_PCT / 100);  // cents→pts (1USD=100pts)
          if (pts > 0) {
            await this.pointRepo.addTransaction(
              level1.referrerUserId,
              pts,
              'REFERRAL_RECURRING',
              level1.id,
              `Recurring reward month ${recurringCount + 1}`,
            );
          }
        }

        // Level-2 indirect reward
        const level2Rows = await this.relRepo.findByReferrerUserId(level1.referrerUserId);
        const l2 = level2Rows.items.find((r) => r.referredUserId === userId && r.level === 2);
        if (l2) {
          const l2Count = await this.relRepo.countRecurringRewards(l2.id);
          if (l2Count < L2_MAX) {
            const l2Pts = Math.floor(payload.amount * L2_PCT / 100);
            if (l2Pts > 0) {
              await this.pointRepo.addTransaction(
                l2.referrerUserId,
                l2Pts,
                'REFERRAL_L2',
                l2.id,
                `Level-2 recurring reward`,
              );
            }
          }
        }
      }

      await this.processedRepo.markProcessed(eventId, 'payment.received');
    } catch (e: any) {
      this.logger.error(`Failed to process user payment ${msgId}: ${e.message}`);
    }
  }
}
