import { type Repository } from 'typeorm';

import type { IAnalyticsSnapshotRepository } from '@core/analytics/domain/repositories/IAnalyticsSnapshotRepository';
import type { SnapshotEntityType, SnapshotPeriod } from '@core/analytics/domain/types/AnalyticsSnapshot';
import { AnalyticsSnapshot } from '@core/analytics/domain/entities/AnalyticsSnapshot';

import { AnalyticsSnapshotOrmEntity } from '../entities/AnalyticsSnapshotOrmEntity';
import { AnalyticsSnapshotOrmMapper } from '../mappers/AnalyticsSnapshotOrmMapper';

export class TypeOrmAnalyticsSnapshotRepository implements IAnalyticsSnapshotRepository {
  constructor(
    private readonly snapshotRepo: Repository<AnalyticsSnapshotOrmEntity>,
    private readonly mapper: AnalyticsSnapshotOrmMapper,
  ) {}

  async save(snapshot: AnalyticsSnapshot): Promise<void> {
    await this.snapshotRepo.save(this.mapper.toOrmEntity(snapshot));
  }

  async findById(id: string): Promise<AnalyticsSnapshot | null> {
    const entity = await this.snapshotRepo.findOneBy({ id });
    return entity ? this.mapper.toDomain(entity) : null;
  }

  async findByEntity(entityType: SnapshotEntityType, entityId: string): Promise<AnalyticsSnapshot[]> {
    const entities = await this.snapshotRepo.find({
      where: { entityType, entityId },
      order: { endDate: 'DESC' },
    });

    return entities.map((e) => this.mapper.toDomain(e));
  }

  async findByPeriod(
    entityType: SnapshotEntityType,
    entityId: string,
    period: SnapshotPeriod,
    startDate: Date,
    endDate: Date,
  ): Promise<AnalyticsSnapshot | null> {
    const qb = this.snapshotRepo
      .createQueryBuilder('s')
      .where('s.entityType = :entityType', { entityType })
      .andWhere('s.entityId = :entityId', { entityId })
      .andWhere('s.period = :period', { period })
      .andWhere('s.startDate >= :startDate', { startDate })
      .andWhere('s.endDate <= :endDate', { endDate })
      .orderBy('s.endDate', 'DESC')
      .limit(1);

    const entity = await qb.getOne();
    return entity ? this.mapper.toDomain(entity) : null;
  }

  async findLatest(
    entityType: SnapshotEntityType,
    entityId: string,
    period: SnapshotPeriod,
  ): Promise<AnalyticsSnapshot | null> {
    const entity = await this.snapshotRepo.findOne({
      where: { entityType, entityId, period },
      order: { endDate: 'DESC' },
    });

    return entity ? this.mapper.toDomain(entity) : null;
  }

  async getHistoricalSnapshots(
    entityType: SnapshotEntityType,
    entityId: string,
    period: SnapshotPeriod,
    limit: number,
  ): Promise<AnalyticsSnapshot[]> {
    const entities = await this.snapshotRepo.find({
      where: { entityType, entityId, period },
      order: { endDate: 'DESC' },
      ...(typeof limit === 'number' ? { take: limit } : {}),
    });

    return entities.map((e) => this.mapper.toDomain(e));
  }
}