import { Between, MoreThanOrEqual, type FindOptionsWhere, type Repository } from 'typeorm';

import type { IEngagementRepository } from '@core/analytics/domain/repositories/IEngagementRepository';
import type { EngagementAction, EngagementEntityType } from '@core/analytics/domain/types/EngagementEvent';
import { EngagementEvent } from '@core/analytics/domain/entities/EngagementEvent';

import { EngagementEventOrmEntity } from '../entities/EngagementEventOrmEntity';
import { EngagementEventOrmMapper } from '../mappers/EngagementEventOrmMapper';

export class TypeOrmEngagementRepository implements IEngagementRepository {
  constructor(
    private readonly engagementRepo: Repository<EngagementEventOrmEntity>,
    private readonly mapper: EngagementEventOrmMapper,
  ) {}

  async save(event: EngagementEvent): Promise<void> {
    await this.engagementRepo.save(this.mapper.toOrmEntity(event));
  }

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

  async findByEntityId(entityType: EngagementEntityType, entityId: string): Promise<EngagementEvent[]> {
    const entities = await this.engagementRepo.find({
      where: { entityType, entityId },
      order: { timestamp: 'DESC' },
    });

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

  async findByAction(action: EngagementAction): Promise<EngagementEvent[]> {
    const entities = await this.engagementRepo.find({
      where: { action },
      order: { timestamp: 'DESC' },
    });

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

  async findByDateRange(startDate: Date, endDate: Date): Promise<EngagementEvent[]> {
    const entities = await this.engagementRepo.find({
      where: { timestamp: Between(startDate, endDate) },
      order: { timestamp: 'DESC' },
    });

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

  async countByAction(action: EngagementAction, entityId?: string, since?: Date): Promise<number> {
    const where: FindOptionsWhere<EngagementEventOrmEntity> = { action };

    if (entityId) {
      where.entityId = entityId;
    }

    if (since) {
      where.timestamp = MoreThanOrEqual(since);
    }

    return this.engagementRepo.count({ where });
  }

  async getSponsorClicksForEntity(entityId: string, since?: Date): Promise<number> {
    const actions: EngagementAction[] = ['click_sponsor_logo', 'click_sponsor_url'];

    const qb = this.engagementRepo
      .createQueryBuilder('e')
      .where('e.entityId = :entityId', { entityId })
      .andWhere('e.action IN (:...actions)', { actions });

    if (since) {
      qb.andWhere('e.timestamp >= :since', { since });
    }

    return qb.getCount();
  }
}