import { Injectable, NotFoundException, ConflictException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
import { CreateRouteDto, AddRouteStopDto, UpdateRouteDto } from './routes.dto';

@Injectable()
export class RoutesService {
  constructor(private prisma: PrismaService) {}

  // ── Route CRUD ─────────────────────────────────────────────────────────────

  async createRoute(dto: CreateRouteDto) {
    const existing = await this.prisma.route.findUnique({ where: { code: dto.code } });
    if (existing) throw new ConflictException(`Route code "${dto.code}" already exists`);

    if (dto.stops.length < 2) throw new BadRequestException('A route must have at least 2 stops');

    const seqs = dto.stops.map(s => s.sequence);
    if (new Set(seqs).size !== seqs.length) throw new ConflictException('Duplicate sequence numbers in stop list');

    const stationIds = [...new Set(dto.stops.map(s => s.stationId))];
    const stations = await this.prisma.station.findMany({ where: { id: { in: stationIds } } });
    if (stations.length !== stationIds.length) throw new BadRequestException('One or more station IDs not found');

    return this.prisma.route.create({
      data: {
        code: dto.code,
        name: dto.name,
        description: dto.description,
        effectiveFrom: new Date(dto.effectiveFrom),
        effectiveUntil: dto.effectiveUntil ? new Date(dto.effectiveUntil) : null,
        stops: {
          create: dto.stops.map(s => ({
            stationId: s.stationId,
            sequence: s.sequence,
            distanceKm: s.distanceKm,
          })),
        },
      },
      include: { stops: { include: { route: false }, orderBy: { sequence: 'asc' } } },
    });
  }

  async listRoutes(activeOnly = false) {
    return this.prisma.route.findMany({
      where: activeOnly ? { active: true } : undefined,
      include: {
        stops: { orderBy: { sequence: 'asc' } },
        _count: { select: { stops: true } },
      },
      orderBy: { code: 'asc' },
    });
  }

  async getRoute(id: string) {
    const route = await this.prisma.route.findUnique({
      where: { id },
      include: {
        stops: {
          orderBy: { sequence: 'asc' },
          include: {
            route: false,
          },
        },
      },
    });
    if (!route) throw new NotFoundException('Route not found');

    // Enrich stops with station details
    const stationIds = route.stops.map(s => s.stationId);
    const stations = await this.prisma.station.findMany({ where: { id: { in: stationIds } } });
    const stationMap = Object.fromEntries(stations.map(s => [s.id, s]));

    return {
      ...route,
      stops: route.stops.map(s => ({ ...s, station: stationMap[s.stationId] })),
    };
  }

  async updateRoute(id: string, dto: UpdateRouteDto) {
    const route = await this.prisma.route.findUnique({ where: { id } });
    if (!route) throw new NotFoundException('Route not found');
    return this.prisma.route.update({
      where: { id },
      data: {
        name: dto.name,
        description: dto.description,
        active: dto.active,
        effectiveUntil: dto.effectiveUntil ? new Date(dto.effectiveUntil) : undefined,
      },
      include: { stops: { orderBy: { sequence: 'asc' } } },
    });
  }

  async deleteRoute(id: string) {
    const route = await this.prisma.route.findUnique({ where: { id } });
    if (!route) throw new NotFoundException('Route not found');
    await this.prisma.route.delete({ where: { id } });
    return { deleted: true, id };
  }

  // ── Route Stops ────────────────────────────────────────────────────────────

  async addStop(routeId: string, dto: AddRouteStopDto) {
    const route = await this.prisma.route.findUnique({ where: { id: routeId } });
    if (!route) throw new NotFoundException('Route not found');

    const station = await this.prisma.station.findUnique({ where: { id: dto.stationId } });
    if (!station) throw new NotFoundException(`Station ${dto.stationId} not found`);

    const existing = await this.prisma.routeStop.findUnique({
      where: { routeId_sequence: { routeId, sequence: dto.sequence } },
    });
    if (existing) throw new ConflictException(`Sequence ${dto.sequence} already exists on this route`);

    return this.prisma.routeStop.create({
      data: { routeId, stationId: dto.stationId, sequence: dto.sequence, distanceKm: dto.distanceKm },
    });
  }

  async removeStop(routeId: string, sequence: number) {
    const stop = await this.prisma.routeStop.findUnique({
      where: { routeId_sequence: { routeId, sequence } },
    });
    if (!stop) throw new NotFoundException(`Stop at sequence ${sequence} not found on route`);

    const total = await this.prisma.routeStop.count({ where: { routeId } });
    if (total <= 2) throw new BadRequestException('A route must retain at least 2 stops');

    await this.prisma.routeStop.delete({ where: { routeId_sequence: { routeId, sequence } } });
    return { deleted: true, sequence };
  }

  async getStops(routeId: string) {
    const route = await this.prisma.route.findUnique({ where: { id: routeId } });
    if (!route) throw new NotFoundException('Route not found');

    const stops = await this.prisma.routeStop.findMany({
      where: { routeId },
      orderBy: { sequence: 'asc' },
    });

    const stationIds = stops.map(s => s.stationId);
    const stations = await this.prisma.station.findMany({ where: { id: { in: stationIds } } });
    const stationMap = Object.fromEntries(stations.map(s => [s.id, s]));

    return stops.map(s => ({ ...s, station: stationMap[s.stationId] }));
  }

  async getSchedulesForRoute(routeId: string) {
    const route = await this.prisma.route.findUnique({ where: { id: routeId } });
    if (!route) throw new NotFoundException('Route not found');

    return this.prisma.trainSchedule.findMany({
      where: { routeId },
      include: { train: true, originStation: true, destinationStation: true },
      orderBy: { departureAt: 'asc' },
    });
  }

  // ── Used by SchedulesService ───────────────────────────────────────────────

  /**
   * Copies RouteStop definitions into TripStopTime rows for a schedule.
   * plannedTimes maps sequence → { arrivalAt?, departureAt? } for actual timing.
   */
  async applyRouteToSchedule(
    routeId: string,
    scheduleId: string,
    plannedTimes: Record<number, { plannedArrivalAt?: string; plannedDepartureAt?: string }>,
  ) {
    const stops = await this.prisma.routeStop.findMany({
      where: { routeId },
      orderBy: { sequence: 'asc' },
    });
    if (stops.length === 0) throw new BadRequestException('Route has no stops defined');

    await this.prisma.tripStopTime.deleteMany({ where: { scheduleId } });

    await this.prisma.tripStopTime.createMany({
      data: stops.map(s => {
        const times = plannedTimes[s.sequence] ?? {};
        return {
          scheduleId,
          stationId: s.stationId,
          sequence: s.sequence,
          plannedArrivalAt: times.plannedArrivalAt ? new Date(times.plannedArrivalAt) : null,
          plannedDepartureAt: times.plannedDepartureAt ? new Date(times.plannedDepartureAt) : null,
        };
      }),
    });

    const intermediateCount = Math.max(0, stops.length - 2);
    await this.prisma.trainSchedule.update({
      where: { id: scheduleId },
      data: { stopsCount: intermediateCount },
    });

    return this.prisma.tripStopTime.findMany({
      where: { scheduleId },
      include: { station: true },
      orderBy: { sequence: 'asc' },
    });
  }
}
