import { Injectable, ConflictException, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
import { HoldSeatsDto } from './seats.dto';
import { Cron, CronExpression } from '@nestjs/schedule';
import { SegmentsService } from '../segments/segments.service';

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

  async getSeatMap(scheduleId: string, coachId?: string) {
    const assignments = await this.prisma.coachAssignment.findMany({
      where: { scheduleId, ...(coachId ? { coachId } : {}) },
      include: {
        coach: {
          include: {
            seats: { orderBy: [{ row: 'asc' }, { col: 'asc' }] },
            coachType: { include: { seatClasses: true } },
          },
        },
      },
      orderBy: { positionNumber: 'asc' },
    });

    console.log(`[getSeatMap] scheduleId=${scheduleId}, coachId=${coachId}, found ${assignments.length} coach assignments`);

    const allSeatIds = assignments.flatMap((a: any) => a.coach.seats.map((s: any) => s.id));
    const effectiveStatuses = await this.resolveEffectiveStatuses(scheduleId, allSeatIds);

    const response = {
      coaches: assignments.map((a) => {
        const allSeats = a.coach.seats;
        const seatClassNames = a.coach.coachType.seatClasses.map((sc: any) => sc.name);
        
        return {
          id: a.coach.id,
          assignmentId: a.id,
          coachNumber: a.coach.number,
          label: a.coach.number,
          mode: a.coach.status,
          name: `Coach ${a.coach.number}`,
          seatClasses: seatClassNames,
          seatClass: seatClassNames.length > 0 ? seatClassNames[0] : 'Standard',
          positionNumber: a.positionNumber,
          seatArrangement: a.coach.arrangement,
          totalSeats: a.coach.capacity,
          seats: allSeats.map((s) => ({
            id: s.id,
            seatNumber: s.seatNumber,
            number: s.seatNumber,
            label: s.seatNumber,
            status: effectiveStatuses.get(s.id) ?? s.status,
            kind: s.kind,
            row: s.row,
            col: s.col,
            isWindow: s.isWindow,
            isAisle: s.isAisle,
            bedPosition: s.bedPosition,
            coach: {
              id: a.coach.id,
              coachNumber: a.coach.number,
              label: a.coach.number,
            },
          })),
        };
      }),
    };

    console.log(`[getSeatMap] returning ${response.coaches.length} coaches with seats`);
    return response;
  }

  async resolveEffectiveStatuses(
    scheduleId: string,
    seatIds: string[],
  ): Promise<Map<string, string>> {
    const statusMap = new Map<string, string>();

    if (seatIds.length === 0) return statusMap;

    const activeHolds = await this.prisma.seatHold.findMany({
      where: {
        scheduleId,
        expiresAt: { gt: new Date() },
        seatIds: { hasSome: seatIds },
      },
      select: { seatIds: true },
    });
    for (const hold of activeHolds) {
      for (const seatId of hold.seatIds) {
        if (seatIds.includes(seatId)) statusMap.set(seatId, 'HELD');
      }
    }

    const bookedSegments = await this.prisma.journeySegment.findMany({
      where: {
        scheduleId,
        seatId: { in: seatIds },
        journey: { status: { in: ['CONFIRMED', 'PENDING_PAYMENT'] } },
      },
      select: { seatId: true },
    });
    for (const seg of bookedSegments) {
      if (seg.seatId) statusMap.set(seg.seatId, 'BOOKED');
    }

    return statusMap;
  }

  async holdSeats(dto: HoldSeatsDto) {
    const passengerIds = dto.passengers.map(p => p.passengerId);
    const seatIds      = dto.passengers.map(p => p.seatId);

    if (new Set(passengerIds).size !== passengerIds.length)
      throw new BadRequestException('Duplicate passengerId in passengers list');
    if (new Set(seatIds).size !== seatIds.length)
      throw new BadRequestException('Duplicate seatId in passengers list');

    const expiresAt = new Date(Date.now() + 5 * 60 * 1000);

    const hold = await this.prisma.$transaction(async (tx) => {
      const seats = await tx.seat.findMany({
        where: { id: { in: seatIds } },
        select: { id: true, status: true, seatNumber: true },
      });

      if (seats.length !== seatIds.length) {
        const found = new Set(seats.map(s => s.id));
        const missing = seatIds.filter(id => !found.has(id));
        throw new NotFoundException(`Seat(s) not found: ${missing.join(', ')}`);
      }

      const blocked = seats.filter(s => s.status === 'BLOCKED');
      if (blocked.length > 0)
        throw new ConflictException(`Seat(s) ${blocked.map(s => s.seatNumber).join(', ')} are blocked`);

      const seatLabelById = Object.fromEntries(seats.map(s => [s.id, s.seatNumber]));

      const stopTimes = await tx.tripStopTime.findMany({
        where: { scheduleId: dto.scheduleId },
        select: { stationId: true, sequence: true },
      });
      const seqOf = (stationId: string) =>
        stopTimes.find(s => s.stationId === stationId)?.sequence;

      const reqFrom = seqOf(dto.originStationId);
      const reqTo   = seqOf(dto.destinationStationId);

      if (reqFrom === undefined || reqTo === undefined)
        throw new BadRequestException('Origin or destination station not found');
      if (reqFrom >= reqTo)
        throw new BadRequestException('Origin must come before destination');

      const activeHolds = await tx.seatHold.findMany({
        where: { scheduleId: dto.scheduleId, expiresAt: { gt: new Date() } },
        select: { seatIds: true, createdBy: true },
      });

      const parsedHolds: { seatIds: string[]; from: number; to: number; passengerIds: string[] }[] = [];
      for (const h of activeHolds) {
        try {
          if (h.createdBy?.trimStart().startsWith('{')) {
            const meta = JSON.parse(h.createdBy);
            const holdFrom = seqOf(meta.originStationId);
            const holdTo   = seqOf(meta.destinationStationId);
            if (holdFrom !== undefined && holdTo !== undefined) {
              parsedHolds.push({
                seatIds:      h.seatIds,
                from:         holdFrom,
                to:           holdTo,
                passengerIds: (meta.passengers ?? []).map((p: any) => p.passengerId),
              });
            }
          }
        } catch { /* ignore */ }
      }

      for (const { passengerId, seatId } of dto.passengers) {
        for (const hold of parsedHolds) {
          const legsOverlap = hold.from < reqTo && reqFrom < hold.to;
          if (!legsOverlap) continue;

          if (hold.seatIds.includes(seatId)) {
            throw new ConflictException(
              `Seat ${seatLabelById[seatId]} is already held for this leg`,
            );
          }

          if (hold.passengerIds.includes(passengerId)) {
            throw new ConflictException(
              `Passenger already holds a seat on this journey leg`,
            );
          }
        }
      }

      const holdMeta = {
        originStationId:      dto.originStationId,
        destinationStationId: dto.destinationStationId,
        passengers: dto.passengers.map(p => ({ passengerId: p.passengerId, seatId: p.seatId })),
      };

      return tx.seatHold.create({
        data: {
          scheduleId:  dto.scheduleId,
          passengerId: dto.passengers[0].passengerId,
          seatIds,
          createdBy:   JSON.stringify(holdMeta),
          expiresAt,
        },
      });
    });

    return this.enrichHold(hold);
  }

  async getHolds(scheduleId?: string, passengerId?: string) {
    const holds = await this.prisma.seatHold.findMany({
      where: {
        expiresAt: { gt: new Date() },
        ...(scheduleId  ? { scheduleId }  : {}),
        ...(passengerId ? { passengerId } : {}),
      },
      orderBy: { createdAt: 'desc' },
    });
    return Promise.all(holds.map(h => this.enrichHold(h)));
  }

  async getHold(holdId: string) {
    const hold = await this.prisma.seatHold.findUnique({ where: { id: holdId } });
    if (!hold) throw new NotFoundException('Hold not found');
    return this.enrichHold(hold);
  }

  private async enrichHold(hold: any) {
    let originStationId: string | null = null;
    let destinationStationId: string | null = null;
    let passengerSeatMap: { passengerId: string; seatId: string }[] = [];

    try {
      if (hold.createdBy) {
        const raw = hold.createdBy;
        if (typeof raw === 'string' && raw.trimStart().startsWith('{')) {
          const meta = JSON.parse(raw);
          originStationId      = meta.originStationId      ?? null;
          destinationStationId = meta.destinationStationId ?? null;
          passengerSeatMap     = Array.isArray(meta.passengers) ? meta.passengers : [];
        }
      }
    } catch { /* ignore */ }

    const seatIds = hold.seatIds as string[];

    const [schedule, originStation, destinationStation, seats] = await Promise.all([
      this.prisma.trainSchedule.findUnique({
        where: { id: hold.scheduleId },
        include: { train: true, originStation: true, destinationStation: true },
      }),
      originStationId      ? this.prisma.station.findUnique({ where: { id: originStationId } })      : null,
      destinationStationId ? this.prisma.station.findUnique({ where: { id: destinationStationId } }) : null,
      this.prisma.seat.findMany({
        where: { id: { in: seatIds } },
        include: { coach: true },
      }),
    ]);

    let originSequence: number | null = null;
    let destinationSequence: number | null = null;
    if (originStationId && destinationStationId) {
      const stopTimes = await this.prisma.tripStopTime.findMany({
        where: { scheduleId: hold.scheduleId, stationId: { in: [originStationId, destinationStationId] } },
        select: { stationId: true, sequence: true },
      });
      originSequence      = stopTimes.find(s => s.stationId === originStationId)?.sequence      ?? null;
      destinationSequence = stopTimes.find(s => s.stationId === destinationStationId)?.sequence ?? null;
    }

    const seatById = Object.fromEntries(seats.map(s => [s.id, s]));

    const passengers = passengerSeatMap.length > 0
      ? passengerSeatMap.map(({ passengerId, seatId }) => {
          const s = seatById[seatId];
          return {
            passengerId,
            seat: s ? {
              id:         s.id,
              label:      s.seatNumber,
              seatNumber: s.seatNumber,
              coach:      s.coach.number,
              seatClass:  'Standard',
              row:        s.row,
              col:        s.col,
            } : { id: seatId },
          };
        })
      : seatIds.map(seatId => {
          const s = seatById[seatId];
          return {
            passengerId: hold.passengerId,
            seat: s ? {
              id:         s.id,
              label:      s.seatNumber,
              seatNumber: s.seatNumber,
              coach:      s.coach.number,
              seatClass:  'Standard',
              row:        s.row,
              col:        s.col,
            } : { id: seatId },
          };
        });

    return {
      holdId:     hold.id,
      expiresAt:  hold.expiresAt,
      createdAt:  hold.createdAt,
      ttlSeconds: Math.max(0, Math.floor((hold.expiresAt.getTime() - Date.now()) / 1000)),
      schedule: schedule ? {
        id:                   schedule.id,
        trainNumber:          schedule.train.number,
        trainName:            schedule.train.name,
        departureAt:          schedule.departureAt,
        arrivalAt:            schedule.arrivalAt,
        fullRouteOrigin:      schedule.originStation.name,
        fullRouteDestination: schedule.destinationStation.name,
      } : null,
      leg: {
        originStationId,
        originStationName:      originStation?.name ?? null,
        originStationCode:      originStation?.code ?? null,
        originSequence,
        destinationStationId,
        destinationStationName: destinationStation?.name ?? null,
        destinationStationCode: destinationStation?.code ?? null,
        destinationSequence,
      },
      passengers,
    };
  }

  async releaseHold(holdId: string) {
    const hold = await this.prisma.seatHold.findUnique({ where: { id: holdId } });
    if (!hold) throw new NotFoundException('Hold not found');
    await this.prisma.seatHold.delete({ where: { id: holdId } });
    return { released: true, holdId };
  }

  async confirmSeats(seatIds: string[]) {
    // No-op
  }

  async releaseSeats(seatIds: string[]) {
    if (seatIds.length > 0) {
      await this.prisma.journeySegment.deleteMany({
        where: { seatId: { in: seatIds } },
      });
    }
  }

  async autoAssignSeats(scheduleId: string, count: number, seatClassName: string): Promise<string[]> {
    const seats = await this.prisma.seat.findMany({
      where: {
        coach: { assignments: { some: { scheduleId } } },
        status: 'AVAILABLE',
        seatNumber: { not: '' },
        NOT: { seatNumber: { startsWith: '-' } },
      },
      orderBy: [{ coach: { number: 'asc' } }, { row: 'asc' }, { col: 'asc' }],
    });

    if (seats.length < count) {
      throw new ConflictException(`Only ${seats.length} seats available, requested ${count}`);
    }

    const assigned = this.findContiguousSeats(seats, count);
    return assigned.map((s) => s.id);
  }

  private findContiguousSeats(seats: any[], count: number): any[] {
    if (count === 1) return [seats[0]];

    const grouped = new Map<string, any[]>();
    for (const seat of seats) {
      const key = `${seat.coachId}-${seat.row}`;
      if (!grouped.has(key)) grouped.set(key, []);
      grouped.get(key)!.push(seat);
    }

    for (const rowSeats of grouped.values()) {
      if (rowSeats.length >= count) {
        return rowSeats.slice(0, count);
      }
    }

    return seats.slice(0, count);
  }

  async exportSeatsCSV(scheduleId: string): Promise<string> {
    const assignments = await this.prisma.coachAssignment.findMany({
      where: { scheduleId },
      include: { coach: { include: { seats: { orderBy: [{ row: 'asc' }, { col: 'asc' }] } } } },
    });
    const rows = ['coachId,coachLabel,row,col,seatNumber,kind,status,premiumFeeMinor'];
    for (const a of assignments) {
      for (const seat of a.coach.seats) {
        rows.push(`${a.coach.id},${a.coach.number},${seat.row},${seat.col},${seat.seatNumber},${seat.kind},${seat.status},${seat.premiumFeeMinor}`);
      }
    }
    return rows.join('\n');
  }

  async previewSeatsCSV(csvContent: string): Promise<{ valid: number; invalid: number; errors: string[] }> {
    const lines = csvContent.trim().split('\n').slice(1);
    const errors: string[] = [];
    let valid = 0;
    let invalid = 0;

    for (let i = 0; i < lines.length; i++) {
      const parts = lines[i].split(',');
      if (parts.length < 8) {
        errors.push(`Line ${i + 2}: Invalid format`);
        invalid++;
        continue;
      }
      const [coachId, coachLabel, row, col, seatNumber, kind, status, premiumFeeMinor] = parts;
      if (!coachId || !row || !col || !seatNumber) {
        errors.push(`Line ${i + 2}: Missing required fields`);
        invalid++;
        continue;
      }
      valid++;
    }

    return { valid, invalid, errors: errors.slice(0, 10) };
  }

  async importSeatsCSV(scheduleId: string, csvContent: string, commit: boolean): Promise<{ imported: number; errors: string[] }> {
    const lines = csvContent.trim().split('\n').slice(1);
    const errors: string[] = [];
    let imported = 0;

    if (!commit) {
      return { imported: 0, errors: ['Preview mode'] };
    }

    for (let i = 0; i < lines.length; i++) {
      try {
        const parts = lines[i].split(',');
        const [coachId, coachLabel, row, col, seatNumber, kind, status, premiumFeeMinor] = parts;

        await this.prisma.seat.upsert({
          where: { coachId_row_col: { coachId, row: parseInt(row), col } },
          update: {
            seatNumber,
            kind: kind as any,
            status: status as any,
            premiumFeeMinor: parseInt(premiumFeeMinor) || 0,
          },
          create: {
            coachId,
            row: parseInt(row),
            col,
            seatNumber,
            kind: kind as any,
            status: status as any,
            premiumFeeMinor: parseInt(premiumFeeMinor) || 0,
          },
        });
        imported++;
      } catch (err) {
        errors.push(`Line ${i + 2}: ${err instanceof Error ? err.message : String(err)}`);
      }
    }

    return { imported, errors: errors.slice(0, 10) };
  }

  async blockSeat(seatId: string, reason: string) {
    const seat = await this.prisma.seat.findUnique({ where: { id: seatId } });
    if (!seat) throw new NotFoundException('Seat not found');

    await this.prisma.seat.update({
      where: { id: seatId },
      data: { status: 'BLOCKED' },
    });

    await this.prisma.seatBlock.create({
      data: {
        seatId,
        reason,
        blockedBy: 'system',
      },
    });

    return { blocked: true, seatId, reason };
  }

  async unblockSeat(seatId: string) {
    const seat = await this.prisma.seat.findUnique({ where: { id: seatId } });
    if (!seat) throw new NotFoundException('Seat not found');

    await this.prisma.seat.update({
      where: { id: seatId },
      data: { status: 'AVAILABLE' },
    });

    await this.prisma.seatBlock.deleteMany({
      where: { seatId },
    });

    return { unblocked: true, seatId };
  }

  async removeSeat(seatId: string) {
    const seat = await this.prisma.seat.findUnique({ where: { id: seatId } });
    if (!seat) throw new NotFoundException('Seat not found');
    if (!seat.seatNumber) throw new BadRequestException('Seat already removed');

    // Mark removed seat with negative seatNumber (e.g., '1' → '-1') to show empty space
    const negatedNumber = `-${seat.seatNumber}`;
    await this.prisma.seat.update({
      where: { id: seatId },
      data: { seatNumber: negatedNumber },
    });

    return { removed: true, seatId, originalSeatNumber: seat.seatNumber };
  }

  async undoRemoveSeat(seatId: string) {
    const seat = await this.prisma.seat.findUnique({ where: { id: seatId } });
    if (!seat) throw new NotFoundException('Seat not found');
    if (!seat.seatNumber || !seat.seatNumber.startsWith('-')) {
      throw new BadRequestException('Seat is not removed');
    }

    // Restore original seatNumber by removing the negative sign
    const originalNumber = seat.seatNumber.slice(1);
    await this.prisma.seat.update({
      where: { id: seatId },
      data: { seatNumber: originalNumber },
    });

    return { restored: true, seatId, seatNumber: originalNumber };
  }

  @Cron(CronExpression.EVERY_MINUTE)
  async expireHolds() {
    const now = new Date();
    const expired = await this.prisma.seatHold.findMany({ where: { expiresAt: { lt: now } } });
    if (expired.length === 0) return;

    const expiredIds = expired.map(h => h.id);
    for (const hold of expired) {
      await this.releaseSeats(hold.seatIds);
    }
    await this.prisma.seatHold.deleteMany({ where: { id: { in: expiredIds } } });
  }
}
