import { WagonStatus } from '@edr/types';
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource, FindOptionsOrder, FindOptionsWhere, ILike } from 'typeorm';
import { CreateWagonDto } from './dto/create-wagon.dto';
import { ListWagonsQueryDto } from './dto/list-wagons-query.dto';
import { UpdateWagonDto } from './dto/update-wagon.dto';
import { AssignWagonToTrainDto } from './dto/assign-wagon-to-train.dto';
import { ReorderWagonsDto } from './dto/reorder-wagons.dto';
import { Wagon } from './entities/wagon.entity';
import { Train } from '../trains/entities/train.entity';

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

  async create(dto: CreateWagonDto): Promise<Wagon> {
    const wagon = this.wagonRepo.create({
      ...dto,
      status: dto.status ?? WagonStatus.Available,
    });
    // Convert undefined to null for nullable fields
    if (dto.trainId === undefined) wagon.trainId = null;
    if (dto.sequenceNumber === undefined) wagon.sequenceNumber = null;
    if (dto.currentYardId === undefined) wagon.currentYardId = null;
    return this.wagonRepo.save(wagon);
  }

  async findAll(query: ListWagonsQueryDto = {}): Promise<Wagon[]> {
    const where: FindOptionsWhere<Wagon>[] | FindOptionsWhere<Wagon> = [];
    const search = query.search?.trim();
    const trainId = query.trainId?.trim();
    const wagonTypeId = query.wagonTypeId?.trim();
    const filters: FindOptionsWhere<Wagon> = {
      ...(query.status ? { status: query.status } : {}),
      ...(query.currentYardId ? { currentYardId: query.currentYardId } : {}),
      ...(trainId ? { trainId } : {}),
      ...(wagonTypeId ? { wagonTypeId } : {}),
    };

    if (search) {
      where.push({
        wagonNumber: ILike(`%${search}%`),
        ...filters,
      });
    }

    const sortBy = ['wagonNumber', 'tareWeight', 'maxPayloadWeight', 'status', 'currentYardId', 'sequenceNumber'].includes(query.sortBy ?? '')
      ? (query.sortBy as keyof Wagon)
      : 'wagonNumber';
    const sortOrder = query.sortOrder?.toUpperCase() === 'DESC' ? 'DESC' : 'ASC';

    return this.wagonRepo.find({
      where: search ? where : filters,
      relations: { currentYard: true },
      order: { [sortBy]: sortOrder } as FindOptionsOrder<Wagon>,
      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<Wagon> {
    const wagon = await this.wagonRepo.findOne({
      where: { id },
      relations: { currentYard: true },
    });
    if (!wagon) throw new NotFoundException(`Wagon ${id} not found`);
    return wagon;
  }

  async update(id: string, dto: UpdateWagonDto): Promise<Wagon> {
    const wagon = await this.findById(id);
    Object.assign(wagon, dto);
    return this.wagonRepo.save(wagon);
  }

  async remove(id: string): Promise<void> {
    const wagon = await this.findById(id);
    await this.wagonRepo.remove(wagon);
  }

  async assignToTrain(wagonId: string, dto: AssignWagonToTrainDto): Promise<Wagon> {
    const wagon = await this.findById(wagonId);
    if (wagon.status === WagonStatus.Assigned) {
      throw new ConflictException('Wagon already assigned to a train');
    }

    const train = await this.trainRepo.findOne({ where: { id: dto.trainId } });
    if (!train) throw new NotFoundException('Train not found');

    let sequence: number | null = dto.sequenceNumber ?? null;
    if (sequence === null) {
      const maxSeq = await this.wagonRepo
        .createQueryBuilder('w')
        .select('MAX(w.sequenceNumber)', 'max')
        .where('w.trainId = :trainId', { trainId: train.id })
        .getRawOne();
      sequence = (maxSeq?.max ?? 0) + 1;
    }

    wagon.trainId = train.id;
    wagon.sequenceNumber = sequence;
    wagon.status = WagonStatus.Assigned;
    return this.wagonRepo.save(wagon);
  }

  async unassignFromTrain(wagonId: string): Promise<Wagon> {
    const wagon = await this.findById(wagonId);
    wagon.trainId = null;
    wagon.sequenceNumber = null;
    wagon.status = WagonStatus.Available;
    return this.wagonRepo.save(wagon);
  }

  async reorderWagons(_trainId: string, dto: ReorderWagonsDto): Promise<void> {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();
    try {
      for (let i = 0; i < dto.wagonIds.length; i++) {
        await queryRunner.manager.update(Wagon, dto.wagonIds[i], { sequenceNumber: i + 1 });
      }
      await queryRunner.commitTransaction();
    } catch (err) {
      await queryRunner.rollbackTransaction();
      throw err;
    } finally {
      await queryRunner.release();
    }
  }
}
