import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { Inject, Injectable } from '@nestjs/common';
import { RecordEventsCommand, EventItemDto } from './record-events.command';
import { EventLog } from '../../../domain/entities/event-log.entity';
import { DeviceProfile } from '../../../domain/entities/device-profile.entity';
import { InstallId } from '../../../domain/value-objects/install-id.vo';
import { EventName } from '../../../domain/value-objects/event-name.vo';
import { EventProperties } from '../../../domain/value-objects/event-properties.vo';
import {
  IEventLogRepository,
  EVENT_LOG_REPOSITORY,
} from '../../../domain/repositories/event-log.repository.interface';
import {
  IDeviceProfileRepository,
  DEVICE_PROFILE_REPOSITORY,
} from '../../../domain/repositories/device-profile.repository.interface';
import { RedisService } from '../../../infrastructure/redis/redis.service';
import { EventPublisherService } from '../../../infrastructure/kafka/event-publisher.service';
import { MetricsService } from '../../../infrastructure/metrics/metrics.service';
import { SessionStartedEvent } from '../../../domain/events/session-started.event';
import { formatToDateKey } from '../../../shared/utils/timezone.util';

export interface RecordEventsResult {
  accepted: number;
  failed: number;
  errors?: string[];
}

@Injectable()
@CommandHandler(RecordEventsCommand)
export class RecordEventsHandler implements ICommandHandler<RecordEventsCommand> {
  constructor(
    @Inject(EVENT_LOG_REPOSITORY)
    private readonly eventLogRepository: IEventLogRepository,
    @Inject(DEVICE_PROFILE_REPOSITORY)
    private readonly deviceProfileRepository: IDeviceProfileRepository,
    private readonly redisService: RedisService,
    private readonly eventPublisher: EventPublisherService,
    private readonly metricsService: MetricsService,
  ) {}

  async execute(command: RecordEventsCommand): Promise<RecordEventsResult> {
    const startTime = Date.now();
    const { events } = command;
    const errors: string[] = [];
    const validLogs: EventLog[] = [];

    // 1. 验证并转换事件
    for (let i = 0; i < events.length; i++) {
      try {
        const log = this.toEventLog(events[i]);
        validLogs.push(log);
      } catch (e) {
        errors.push(`Event[${i}]: ${e.message}`);
      }
    }

    if (validLogs.length === 0) {
      return { accepted: 0, failed: events.length, errors };
    }

    // 2. 批量写入事件流水
    await this.eventLogRepository.batchInsert(validLogs);

    // 3. Upsert 设备档案（大厂标准：事件流水 + 设备快照分离）
    // 只有携带设备信息的事件才更新档案，每台设备一行，O(devices) 而非 O(events)
    const deviceProfiles = validLogs
      .filter((log) => log.installId.value)
      .map((log) =>
        DeviceProfile.create({
          installId: log.installId.value,
          userId: log.userId,
          deviceBrand: log.deviceBrand,
          deviceModel: log.deviceModel,
          deviceOs: log.deviceOs,
          appVersion: log.appVersion,
          locale: log.locale,
          eventCount: 1,
        }),
      );
    await this.deviceProfileRepository.upsertBatch(deviceProfiles);

    // 4. 更新实时DAU (HyperLogLog)
    const todayKey = formatToDateKey(new Date());
    for (const log of validLogs) {
      if (log.eventName.isDauEvent()) {
        await this.redisService.pfadd(
          `analytics:dau:${todayKey}`,
          log.dauIdentifier,
        );

        // 5. 发布领域事件
        await this.eventPublisher.publish(
          SessionStartedEvent.EVENT_NAME,
          new SessionStartedEvent(
            log.userId,
            log.installId.value,
            log.eventTime,
            {
              appVersion: log.appVersion ?? log.properties.appVersion,
              os: log.deviceOs ?? log.properties.os,
              osVersion: log.deviceOs ?? log.properties.osVersion,
            },
          ),
        );
      }
    }

    // 6. 记录 Prometheus 指标
    for (const log of validLogs) {
      this.metricsService.recordEvent(log.eventName.value);
    }
    const durationSeconds = (Date.now() - startTime) / 1000;
    this.metricsService.recordEventBatchDuration(durationSeconds);

    return {
      accepted: validLogs.length,
      failed: events.length - validLogs.length,
      errors: errors.length > 0 ? errors : undefined,
    };
  }

  private toEventLog(dto: EventItemDto): EventLog {
    return EventLog.create({
      userId: dto.userId ?? null,  // userSerialNum string, e.g. "D25121400005"
      installId: InstallId.fromString(dto.installId),
      eventName: EventName.fromString(dto.eventName),
      eventTime: new Date(dto.clientTs * 1000),
      deviceBrand: dto.deviceBrand ?? null,
      deviceModel: dto.deviceModel ?? null,
      deviceOs: dto.deviceOs ?? null,
      appVersion: dto.appVersion ?? null,
      locale: dto.locale ?? null,
      properties: EventProperties.fromData(dto.properties ?? {}),
    });
  }
}
