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 { 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,
  ) {}

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

    for (let i = 0; i < events.length; i++) {
      try {
        validLogs.push(this.toEventLog(events[i]));
      } catch (e) {
        errors.push(`Event[${i}]: ${e.message}`);
      }
    }

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

    await this.eventLogRepository.batchInsert(validLogs);

    // Upsert device profiles
    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);

    // Update realtime DAU (HyperLogLog)
    const todayKey = formatToDateKey(new Date());
    for (const log of validLogs) {
      if (log.eventName.isDauEvent()) {
        await this.redisService.pfadd(`presence:dau:${todayKey}`, log.dauIdentifier);
        await this.redisService.expire(`presence:dau:${todayKey}`, 86400 * 3);
      }
    }

    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,
      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 ?? {}),
    });
  }
}
