import type { DataSource } from 'typeorm';

import type { TeamRatingEvent } from '@core/racing/domain/entities/TeamRatingEvent';
import type { FindByTeamIdOptions, PaginatedQueryOptions, PaginatedResult, TeamRatingEventRepository } from '@core/racing/domain/repositories/TeamRatingEventRepository';
import type { TeamRatingEventId } from '@core/racing/domain/value-objects/TeamRatingEventId';

import { TeamRatingEventOrmEntity } from '../entities/TeamRatingEventOrmEntity';
import { TeamRatingEventOrmMapper } from '../mappers/TeamRatingEventOrmMapper';

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

  async save(event: TeamRatingEvent): Promise<TeamRatingEvent> {
    const repo = this.dataSource.getRepository(TeamRatingEventOrmEntity);
    const entity = TeamRatingEventOrmMapper.toOrmEntity(event);
    await repo.save(entity);
    return event;
  }

  async findByTeamId(teamId: string, options?: FindByTeamIdOptions): Promise<TeamRatingEvent[]> {
    const repo = this.dataSource.getRepository(TeamRatingEventOrmEntity);
    
    const query = repo
      .createQueryBuilder('event')
      .where('event.teamId = :teamId', { teamId })
      .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 => TeamRatingEventOrmMapper.toDomain(entity));
  }

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

    const repo = this.dataSource.getRepository(TeamRatingEventOrmEntity);
    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 => TeamRatingEventOrmMapper.toDomain(entity));
  }

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

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

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

    // 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.source.type 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.reason.code IN (:...reasonCodes)', { reasonCodes: filter.reasonCodes });
      }
      
      if (filter.visibility) {
        query.andWhere('event.visibility.public = :visibility', { visibility: filter.visibility === 'public' });
      }
    }

    // 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 => TeamRatingEventOrmMapper.toDomain(entity));

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

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

    return result;
  }
}