import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
import { CreateTrainDto, CreateCoachDto, UpdateCoachDto, AssignCoachDto, CreateSeatBatchDto, ListCoachesDto } 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 a seat-mode arrangement string.
// '2+2' → ['A','B','C','D']  (A/D window, B/C aisle)
// '1+2+1' → ['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)); // A, B, C …
}

// Returns true if the column index is a window seat given the arrangement groups.
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 the column index 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;
}

// Bed positions for a given tier count: 2 → lower/upper, 3 → lower/middle/upper
const BED_POSITIONS: Record<number, string[]> = {
  2: ['lower', 'upper'],
  3: ['lower', 'middle', 'upper'],
};

type SeatRow = {
  coachId: string;
  row: number;
  col: string;
  label: string;
  seatNumber: string;
  kind: SeatKind;
  isWindow: boolean;
  isAisle: boolean;
  bedPosition?: string;
};

function buildSeatSeats(coachId: string, coachLabel: string, arrangement: string, totalUnits: number): SeatRow[] {
  const cols = seatCols(arrangement);
  const groups = parseArrangement(arrangement);
  const seats: SeatRow[] = [];
  let row = 1;
  while (seats.length < totalUnits) {
    for (let ci = 0; ci < cols.length && seats.length < totalUnits; ci++) {
      const col = cols[ci];
      seats.push({
        coachId, row, col,
        label: `${row}${col}`,
        seatNumber: `${coachLabel}${row}${col}`,
        kind: SeatKind.STANDARD,
        isWindow: isWindowCol(ci, groups),
        isAisle: isAisleCol(ci, groups),
      });
    }
    row++;
  }
  return seats;
}

function buildBedSeats(coachId: string, coachLabel: string, arrangement: string, totalUnits: number): SeatRow[] {
  // arrangement for beds describes tiers per berth, e.g. '2+2' = 2 lower+upper on each side
  // Each compartment number is the row; each tier is the col (L=lower, M=middle, U=upper)
  const groups = parseArrangement(arrangement);
  const tiersPerSide = groups[0]; // e.g. 2 → lower+upper
  const positions = BED_POSITIONS[tiersPerSide] ?? ['lower', 'upper'];
  const tierCols = positions.map((_, i) => String.fromCharCode(65 + i)); // A=lower, B=upper, C=middle
  const seats: SeatRow[] = [];
  let compartment = 1;
  while (seats.length < totalUnits) {
    for (let ti = 0; ti < tierCols.length && seats.length < totalUnits; ti++) {
      const col = tierCols[ti];
      seats.push({
        coachId, row: compartment, col,
        label: `${compartment}${col}`,
        seatNumber: `${coachLabel}${compartment}${col}`,
        kind: SeatKind.STANDARD,
        isWindow: false,
        isAisle: false,
        bedPosition: positions[ti],
      });
    }
    compartment++;
  }
  return seats;
}

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

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

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

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

    // Group seats by row to reflect the physical arrangement layout
    const rowMap = new Map<number, typeof coach.seats>();
    for (const seat of coach.seats) {
      if (!rowMap.has(seat.row)) rowMap.set(seat.row, []);
      rowMap.get(seat.row)!.push(seat);
    }

    const seatsByRow = Array.from(rowMap.entries()).map(([row, seats]) => ({ row, seats }));

    const seatStatusSummary = {
      total:     coach.seats.length,
      available: coach.seats.filter(s => s.status === 'AVAILABLE').length,
      held:      coach.seats.filter(s => s.status === 'HELD').length,
      booked:    coach.seats.filter(s => s.status === 'BOOKED').length,
      blocked:   coach.seats.filter(s => s.status === 'BLOCKED').length,
    };

    const { seats, ...coachData } = coach;
    return { ...coachData, seatsByRow, seatStatusSummary };
  }

  async listCoaches(dto: ListCoachesDto) {
    const where: any = {};
    if (dto.isActive !== undefined) where.isActive = dto.isActive;
    if (dto.mode) where.mode = dto.mode;
    if (dto.seatClassId) where.seatClassId = dto.seatClassId;
    if (dto.scheduleId) where.assignments = { some: { scheduleId: dto.scheduleId } };

    const coaches = await this.prisma.coach.findMany({
      where,
      include: {
        seatClass: true,
        seats: { select: { status: true } },
        _count: { select: { seats: true, assignments: true } },
      },
      orderBy: [{ isActive: 'desc' }, { label: 'asc' }],
    });

    return coaches.map(({ seats, ...coach }) => ({
      ...coach,
      seatStatusSummary: {
        total: seats.length,
        available: seats.filter(s => s.status === 'AVAILABLE').length,
        held:      seats.filter(s => s.status === 'HELD').length,
        booked:    seats.filter(s => s.status === 'BOOKED').length,
        blocked:   seats.filter(s => s.status === 'BLOCKED').length,
      },
    }));
  }

  async createCoach(dto: CreateCoachDto) {
    const mode = dto.mode ?? 'seat';
    const totalUnits = dto.totalUnits ?? 0;

    const isBed = mode === 'bed';
    const arrangement = isBed
      ? (dto.bedArrangement ?? dto.seatArrangement ?? '2+2')
      : (dto.seatArrangement ?? '2+2');

    if (totalUnits > 0) {
      const groups = parseArrangement(arrangement);
      if (groups.some(isNaN)) {
        throw new BadRequestException(`Invalid arrangement format "${arrangement}". Use e.g. "2+2" or "2+2+2"`);
      }
    }

    const coach = await this.prisma.coach.create({ data: dto });

    if (totalUnits > 0) {
      const seats = isBed
        ? buildBedSeats(coach.id, coach.label, arrangement, totalUnits)
        : buildSeatSeats(coach.id, coach.label, arrangement, totalUnits);
      await this.prisma.seat.createMany({ data: seats, skipDuplicates: true });
    }

    return this.prisma.coach.findUnique({
      where: { id: coach.id },
      include: { seatClass: true, _count: { select: { seats: true } } },
    });
  }

  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: dto });
  }

  async deleteTrain(id: string) {
    const train = await this.prisma.train.findUnique({ where: { id } });
    if (!train) throw new NotFoundException('Train not found');
    return this.prisma.train.delete({ where: { id } });
  }

  async deleteCoach(id: string) {
    const coach = await this.prisma.coach.findUnique({ where: { id } });
    if (!coach) throw new NotFoundException('Coach not found');
    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 createSeatBatch(dto: CreateSeatBatchDto) {
    const coach = await this.prisma.coach.findUnique({ where: { id: dto.coachId } });
    if (!coach) throw new NotFoundException('Coach not found');
    const seats = [];
    for (let row = 1; row <= dto.rows; row++) {
      for (const col of dto.cols) {
        seats.push({ coachId: dto.coachId, row, col, label: `${row}${col}`, seatNumber: `${coach.label}${row}${col}` });
      }
    }
    await this.prisma.seat.createMany({ data: seats, skipDuplicates: true });
    return { created: seats.length };
  }

  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 };
  }
}
