import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { IEventLogRepository, DauQueryResult } from '../../../domain/repositories/event-log.repository.interface';
import { EventLog } from '../../../domain/entities/event-log.entity';
import { EventName } from '../../../domain/value-objects/event-name.vo';
import { EventLogMapper } from '../mappers/event-log.mapper';

@Injectable()
export class EventLogRepositoryImpl implements IEventLogRepository {
  constructor(
    private readonly prisma: PrismaService,
    private readonly mapper: EventLogMapper,
  ) {}

  async batchInsert(logs: EventLog[]): Promise<void> {
    const data = logs.map((log) => this.mapper.toPersistence(log));
    await this.prisma.eventLog.createMany({ data });
  }

  async insert(log: EventLog): Promise<EventLog> {
    const data = this.mapper.toPersistence(log);
    const created = await this.prisma.eventLog.create({ data });
    return this.mapper.toDomain(created);
  }

  async queryDau(eventName: EventName, startTime: Date, endTime: Date): Promise<DauQueryResult> {
    const result = await this.prisma.$queryRaw<
      { total: bigint; province: string | null; city: string | null; count: bigint }[]
    >`
      WITH base AS (
        SELECT
          COALESCE(user_id::text, install_id) AS identifier,
          properties->>'province' AS province,
          properties->>'city' AS city
        FROM presence_event_log
        WHERE event_name = ${eventName.value}
          AND event_time >= ${startTime}
          AND event_time < ${endTime}
      ),
      unique_users AS (
        SELECT DISTINCT identifier, province, city FROM base
      )
      SELECT
        COUNT(DISTINCT identifier) AS total,
        province,
        city,
        COUNT(*) AS count
      FROM unique_users
      GROUP BY GROUPING SETS ((), (province), (city))
    `;

    let total = 0;
    const byProvince = new Map<string, number>();
    const byCity = new Map<string, number>();

    for (const row of result) {
      if (row.province === null && row.city === null) {
        total = Number(row.total);
      } else if (row.province !== null && row.city === null) {
        byProvince.set(row.province, Number(row.count));
      } else if (row.city !== null && row.province === null) {
        byCity.set(row.city, Number(row.count));
      }
    }

    return { total, byProvince, byCity };
  }

  async findByTimeRange(eventName: EventName, startTime: Date, endTime: Date, limit?: number): Promise<EventLog[]> {
    const records = await this.prisma.eventLog.findMany({
      where: { eventName: eventName.value, eventTime: { gte: startTime, lt: endTime } },
      orderBy: { eventTime: 'desc' },
      take: limit,
    });
    return records.map((r) => this.mapper.toDomain(r));
  }
}
