import { WagonStatus } from '@edr/types';
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, FindOptionsOrder, FindOptionsWhere, ILike, Repository } from 'typeorm';
import { CreateTrainDto } from './dto/create-train.dto';
import { UpdateTrainDto } from './dto/update-train.dto';
import { TrainLocomotive } from './entities/train-locomotive.entity';
import { Train } from './entities/train.entity';
import { Wagon } from '../wagons/entities/wagon.entity';

@Injectable()
export class TrainsService {
  constructor(
    @InjectRepository(Train)
    private readonly trainRepo: Repository<Train>,
    private readonly dataSource: DataSource,
  ) {}

  create(dto: CreateTrainDto): Promise<Train> {
    const train = this.trainRepo.create(dto);
    return this.trainRepo.save(train);
  }

  findAll(query: Record<string, string | undefined> = {}): Promise<Train[]> {
    const where: FindOptionsWhere<Train>[] | FindOptionsWhere<Train> = [];
    const search = query.search?.trim();
    const status = query.status?.trim();

    if (search) {
      where.push({ code: ILike(`%${search}%`), ...(status ? { status: status as Train['status'] } : {}) });
      where.push({ trainNumber: ILike(`%${search}%`), ...(status ? { status: status as Train['status'] } : {}) });
      where.push({ trainName: ILike(`%${search}%`), ...(status ? { status: status as Train['status'] } : {}) });
    }

    const sortBy = ['code', 'trainNumber', 'trainName', 'capacityTons', 'status'].includes(query.sortBy ?? '')
      ? (query.sortBy as keyof Train)
      : 'code';
    const sortOrder = query.sortOrder?.toUpperCase() === 'DESC' ? 'DESC' : 'ASC';

    return this.trainRepo.find({
      where: search ? where : status ? { status: status as Train['status'] } : {},
      order: { [sortBy]: sortOrder } as FindOptionsOrder<Train>,
      skip: query.page && query.limit ? (Number(query.page) - 1) * Number(query.limit) : undefined,
      take: query.limit ? Number(query.limit) : undefined,
    });
  }

  async findById(id: string): Promise<Train> {
    const train = await this.trainRepo.findOne({ where: { id } });
    if (!train) throw new NotFoundException(`Train ${id} not found`);
    return train;
  }

  async update(id: string, dto: UpdateTrainDto): Promise<Train> {
    const train = await this.findById(id);
    Object.assign(train, dto);
    // Convert undefined to null for optional fields if needed
    return this.trainRepo.save(train);
  }

  /**
   * Delete a built train. Blocked while it still has a live (DRAFT/SCHEDULED/
   * DISPATCHED) schedule; otherwise its wagons are freed (back to AVAILABLE)
   * and its locomotive links dropped so nothing is stranded, then the train is
   * soft-deleted. Mirrors TrainBuilderService.disband but prefers softRemove.
   */
  async remove(id: string): Promise<void> {
    await this.dataSource.transaction(async (manager) => {
      const train = await manager.getRepository(Train).findOne({ where: { id } });
      if (!train) throw new NotFoundException(`Train ${id} not found`);

      const active: { count: string }[] = await manager.query(
        `SELECT COUNT(*)::text AS count
           FROM freight.train_schedules ts
           JOIN freight.train_sets tset ON tset.id = ts.train_set_id
          WHERE tset.train_id = $1
            AND ts.deleted_at IS NULL
            AND ts.status IN ('DRAFT', 'SCHEDULED', 'DISPATCHED')`,
        [id],
      );
      if (Number(active[0]?.count ?? 0) > 0) {
        throw new ConflictException(
          'Train has active schedules; cancel them before deleting the train',
        );
      }

      await manager
        .getRepository(Wagon)
        .update(
          { trainId: train.id },
          { trainId: null, sequenceNumber: null, status: WagonStatus.Available },
        );
      await manager.getRepository(TrainLocomotive).delete({ trainId: train.id });
      await manager.getRepository(Train).softRemove(train);
    });
  }
}
