import { Injectable, BadRequestException, ConflictException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
import { SegmentsService, Segment } from '../segments/segments.service';
import { EventEmitter2 } from '@nestjs/event-emitter';

export interface SeatHoldRequest {
  scheduleId: string;
  seatIds: string[];
  passengerId: string;
  originStationId: string;
  destinationStationId: string;
}

export interface BookingConfirmRequest {
  holdId: string;
  bookingId: string;
}

@Injectable()
export class EnhancedSeatsService {
  constructor(
    private prisma: PrismaService,
    private segmentsService: SegmentsService,
    private eventEmitter: EventEmitter2,
  ) {}

  async holdSeats(request: SeatHoldRequest) {
    return this.prisma.$transaction(async (tx) => {
      const segments = await this.segmentsService.getJourneySegments(
        request.scheduleId, request.originStationId, request.destinationStationId,
      );
      const reqFrom = Math.min(...segments.map(s => s.fromSequence));
      const reqTo   = Math.max(...segments.map(s => s.toSequence));

      for (const seatId of request.seatIds) {
        const seat = await tx.seat.findUnique({ where: { id: seatId } });
        if (!seat) throw new BadRequestException(`Seat ${seatId} not found`);
        if (seat.status === 'BLOCKED') throw new BadRequestException(`Seat ${seat.seatNumber} is blocked`);

        const free = await this.segmentsService.isSeatFreeForLeg(
          request.scheduleId, seatId, reqFrom, reqTo,
        );
        if (!free) throw new ConflictException(`Seat ${seat.seatNumber} is not available for the requested leg`);
      }

      const expiresAt = new Date(Date.now() + 10 * 60 * 1000);
      const seatHold = await tx.seatHold.create({
        data: {
          scheduleId:  request.scheduleId,
          seatIds:     request.seatIds,
          passengerId: request.passengerId,
          createdBy: JSON.stringify({
            originStationId:      request.originStationId,
            destinationStationId: request.destinationStationId,
          }),
          expiresAt,
        },
      });

      this.eventEmitter.emit('seats.held', { holdId: seatHold.id, scheduleId: request.scheduleId, seatIds: request.seatIds, segments });
      return { holdId: seatHold.id, expiresAt, segments, seats: request.seatIds };
    });
  }

  async confirmBooking(request: BookingConfirmRequest) {
    return this.prisma.$transaction(async (tx) => {
      const hold = await tx.seatHold.findUnique({ where: { id: request.holdId } });
      if (!hold) throw new BadRequestException('Seat hold not found');
      if (hold.expiresAt < new Date()) throw new BadRequestException('Seat hold has expired');

      const booking = await tx.booking.findUnique({ where: { id: request.bookingId } });
      if (!booking) throw new BadRequestException('Booking not found');

      const schedule = await tx.trainSchedule.findUnique({
        where: { id: hold.scheduleId },
        include: { stopTimes: { orderBy: { sequence: 'asc' } } },
      });
      if (!schedule) throw new BadRequestException('Schedule not found');

      let originStationId: string | undefined;
      let destinationStationId: string | undefined;
      try {
        if (hold.createdBy) {
          const meta = JSON.parse(hold.createdBy);
          originStationId      = meta.originStationId;
          destinationStationId = meta.destinationStationId;
        }
      } catch { /* ignore */ }

      const originStop = originStationId ? schedule.stopTimes.find((s: any) => s.stationId === originStationId) : undefined;
      const destStop = destinationStationId ? schedule.stopTimes.find((s: any) => s.stationId === destinationStationId) : undefined;
      const fromSeq = originStop?.sequence ?? schedule.stopTimes[0].sequence;
      const toSeq = destStop?.sequence ?? schedule.stopTimes[schedule.stopTimes.length - 1].sequence;

      const segments: Segment[] = [];
      for (let i = fromSeq; i < toSeq; i++) {
        const fromStop = schedule.stopTimes.find((s: any) => s.sequence === i);
        const toStop = schedule.stopTimes.find((s: any) => s.sequence === i + 1);
        if (fromStop && toStop) {
          segments.push({
            fromStationId: fromStop.stationId,
            toStationId: toStop.stationId,
            fromSequence: fromStop.sequence,
            toSequence: toStop.sequence,
            fromName: '',
            toName: '',
          });
        }
      }

      const journey = await tx.journey.create({
        data: { passengerId: hold.passengerId, status: 'CONFIRMED', totalMinor: booking.totalMinor, currency: booking.currency },
      });

      for (const seatId of hold.seatIds) {
        for (let i = 0; i < segments.length; i++) {
          await tx.journeySegment.create({
            data: {
              journeyId: journey.id,
              scheduleId: hold.scheduleId,
              segmentOrder: i + 1,
              seatId,
              departureStationId: segments[i].fromStationId,
              arrivalStationId: segments[i].toStationId,
            },
          });
        }
      }

      await tx.seatHold.delete({ where: { id: request.holdId } });

      this.eventEmitter.emit('booking.confirmed', { bookingId: request.bookingId, scheduleId: hold.scheduleId, seatIds: hold.seatIds, segments });
      return { bookingId: request.bookingId, confirmedSeats: hold.seatIds, segments };
    });
  }

  async releaseSeats(scheduleId: string, currentStationId: string) {
    return this.prisma.$transaction(async (tx) => {
      const completedSegments = await tx.journeySegment.findMany({
        where: { scheduleId, arrivalStationId: currentStationId },
        include: { journey: { include: { journeySegments: { where: { scheduleId } } } } },
      });

      const seatsToRelease: string[] = [];
      for (const segment of completedSegments) {
        const allSegments = segment.journey.journeySegments.filter((js: any) => js.seatId === segment.seatId);
        const maxSegmentOrder = Math.max(...allSegments.map((js: any) => js.segmentOrder));
        if (segment.segmentOrder === maxSegmentOrder) seatsToRelease.push(segment.seatId!);
      }

      if (seatsToRelease.length > 0) {
        await tx.seat.updateMany({ where: { id: { in: seatsToRelease } }, data: { status: 'AVAILABLE' } });
        this.eventEmitter.emit('seats.released', { scheduleId, stationId: currentStationId, releasedSeats: seatsToRelease });
      }

      return { releasedSeats: seatsToRelease, stationId: currentStationId };
    });
  }

  async expireHolds() {
    return this.prisma.$transaction(async (tx) => {
      const expiredHolds = await tx.seatHold.findMany({ where: { expiresAt: { lt: new Date() } } });
      const expiredSeatIds = expiredHolds.flatMap(h => h.seatIds);

      if (expiredSeatIds.length > 0) {
        await tx.seat.updateMany({ where: { id: { in: expiredSeatIds } }, data: { status: 'AVAILABLE', heldUntil: null } });
        await tx.seatHold.deleteMany({ where: { expiresAt: { lt: new Date() } } });
        this.eventEmitter.emit('holds.expired', { expiredHolds: expiredHolds.length, releasedSeats: expiredSeatIds });
      }

      return { expiredHolds: expiredHolds.length, releasedSeats: expiredSeatIds };
    });
  }

  async getSeatAvailability(scheduleId: string, originStationId: string, destinationStationId: string) {
    const segments = await this.segmentsService.getJourneySegments(scheduleId, originStationId, destinationStationId);
    const reqFrom = Math.min(...segments.map(s => s.fromSequence));
    const reqTo   = Math.max(...segments.map(s => s.toSequence));

    const schedule = await this.prisma.trainSchedule.findUnique({
      where: { id: scheduleId },
      include: { coachAssignments: { include: { coach: { include: { seats: true } } } } },
    });
    if (!schedule) throw new BadRequestException('Schedule not found');

    const availableSeats = [];
    for (const assignment of schedule.coachAssignments) {
      for (const seat of assignment.coach.seats) {
        if (seat.status === 'BLOCKED') continue;
        const free = await this.segmentsService.isSeatFreeForLeg(scheduleId, seat.id, reqFrom, reqTo);
        if (free) {
          availableSeats.push({
            id: seat.id, label: seat.seatNumber,
            coach: assignment.coach.number,
            seatClass: 'Standard',
            row: seat.row, col: seat.col,
            kind: seat.kind,
            isWindow: seat.isWindow,
            isAisle: seat.isAisle,
            bedPosition: seat.bedPosition,
          });
        }
      }
    }

    return { segments, availableSeats, totalAvailable: availableSeats.length };
  }
}
