import type { DataSource } from 'typeorm';

import type { IRatingEventRepository, FindByUserIdOptions, PaginatedQueryOptions, PaginatedResult } from '@core/identity/domain/repositories/IRatingEventRepository';
import type { RatingEvent } from '@core/identity/domain/entities/RatingEvent';
import type { RatingEventId } from '@core/identity/domain/value-objects/RatingEventId';

import { RatingEventOrmEntity } from '../entities/RatingEventOrmEntity';
import { RatingEventOrmMapper } from '../mappers/RatingEventOrmMapper';

/**
 * TypeORM Implementation: IRatingEventRepository
 * 
 * Persists rating events in the ledger with efficient querying by userId
 * and ordering for snapshot computation.
 */
export class TypeOrmRatingEventRepository implements IRatingEventRepository {
  constructor(private readonly dataSource: DataSource) {}

  async save(event: RatingEvent): Promise<RatingEvent> {
    const repo = this.dataSource.getRepository(RatingEventOrmEntity);
    const entity = RatingEventOrmMapper.toOrmEntity(event);
    await repo.save(entity);
    return event;
  }

  async findByUserId(userId: string, options?: FindByUserIdOptions): Promise<RatingEvent[]> {
    const repo = this.dataSource.getRepository(RatingEventOrmEntity);
    
    const query = repo
      .createQueryBuilder('event')
      .where('event.userId = :userId', { userId })
      .orderBy('event.occurredAt', 'ASC')
      .addOrderBy('event.createdAt', 'ASC')
      .addOrderBy('event.id', 'ASC');

    if (options?.afterId) {
      query.andWhere('event.id > :afterId', { afterId: options.afterId.value });
    }

    if (options?.limit) {
      query.limit(options.limit);
    }

    const entities = await query.getMany();
    return entities.map(entity => RatingEventOrmMapper.toDomain(entity));
  }

  async findByIds(ids: RatingEventId[]): Promise<RatingEvent[]> {
    if (ids.length === 0) {
      return [];
    }

    const repo = this.dataSource.getRepository(RatingEventOrmEntity);
    const idValues = ids.map(id => id.value);
    
    const entities = await repo
      .createQueryBuilder('event')
      .where('event.id IN (:...ids)', { ids: idValues })
      .orderBy('event.occurredAt', 'ASC')
      .addOrderBy('event.createdAt', 'ASC')
      .addOrderBy('event.id', 'ASC')
      .getMany();

    return entities.map(entity => RatingEventOrmMapper.toDomain(entity));
  }

  async getAllByUserId(userId: string): Promise<RatingEvent[]> {
    const repo = this.dataSource.getRepository(RatingEventOrmEntity);
    
    const entities = await repo
      .createQueryBuilder('event')
      .where('event.userId = :userId', { userId })
      .orderBy('event.occurredAt', 'ASC')
      .addOrderBy('event.createdAt', 'ASC')
      .addOrderBy('event.id', 'ASC')
      .getMany();

    return entities.map(entity => RatingEventOrmMapper.toDomain(entity));
  }

  async findEventsPaginated(userId: string, options?: PaginatedQueryOptions): Promise<PaginatedResult<RatingEvent>> {
    const repo = this.dataSource.getRepository(RatingEventOrmEntity);
    
    const query = repo
      .createQueryBuilder('event')
      .where('event.userId = :userId', { userId });

    // Apply filters
    if (options?.filter) {
      const filter = options.filter;
      
      if (filter.dimensions) {
        query.andWhere('event.dimension IN (:...dimensions)', { dimensions: filter.dimensions });
      }
      
      if (filter.sourceTypes) {
        query.andWhere('event.sourceType IN (:...sourceTypes)', { sourceTypes: filter.sourceTypes });
      }
      
      if (filter.from) {
        query.andWhere('event.occurredAt >= :from', { from: filter.from });
      }
      
      if (filter.to) {
        query.andWhere('event.occurredAt <= :to', { to: filter.to });
      }
      
      if (filter.reasonCodes) {
        query.andWhere('event.reasonCode IN (:...reasonCodes)', { reasonCodes: filter.reasonCodes });
      }
      
      if (filter.visibility) {
        query.andWhere('event.visibility = :visibility', { visibility: filter.visibility });
      }
    }

    // Get total count
    const total = await query.getCount();

    // Apply pagination
    const limit = options?.limit ?? 10;
    const offset = options?.offset ?? 0;
    
    query
      .orderBy('event.occurredAt', 'ASC')
      .addOrderBy('event.createdAt', 'ASC')
      .addOrderBy('event.id', 'ASC')
      .limit(limit)
      .offset(offset);

    const entities = await query.getMany();
    const items = entities.map(entity => RatingEventOrmMapper.toDomain(entity));

    const hasMore = offset + limit < total;
    const nextOffset = hasMore ? offset + limit : undefined;

    const result: PaginatedResult<RatingEvent> = {
      items,
      total,
      limit,
      offset,
      hasMore
    };
    
    if (nextOffset !== undefined) {
      result.nextOffset = nextOffset;
    }

    return result;
  }
}
