import { BadRequestException, ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { DataSource, ILike } from 'typeorm';

import { Yard } from '../rule-engine/entities/yard.entity';
import { CreateRouteDto } from './dto/create-route.dto';
import { FilterRoutesDto } from './dto/filter-routes.dto';
import { UpdateRouteDto } from './dto/update-route.dto';
import { RouteMilestone } from './entities/route-milestone.entity';
import { Route } from './entities/route.entity';
import { RoutesRepository } from './routes.repository';

@Injectable()
export class RoutesService {
  constructor(
    private readonly dataSource: DataSource,
    private readonly routesRepository: RoutesRepository,
  ) {}

  findAll(filter: FilterRoutesDto): Promise<Route[]> {
    return this.routesRepository.findAll({
      where: {
        ...(filter.search ? { name: ILike(`%${filter.search.trim()}%`) } : {}),
        ...(filter.isActive !== undefined ? { isActive: filter.isActive } : {}),
      },
      relations: {
        originYard: true,
        destinationYard: true,
        milestones: { yard: true },
      },
      order: {
        name: 'ASC',
        milestones: { sequenceNo: 'ASC' },
      },
    });
  }

  async findById(id: string): Promise<Route> {
    const route = await this.dataSource.getRepository(Route).findOne({
      where: { id },
      relations: {
        originYard: true,
        destinationYard: true,
        milestones: { yard: true },
      },
      order: { milestones: { sequenceNo: 'ASC' } },
    });

    if (!route) {
      throw new NotFoundException(`Route ${id} not found`);
    }

    return route;
  }

  async create(dto: CreateRouteDto): Promise<Route> {
    await this.validateRouteName(dto.name);
    const validated = await this.validateMilestones(dto.milestones);

    const route = await this.dataSource.transaction(async (manager) => {
      const savedRoute = await manager.getRepository(Route).save(
        manager.getRepository(Route).create({
          name: dto.name.trim(),
          originYardId: validated.originYardId,
          destinationYardId: validated.destinationYardId,
          isActive: dto.isActive ?? true,
        }),
      );

      await manager.getRepository(RouteMilestone).save(
        validated.milestones.map((milestone) =>
          manager.getRepository(RouteMilestone).create({
            routeId: savedRoute.id,
            yardId: milestone.yardId,
            sequenceNo: milestone.sequenceNo,
          }),
        ),
      );

      return savedRoute;
    });

    return this.findById(route.id);
  }

  async update(id: string, dto: UpdateRouteDto): Promise<Route> {
    const existing = await this.findById(id);

    if (dto.name && dto.name.trim() !== existing.name) {
      await this.validateRouteName(dto.name, id);
    }

    const milestoneInput = dto.milestones
      ? await this.validateMilestones(dto.milestones)
      : null;

    await this.dataSource.transaction(async (manager) => {
      await manager.getRepository(Route).update(id, {
        name: dto.name?.trim() ?? existing.name,
        originYardId: milestoneInput?.originYardId ?? existing.originYardId,
        destinationYardId: milestoneInput?.destinationYardId ?? existing.destinationYardId,
        isActive: dto.isActive ?? existing.isActive,
      });

      if (milestoneInput) {
        await manager.getRepository(RouteMilestone).delete({ routeId: id });
        await manager.getRepository(RouteMilestone).save(
          milestoneInput.milestones.map((milestone) =>
            manager.getRepository(RouteMilestone).create({
              routeId: id,
              yardId: milestone.yardId,
              sequenceNo: milestone.sequenceNo,
            }),
          ),
        );
      }
    });

    return this.findById(id);
  }

  async deactivate(id: string): Promise<Route> {
    await this.findById(id);
    const updated = await this.routesRepository.update(id, { isActive: false });

    if (!updated) {
      throw new NotFoundException(`Route ${id} not found`);
    }

    return this.findById(id);
  }

  private async validateRouteName(name: string, routeId?: string) {
    const trimmedName = name.trim();
    const [existing] = await this.routesRepository.findAll({ where: { name: trimmedName } });

    if (existing && existing.id !== routeId) {
      throw new ConflictException(`Route name ${trimmedName} already exists`);
    }
  }

  private async validateMilestones(milestones: Array<{ yardId: string }>) {
    if (milestones.length < 2) {
      throw new BadRequestException('A route requires at least two yards');
    }

    const normalized = milestones.map((milestone, index) => ({
      yardId: milestone.yardId,
      sequenceNo: index + 1,
    }));

    const uniqueYardIds = [...new Set(normalized.map((milestone) => milestone.yardId))];
    const yards = await this.dataSource.getRepository(Yard).find({ where: uniqueYardIds.map((id) => ({ id })) });
    const yardIds = new Set(yards.map((yard) => yard.id));

    for (const milestone of normalized) {
      if (!yardIds.has(milestone.yardId)) {
        throw new BadRequestException(`Yard ${milestone.yardId} does not exist`);
      }
    }

    if (normalized[0].yardId === normalized[normalized.length - 1].yardId) {
      throw new BadRequestException('Origin and destination yards must be different');
    }

    return {
      originYardId: normalized[0].yardId,
      destinationYardId: normalized[normalized.length - 1].yardId,
      milestones: normalized,
    };
  }
}
