import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
import { CreateTrainDto, CreateCoachDto, UpdateCoachDto, AssignCoachDto, ListCoachesDto, CreateCoachTypeDto, UpdateCoachTypeDto, CreateClassDto, UpdateClassDto } from './fleet.dto';
import { SeatKind } from '@prisma/client';

// Parses '2+2' → [2, 2], '2+2+2' → [2, 2, 2]
function parseArrangement(arrangement: string): number[] {
  return arrangement.split('+').map((n) => parseInt(n, 10));
}

// Derives column labels from arrangement: '2+2' → ['A','B','C','D']
function seatCols(arrangement: string): string[] {
  const groups = parseArrangement(arrangement);
  const total = groups.reduce((s, n) => s + n, 0);
  return Array.from({ length: total }, (_, i) => String.fromCharCode(65 + i));
}

// Returns true if column is a window seat
function isWindowCol(colIndex: number, groups: number[]): boolean {
  const total = groups.reduce((s, n) => s + n, 0);
  return colIndex === 0 || colIndex === total - 1;
}

// Returns true if column is an aisle seat
function isAisleCol(colIndex: number, groups: number[]): boolean {
  let cursor = 0;
  for (const g of groups) {
    cursor += g;
    const leftAisle = cursor - 1;
    const rightAisle = cursor;
    if (colIndex === leftAisle || colIndex === rightAisle) return true;
  }
  return false;
}

function buildSeats(coachId: string, coachNumber: string, arrangement: string, capacity: number, seatClass?: string): SeatRow[] {
  const cols = seatCols(arrangement);
  const groups = parseArrangement(arrangement);
  const seats: SeatRow[] = [];
  let row = 1;
  let seatNumber = 1;
  let seatIndex = 0;
  const isBedCoach = seatClass?.toLowerCase().includes('bed');
  const totalCols = cols.length;

  while (seatIndex < capacity) {
    for (let ci = 0; ci < cols.length && seatIndex < capacity; ci++) {
      const col = cols[ci];
      let bedPosition = null;

      // Set bedPosition for bed coaches based on ROW cycling (not seat number)
      if (isBedCoach) {
        if (totalCols === 3) {
          // Economy bed (3-row cycle): upper, middle, lower
          if (row % 3 === 1) bedPosition = 'upper';
          else if (row % 3 === 2) bedPosition = 'middle';
          else bedPosition = 'lower';
        } else if (totalCols === 2) {
          // VIP bed (2-row cycle): upper, lower
          bedPosition = row % 2 === 1 ? 'upper' : 'lower';
        }
      }

      seats.push({
        coachId,
        row,
        col,
        seatNumber: `${seatNumber}`,
        kind: SeatKind.STANDARD,
        bedPosition,
      });
      seatNumber++;
      seatIndex++;
    }
    row++;
  }
  return seats;
}

