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

import type { IPageViewRepository } from '@core/analytics/application/repositories/IPageViewRepository';
import type { EntityType } from '@core/analytics/domain/types/PageView';
import { PageView } from '@core/analytics/domain/entities/PageView';

import { PageViewOrmEntity } from '../entities/PageViewOrmEntity';
import { PageViewOrmMapper } from '../mappers/PageViewOrmMapper';

type CountRow = { count: string | number } | null | undefined;

export class TypeOrmPageViewRepository implements IPageViewRepository {
  constructor(
    private readonly pageViewRepo: Repository<PageViewOrmEntity>,
    private readonly mapper: PageViewOrmMapper,
  ) {}

  async save(pageView: PageView): Promise<void> {
    await this.pageViewRepo.save(this.mapper.toOrmEntity(pageView));
  }

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

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

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

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

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

  async findBySession(sessionId: string): Promise<PageView[]> {
    const entities = await this.pageViewRepo.find({
      where: { sessionId },
      order: { timestamp: 'DESC' },
    });

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

  async countByEntityId(entityType: EntityType, entityId: string, since?: Date): Promise<number> {
    if (!since) {
      return this.pageViewRepo.count({ where: { entityType, entityId } });
    }

    return this.pageViewRepo.count({
      where: { entityType, entityId, timestamp: MoreThanOrEqual(since) },
    });
  }

  async countUniqueVisitors(entityType: EntityType, entityId: string, since?: Date): Promise<number> {
    const qb = this.pageViewRepo
      .createQueryBuilder('pv')
      .select('COUNT(DISTINCT COALESCE(pv.visitorId, pv.sessionId))', 'count')
      .where('pv.entityType = :entityType', { entityType })
      .andWhere('pv.entityId = :entityId', { entityId });

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

    const row: CountRow = await qb.getRawOne();
    const raw = row?.count;

    if (typeof raw === 'number') return raw;
    if (typeof raw === 'string') return Number.parseInt(raw, 10);
    return 0;
  }
}