import { BaseRepository } from '@edr/api-common';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

import { Locomotive, type LocomotiveStatus, type LocomotiveType } from './entities/locomotive.entity';
import { TrainLocomotive } from '../trains/entities/train-locomotive.entity';

@Injectable()
export class LocomotivesRepository extends BaseRepository<Locomotive> {
  constructor(
    @InjectRepository(Locomotive)
    repository: Repository<Locomotive>,
  ) {
    super(repository);
  }

  /**
   * List locomotives for the train-builder coupling picker: the usual
   * status/type/yard filters, plus optional exclusion of any loco already
   * coupled to a built train. `keepTrainId` spares that one train's own locos
   * from the exclusion so they stay selectable while editing its consist.
   */
  findForCoupling(opts: {
    status?: LocomotiveStatus;
    locomotiveType?: LocomotiveType;
    currentYardId?: string;
    excludeCoupled?: boolean;
    keepTrainId?: string;
  }): Promise<Locomotive[]> {
    const qb = this.repository
      .createQueryBuilder('locomotive')
      .leftJoinAndSelect('locomotive.currentYard', 'currentYard')
      .orderBy('locomotive.code', 'ASC');

    if (opts.status) qb.andWhere('locomotive.status = :status', { status: opts.status });
    if (opts.locomotiveType)
      qb.andWhere('locomotive.locomotiveType = :type', { type: opts.locomotiveType });
    if (opts.currentYardId)
      qb.andWhere('locomotive.currentYardId = :yardId', { yardId: opts.currentYardId });

    if (opts.excludeCoupled) {
      // NOT EXISTS a link to a DIFFERENT train. Own-train links are kept so the
      // consist being edited still lists its current locomotives.
      const sub = this.repository.manager
        .getRepository(TrainLocomotive)
        .createQueryBuilder('tl')
        .select('1')
        .where('tl.locomotiveId = locomotive.id');
      if (opts.keepTrainId) {
        sub.andWhere('tl.trainId != :keepTrainId', { keepTrainId: opts.keepTrainId });
      }
      qb.andWhere(`NOT EXISTS (${sub.getQuery()})`).setParameters(sub.getParameters());
    }

    return qb.getMany();
  }

  /**
   * A live locomotive already holding this name, compared the same way the
   * `UQ_locomotives_name_active` index compares: case- and whitespace-
   * insensitive, soft-deleted rows excluded. `excludeId` skips the row being
   * updated so it can keep its own name.
   */
  findByName(name: string, excludeId?: string): Promise<Locomotive | null> {
    const qb = this.repository
      .createQueryBuilder('locomotive')
      .where('lower(btrim(locomotive.name)) = lower(btrim(:name))', { name });

    if (excludeId) {
      qb.andWhere('locomotive.id != :excludeId', { excludeId });
    }

    // createQueryBuilder already filters soft-deleted rows (no withDeleted()).
    return qb.getOne();
  }
}
