import { Injectable, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';

export interface Segment {
  fromStationId: string;
  toStationId: string;
  fromSequence: number;
  toSequence: number;
  fromName: string;
  toName: string;
}

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

  async getJourneySegments(
    scheduleId: string,
    originStationId: string,
    destinationStationId: string,
  ): Promise<Segment[]> {
    const stopTimes = await this.prisma.tripStopTime.findMany({
      where: { scheduleId },
      include: { station: true },
      orderBy: { sequence: 'asc' },
    });

    const originStop = stopTimes.find(st => st.stationId === originStationId);
    const destStop   = stopTimes.find(st => st.stationId === destinationStationId);

    if (!originStop || !destStop) {
      throw new BadRequestException('Origin or destination station not found on this schedule');
    }
    if (originStop.sequence >= destStop.sequence) {
      throw new BadRequestException('Origin must come before destination');
    }

    const segments: Segment[] = [];
    for (let i = originStop.sequence; i < destStop.sequence; i++) {
      const fromStop = stopTimes.find(st => st.sequence === i);
      const toStop   = stopTimes.find(st => st.sequence === i + 1);
      if (fromStop && toStop) {
        segments.push({
          fromStationId: fromStop.stationId,
          toStationId:   toStop.stationId,
          fromSequence:  fromStop.sequence,
          toSequence:    toStop.sequence,
          fromName:      fromStop.station.name,
          toName:        toStop.station.name,
        });
      }
    }
    return segments;
  }

  /**
   * Checks whether a seat is free for the requested leg [reqFrom, reqTo).
   *
   * Overlap rule (strict):  existingFrom < reqTo  AND  reqFrom < existingTo
   *
   * This means two journeys that TOUCH at a boundary do NOT conflict:
   *   P1: A(1) → B(2)   reqFrom=1, reqTo=2
   *   P2: B(2) → D(4)   reqFrom=2, reqTo=4
   *   Check P1 vs P2: 1 < 4 AND 2 < 2  →  true AND false  →  NO conflict ✓
   *
   *   P3: A(1) → D(4)   reqFrom=1, reqTo=4
   *   Check P3 vs P2: 1 < 4 AND 2 < 4  →  true AND true   →  CONFLICT ✓
   *
   * Sources checked:
   *   1. Active SeatHolds  — leg decoded from createdBy JSON ({ originStationId, destinationStationId })
   *   2. Active JourneySegments — per-leg rows for CONFIRMED / PENDING_PAYMENT journeys
   */
  async isSeatFreeForLeg(
    scheduleId: string,
    seatId: string,
    reqFrom: number,
    reqTo: number,
  ): Promise<boolean> {
    // ── Load stop-time sequences once ────────────────────────────────────────
    const stopTimes = await this.prisma.tripStopTime.findMany({
      where: { scheduleId },
      select: { stationId: true, sequence: true },
    });
    const seqOf = (stationId: string) =>
      stopTimes.find(s => s.stationId === stationId)?.sequence;

    // ── 1. Active holds ───────────────────────────────────────────────────────
    const activeHolds = await this.prisma.seatHold.findMany({
      where: { scheduleId, seatIds: { has: seatId }, expiresAt: { gt: new Date() } },
    });

    for (const hold of activeHolds) {
      // Decode leg from createdBy JSON: { originStationId, destinationStationId, passengers }
      let holdFrom: number | undefined;
      let holdTo:   number | undefined;
      try {
        if (hold.createdBy) {
          const meta = JSON.parse(hold.createdBy);
          holdFrom = seqOf(meta.originStationId);
          holdTo   = seqOf(meta.destinationStationId);
        }
      } catch { /* ignore */ }

      if (holdFrom !== undefined && holdTo !== undefined) {
        if (holdFrom < reqTo && reqFrom < holdTo) return false;
      } else {
        // Cannot resolve leg — conservative block
        return false;
      }
    }

    // ── 2. Active JourneySegments ─────────────────────────────────────────────
    // Each row is one leg (e.g. A→B, B→C). We group by journeyId to get the
    // full range [min(depSeq), max(arrSeq)] per journey for this seat.
    const bookedLegs = await this.prisma.journeySegment.findMany({
      where: {
        scheduleId,
        seatId,
        journey: { status: { in: ['CONFIRMED', 'PENDING_PAYMENT'] } },
      },
    });

    // Group legs by journeyId → find the full range each journey occupies
    const journeyRanges = new Map<string, { from: number; to: number }>();
    for (const leg of bookedLegs) {
      const depSeq = seqOf(leg.departureStationId);
      const arrSeq = seqOf(leg.arrivalStationId);
      if (depSeq === undefined || arrSeq === undefined) continue;

      const existing = journeyRanges.get(leg.journeyId);
      if (!existing) {
        journeyRanges.set(leg.journeyId, { from: depSeq, to: arrSeq });
      } else {
        journeyRanges.set(leg.journeyId, {
          from: Math.min(existing.from, depSeq),
          to:   Math.max(existing.to,   arrSeq),
        });
      }
    }

    for (const { from, to } of journeyRanges.values()) {
      // Strict overlap: existingFrom < reqTo AND reqFrom < existingTo
      if (from < reqTo && reqFrom < to) return false;
    }

    return true;
  }

  /** Legacy wrapper used by EnhancedSeatsService.getOverlappingReservations */
  async getOverlappingReservations(
    scheduleId: string,
    seatId: string,
    requestedSegments: Segment[],
  ): Promise<{ type: string; id: string }[]> {
    const reqFrom = Math.min(...requestedSegments.map(s => s.fromSequence));
    const reqTo   = Math.max(...requestedSegments.map(s => s.toSequence));
    const free = await this.isSeatFreeForLeg(scheduleId, seatId, reqFrom, reqTo);
    return free ? [] : [{ type: 'conflict', id: seatId }];
  }

  segmentsOverlap(segments1: Segment[], segments2: Segment[]): boolean {
    for (const s1 of segments1) {
      for (const s2 of segments2) {
        if (s1.fromSequence < s2.toSequence && s2.fromSequence < s1.toSequence) return true;
      }
    }
    return false;
  }
}
