import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Redis from 'ioredis';
import { UsageAggregateRepository } from '../../infrastructure/repositories/usage-aggregate.repository';

interface UsageRecordedEvent {
  tenantId: string;
  taskId: string;
  inputTokens: number;
  outputTokens: number;
  totalTokens: number;
  costUsd: number;
  model: string;
  recordedAt: string;
}

const STREAM_KEY = 'events:usage.recorded';
const CONSUMER_GROUP = 'billing-service';
const CONSUMER_NAME = 'billing-consumer-1';

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

  constructor(
    private readonly usageAggRepo: UsageAggregateRepository,
    private readonly configService: ConfigService,
  ) {}

  async onModuleInit() {
    const redisUrl = this.configService.get<string>('REDIS_URL', 'redis://localhost:6379');
    this.client = new Redis(redisUrl);

    // Create consumer group (ignore error if already exists)
    try {
      await this.client.xgroup('CREATE', STREAM_KEY, CONSUMER_GROUP, '0', 'MKSTREAM');
    } catch {
      // Group already exists
    }

    this.running = true;
    this.consumeLoop().catch((err) =>
      this.logger.error(`Usage consumer loop error: ${err.message}`),
    );
  }

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

  private async consumeLoop() {
    while (this.running) {
      try {
        const response = await this.client.xreadgroup(
          'GROUP', CONSUMER_GROUP, CONSUMER_NAME,
          'COUNT', '10',
          'BLOCK', '5000',
          'STREAMS', STREAM_KEY, '>',
        ) as Array<[string, Array<[string, string[]]>]> | null;

        if (!response) continue;

        for (const [, messages] of response) {
          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.processMessage(id, record);
            await this.client.xack(STREAM_KEY, CONSUMER_GROUP, id);
          }
        }
      } catch (err) {
        if (this.running) {
          this.logger.error(`Redis consumer error: ${err.message}`);
          await new Promise((r) => setTimeout(r, 5000));
        }
      }
    }
  }

  private async processMessage(id: string, record: Record<string, string>) {
    try {
      const payload: UsageRecordedEvent = JSON.parse(record['data'] ?? '{}');
      if (!payload.tenantId || !payload.inputTokens) return;

      const recordedAt = new Date(payload.recordedAt ?? Date.now());
      const year = recordedAt.getFullYear();
      const month = recordedAt.getMonth() + 1;

      await this.usageAggRepo.upsertTokens(
        payload.tenantId,
        year,
        month,
        payload.inputTokens ?? 0,
        payload.outputTokens ?? 0,
        payload.costUsd ?? 0,
      );
    } catch (err) {
      this.logger.error(`Failed to process usage message ${id}: ${err.message}`);
    }
  }
}