type SeatRow = {
  coachId: string;
  row: number;
  col: string;
  seatNumber: string;
  kind: SeatKind;
  bedPosition?: string | null;
};

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

  async createCoachType(dto: CreateCoachTypeDto) {
    return this.prisma.coachType.create({
      data: {
        code: dto.code,
        name: dto.name,
        type: dto.type || 'passenger',
      },
      include: {
        seatClasses: true,
        coaches: true,
      },
    });
  }

  async getCoachTypes() {
    return this.prisma.coachType.findMany({
      include: {
        seatClasses: true,
        coaches: true,
      },
      orderBy: { createdAt: 'desc' },
    });
  }

  async updateCoachType(id: string, dto: UpdateCoachTypeDto) {
    const coachType = await this.prisma.coachType.findUnique({ where: { id } });
    if (!coachType) throw new NotFoundException('Coach type not found');
    
    const data: any = {};
    if (dto.code !== undefined) data.code = dto.code;
    if (dto.name !== undefined) data.name = dto.name;
    if (dto.type !== undefined) data.type = dto.type;
    
    return this.prisma.coachType.update({
      where: { id },
      data,
      include: {
        seatClasses: true,
        coaches: true,
      },
    });
  }

  async deleteCoachType(id: string) {
    const coachType = await this.prisma.coachType.findUnique({ 
      where: { id },
      include: {
        coaches: true,
        seatClasses: true,
      },
    });
    if (!coachType) throw new NotFoundException('Coach type not found');
    
    // Check for related records
    if (coachType.coaches.length > 0) {
      throw new BadRequestException(
        `Cannot delete coach type. ${coachType.coaches.length} coach(es) are still using this coach type. Please reassign or delete the coaches first.`
      );
    }
    
    if (coachType.seatClasses.length > 0) {
      throw new BadRequestException(
        `Cannot delete coach type. ${coachType.seatClasses.length} seat class(es) are still using this coach type. Please reassign or delete the seat classes first.`
      );
    }
    
    return this.prisma.coachType.delete({ where: { id } });
  }

  async createClass(dto: CreateClassDto) {
    return this.prisma.seatClass.create({
      data: {
        coachTypeId: dto.coachTypeId,
        name: dto.name,
        description: dto.description,
        baseFareMinor: dto.baseFareMinor,
      },
    });
  }

  async getClasses(coachTypeId?: string) {
    const where = coachTypeId ? { coachTypeId } : {};
    return this.prisma.seatClass.findMany({
      where,
      include: { coachType: true },
      orderBy: { createdAt: 'desc' },
    });
  }

  async updateClass(id: string, dto: UpdateClassDto) {
    const seatClass = await this.prisma.seatClass.findUnique({ where: { id } });
    if (!seatClass) throw new NotFoundException('Seat class not found');
    
    const updateData: any = {
      coachTypeId: dto.coachTypeId,
      name: dto.name,
      description: dto.description,
      baseFareMinor: dto.baseFareMinor,
    };
    
    if (dto.isActive !== undefined) {
      updateData.isActive = dto.isActive;
    }
    
    return this.prisma.seatClass.update({
      where: { id },
      data: updateData,
      include: { coachType: true },
    });
  }

  async deleteClass(id: string) {
    const seatClass = await this.prisma.seatClass.findUnique({ 
      where: { id },
      include: {
        fareRules: true,
        routeFareRules: true,
        segmentFares: true,
      },
    });
    if (!seatClass) throw new NotFoundException('Seat class not found');
    
    // Check for related records
    const relatedRecords = [
      ...seatClass.fareRules,
      ...seatClass.routeFareRules,
      ...seatClass.segmentFares,
    ];
    
    if (relatedRecords.length > 0) {
      throw new BadRequestException(
        `Cannot delete seat class. ${relatedRecords.length} fare rule(s) are still using this seat class. Please delete the fare rules first.`
      );
    }
    
    return this.prisma.seatClass.delete({ where: { id } });
  }

  createSeatClass(dto: CreateClassDto) {
    return this.createClass(dto);
  }

  getSeatClasses(coachTypeId?: string) {
    return this.getClasses(coachTypeId);
  }

  async updateSeatClass(id: string, dto: UpdateClassDto) {
    return this.updateClass(id, dto);
  }

  async deleteSeatClass(id: string) {
    return this.deleteClass(id);
  }

  getTrains() {
    return this.prisma.train.findMany({ include: { schedules: { take: 5, orderBy: { departureAt: 'desc' } } } });
  }

  createTrain(dto: CreateTrainDto) {
    return this.prisma.train.create({ data: dto });
  }

  async updateTrain(id: string, dto: CreateTrainDto) {
    const train = await this.prisma.train.findUnique({ where: { id } });
    if (!train) throw new NotFoundException('Train not found');
    return this.prisma.train.update({ where: { id }, data: dto });
  }

  async deleteTrain(id: string) {
    const train = await this.prisma.train.findUnique({ 
      where: { id },
      include: {
        schedules: true,
      },
    });
    if (!train) throw new NotFoundException('Train not found');
    
    // Check for active schedules
    if (train.schedules.length > 0) {
      throw new BadRequestException(
        `Cannot delete train. This train has ${train.schedules.length} schedule(s). Please delete the schedules first.`
      );
    }
    
    return this.prisma.train.delete({ where: { id } });
  }

  async getCoach(id: string) {
    const coach = await this.prisma.coach.findUnique({
      where: { id },
      include: {
        coachType: true,
        seats: { orderBy: [{ row: 'asc' }, { col: 'asc' }] },
        assignments: {
          include: { schedule: { include: { originStation: true, destinationStation: true } } },
          orderBy: { schedule: { departureAt: 'desc' } },
          take: 5,
        },
      },
    });
    if (!coach) throw new NotFoundException('Coach not found');
    return coach;
  }

  async listCoaches(dto: ListCoachesDto) {
    const where: any = {};
    if (dto.status) where.status = dto.status;
    if (dto.scheduleId) where.assignments = { some: { scheduleId: dto.scheduleId } };

    return this.prisma.coach.findMany({
      where,
      include: { coachType: true },
      orderBy: { sequence: 'asc' },
    });
  }

  async createCoach(dto: CreateCoachDto) {
    const groups = parseArrangement(dto.arrangement);
    if (groups.some(isNaN)) {
      throw new BadRequestException(`Invalid arrangement format "${dto.arrangement}". Use e.g. "2+2"`);
    }

    // Get the next sequence number for this coach type
    const lastCoach = await this.prisma.coach.findFirst({
      where: { coachTypeId: dto.coachTypeId },
      orderBy: { sequence: 'desc' },
    });
    const nextSequence = (lastCoach?.sequence ?? 0) + 1;

    const coach = await this.prisma.coach.create({
      data: {
        coachTypeId: dto.coachTypeId,
        number: dto.number,
        sequence: nextSequence,
        arrangement: dto.arrangement,
        capacity: dto.capacity,
        status: dto.status || 'ACTIVE',
      },
      include: { coachType: true },
    });

    if (dto.capacity > 0) {
      const seatClass = coach.coachType?.name || '';
      const seats = buildSeats(coach.id, coach.number, dto.arrangement, dto.capacity, seatClass);
      await this.prisma.seat.createMany({ data: seats });
    }

    return coach;
  }

  async updateCoach(id: string, dto: UpdateCoachDto) {
    const coach = await this.prisma.coach.findUnique({ where: { id } });
    if (!coach) throw new NotFoundException('Coach not found');
    
    return this.prisma.coach.update({ 
      where: { id }, 
      data: {
        arrangement: dto.arrangement,
        capacity: dto.capacity,
        status: dto.status,
        sequence: dto.sequence,
      },
      include: { coachType: true },
    });
  }

  async deleteCoach(id: string) {
    const coach = await this.prisma.coach.findUnique({ 
      where: { id },
      include: {
        assignments: true,
        seats: {
          include: {
            bookingSeats: true,
            blocks: true,
            ticketSeats: true,
          },
        },
      },
    });
    if (!coach) throw new NotFoundException('Coach not found');
    
    // Check for active assignments
    if (coach.assignments.length > 0) {
      throw new BadRequestException(
        `Cannot delete coach. This coach is assigned to ${coach.assignments.length} schedule(s). Please remove the assignments first.`
      );
    }
    
    // Check for booked seats
    const bookedSeats = coach.seats.filter(seat => seat.bookingSeats.length > 0);
    if (bookedSeats.length > 0) {
      throw new BadRequestException(
        `Cannot delete coach. ${bookedSeats.length} seat(s) have active bookings. Please wait for bookings to complete or cancel them first.`
      );
    }
    
    // Check for blocked seats
    const blockedSeats = coach.seats.filter(seat => seat.blocks.length > 0);
    if (blockedSeats.length > 0) {
      throw new BadRequestException(
        `Cannot delete coach. ${blockedSeats.length} seat(s) are blocked. Please unblock them first.`
      );
    }
    
    // Check for tickets
    const seatsWithTickets = coach.seats.filter(seat => seat.ticketSeats.length > 0);
    if (seatsWithTickets.length > 0) {
      throw new BadRequestException(
        `Cannot delete coach. ${seatsWithTickets.length} seat(s) have issued tickets. Please wait for travel completion.`
      );
    }
    
    // Delete related seats first (now safe to do)
    await this.prisma.seat.deleteMany({ where: { coachId: id } });
    
    return this.prisma.coach.delete({ where: { id } });
  }

  async assignCoach(dto: AssignCoachDto) {
    const [schedule, coach] = await Promise.all([
      this.prisma.trainSchedule.findUnique({ where: { id: dto.scheduleId } }),
      this.prisma.coach.findUnique({ where: { id: dto.coachId } }),
    ]);
    if (!schedule) throw new NotFoundException('Schedule not found');
    if (!coach) throw new NotFoundException('Coach not found');
    return this.prisma.coachAssignment.create({ data: dto });
  }

  async removeAssignment(id: string) {
    const assignment = await this.prisma.coachAssignment.findUnique({ where: { id } });
    if (!assignment) throw new NotFoundException('Assignment not found');
    return this.prisma.coachAssignment.delete({ where: { id } });
  }

  async getAnalytics() {
    const [totalTrains, totalSchedules, totalSeats, bookedSeats] = await Promise.all([
      this.prisma.train.count(),
      this.prisma.trainSchedule.count(),
      this.prisma.seat.count(),
      this.prisma.seat.count({ where: { status: 'BOOKED' } }),
    ]);
    return {
      totalTrains,
      totalSchedules,
      totalSeats,
      bookedSeats,
      occupancyRate: totalSeats > 0 ? +((bookedSeats / totalSeats) * 100).toFixed(2) : 0,
    };
  }
}
