import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
import * as QRCode from 'qrcode';

interface OfflineValidation {
  bookingRef: string;
  validatorId: string;
  gateId?: string;
  validatedAt: string;
  leg?: string;
}

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

  async listTickets(filters: { search?: string; status?: string; originStationId?: string; destinationStationId?: string; arrivalDate?: string; skip: number; take: number }) {
    const where: any = {};
    if (filters.search) {
      where.OR = [
        { bookingRef: { contains: filters.search, mode: 'insensitive' } },
        { barcodePayload: { contains: filters.search, mode: 'insensitive' } },
        { booking: { bookingRef: { contains: filters.search, mode: 'insensitive' } } },
      ];
    }
    if (filters.status) {
      where.booking = { ...where.booking, status: filters.status };
    }
    if (filters.originStationId) {
      where.booking = { ...where.booking, schedule: { ...where.booking?.schedule, originStationId: filters.originStationId } };
    }
    if (filters.destinationStationId) {
      where.booking = { ...where.booking, schedule: { ...where.booking?.schedule, destinationStationId: filters.destinationStationId } };
    }
    if (filters.arrivalDate) {
      const start = new Date(filters.arrivalDate);
      const end = new Date(filters.arrivalDate);
      end.setDate(end.getDate() + 1);
      where.booking = { ...where.booking, schedule: { ...where.booking?.schedule, arrivalAt: { gte: start, lt: end } } };
    }
    const tickets = await this.prisma.ticket.findMany({
      where,
      include: {
        booking: {
          include: {
            schedule: { include: { originStation: true, destinationStation: true, train: true } },
            seats: { include: { seat: { include: { coach: { include: { coachType: true } } } } } },
            passenger: { include: { user: true } },
          },
        },
      },
      skip: filters.skip,
      take: filters.take,
      orderBy: { issuedAt: 'desc' },
    });
    const total = await this.prisma.ticket.count({ where });
    return {
      items: tickets.map((t) => ({
        id: t.id,
        ticketNumber: t.barcodePayload,
        bookingRef: t.bookingRef,
        booking: {
          bookingRef: t.booking.bookingRef,
          status: t.booking.status,
          bookingType: t.booking.bookingType,
          returnLegStatus: (t.booking as any).returnLegStatus ?? null,
          outboundBoardedAt: (t.booking as any).outboundBoardedAt ?? null,
          returnBoardedAt: (t.booking as any).returnBoardedAt ?? null,
          totalMinor: t.booking.totalMinor,
          currency: t.booking.currency,
          displayCurrency: t.booking.displayCurrency,
          displayTotalMinor: t.booking.displayTotalMinor,
          passenger: t.booking.passenger?.user || { fullName: 'Guest', email: t.booking.contactEmail },
          contactEmail: t.booking.contactEmail,
        },
        schedule: t.booking.schedule,
        seat: t.booking.seats[0]?.seat,
        status: t.status,
        validatedAt: t.validatedAt,
        createdAt: t.issuedAt,
      })),
      total,
      skip: filters.skip,
      take: filters.take,
    };
  }

  async generate(bookingId: string) {
    if (!bookingId) throw new BadRequestException('Booking ID is required');

    const booking = await this.prisma.booking.findUnique({
      where: { id: bookingId },
      include: {
        schedule: { include: { originStation: true, destinationStation: true, train: true } },
        seats: { include: { seat: { include: { coach: true } } } },
        paymentIntent: true,
      },
    });
    if (!booking) throw new NotFoundException(`Booking ${bookingId} not found`);

    if (booking.status !== 'CONFIRMED') {
      const paymentStatus = booking.paymentIntent?.status ?? null;
      throw new BadRequestException(
        `Payment not completed. Please complete your payment before accessing the ticket. ` +
        `Booking status: ${booking.status}` +
        (paymentStatus ? `. Payment status: ${paymentStatus}` : ''),
      );
    }

    // Build a compact multi-leg payload for the QR so gate scanners see all legs
    const legSummary = this.buildLegSummary(booking);
    const qrData = JSON.stringify({
      ref:  booking.bookingRef,
      type: booking.bookingType,
      legs: legSummary,
    });
    const qrPayload      = await QRCode.toDataURL(qrData);
    const barcodePayload = `EDR${booking.bookingRef}${booking.id.substring(0, 8).toUpperCase()}`;

    const ticket = await this.prisma.ticket.upsert({
      where:  { bookingId },
      update: { qrPayload, barcodePayload },
      create: { bookingId, bookingRef: booking.bookingRef, qrPayload, barcodePayload },
    });

    // Block all seats across all legs
    const seatIds = booking.seats.map(bs => bs.seatId);
    for (const seatId of seatIds) {
      await this.prisma.seat.update({ where: { id: seatId }, data: { status: 'BOOKED' } });
      await this.prisma.seatBlock.create({
        data: { seatId, reason: `Booked in ticket ${ticket.id}`, blockedBy: 'SYSTEM', approvedBy: 'SYSTEM' },
      }).catch(() => null);
    }

    return { ...ticket, legs: legSummary };
  }

  private buildLegSummary(booking: any) {
    const seatsByLeg = new Map<number, any[]>();
    for (const bs of booking.seats) {
      const leg = bs.leg ?? 1;
      if (!seatsByLeg.has(leg)) seatsByLeg.set(leg, []);
      seatsByLeg.get(leg)!.push(bs);
    }
    return Array.from(seatsByLeg.entries())
      .sort(([a], [b]) => a - b)
      .map(([leg, seats]) => ({
        leg,
        scheduleId: (seats[0] as any).scheduleId ?? booking.scheduleId,
        passengers: seats.map(bs => ({
          name:      bs.passengerName,
          category:  bs.passengerCategory,
          coach:     bs.seat?.coach?.number,
          seat:      bs.seat?.seatNumber,
          fareMinor: bs.fareMinor,
        })),
      }));
  }

  async updateSeats(bookingId: string, newSeatIds: string[]) {
    const booking = await this.prisma.booking.findUnique({
      where: { id: bookingId },
      include: { seats: true, ticket: true },
    });
    if (!booking) throw new NotFoundException(`Booking ${bookingId} not found`);
    if (!booking.ticket) throw new BadRequestException('No ticket found for this booking');

    // Remove old seat blocks
    const oldSeatIds = booking.seats.map(bs => bs.seatId);
    for (const seatId of oldSeatIds) {
      await this.prisma.seatBlock.deleteMany({
        where: { 
          seatId,
          reason: { contains: booking.ticket.id }
        }
      });
    }

    // Remove old booking seats
    await this.prisma.bookingSeat.deleteMany({ where: { bookingId } });

    // Create new seat blocks
    for (const seatId of newSeatIds) {
      await this.prisma.seatBlock.create({
        data: {
          seatId,
          reason: `Permanently booked in ticket ${booking.ticket.id}`,
          blockedBy: 'SYSTEM',
          approvedBy: 'SYSTEM',
        }
      }).catch(() => null);
    }

    // Create new booking seats (placeholder with minimal data)
    for (let i = 0; i < newSeatIds.length; i++) {
      await this.prisma.bookingSeat.create({
        data: {
          bookingId,
          seatId: newSeatIds[i],
          passengerName: `Passenger ${i + 1}`,
        }
      });
    }

    return { success: true, updatedSeats: newSeatIds.length };
  }

  async getByMerchantOrderId(merchantOrderId: string) {
    const intent = await this.prisma.paymentIntent.findUnique({
      where: { merchantOrderId },
      select: { bookingId: true },
    });
    if (!intent) throw new NotFoundException(`No payment intent found for order ${merchantOrderId}`);
    const booking = await this.prisma.booking.findUnique({
      where: { id: intent.bookingId },
      include: { schedule: { include: { originStation: true, destinationStation: true, train: true } }, seats: { include: { seat: { include: { coach: true } } } }, ticket: true },
    });
    if (!booking?.ticket) throw new NotFoundException('Ticket not found');
    const seat = booking.seats[0];
    return {
      id: booking.ticket.id, bookingId: booking.id, bookingRef: booking.bookingRef, status: booking.status,
      fromStationName: booking.schedule.originStation.name, toStationName: booking.schedule.destinationStation.name,
      departureAt: booking.schedule.departureAt, trainName: booking.schedule.train.name,
      coachLabel: seat?.seat.coach.number, seatLabel: seat?.seat.seatNumber, passengerName: seat?.passengerName,
      priceMinor: booking.totalMinor, currency: booking.currency, qrPayload: booking.ticket.qrPayload,
      barcodePayload: booking.ticket.barcodePayload,
    };
  }

  async getByRef(bookingRef: string) {
    const booking = await this.prisma.booking.findUnique({
      where: { bookingRef },
      include: { 
        schedule: { include: { originStation: true, destinationStation: true, train: true } }, 
        seats: { include: { seat: { include: { coach: true } } } }, 
        ticket: true 
      },
    });
    if (!booking?.ticket) throw new NotFoundException('Ticket not found');
    const seat = booking.seats[0];
    return {
      id: booking.ticket.id, 
      bookingId: booking.id, 
      bookingRef: booking.bookingRef, 
      status: booking.status,
      fromStationName: booking.schedule.originStation.name, 
      toStationName: booking.schedule.destinationStation.name,
      departureAt: booking.schedule.departureAt, 
      trainName: booking.schedule.train.name,
      coachLabel: seat?.seat.coach.number, 
      seatLabel: seat?.seat.seatNumber, 
      passengerName: seat?.passengerName,
      priceMinor: booking.totalMinor, 
      currency: booking.currency, 
      qrPayload: booking.ticket.qrPayload,
      barcodePayload: booking.ticket.barcodePayload
    };
  }

  async validate(ticketIdOrRef: string, validatorId: string, gateId?: string, leg?: string) {
    // Accept either a ticket UUID or a bookingRef
    let bookingRef = ticketIdOrRef;
    const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(ticketIdOrRef);
    if (isUuid) {
      const ticket = await this.prisma.ticket.findUnique({ where: { id: ticketIdOrRef }, select: { bookingRef: true } });
      if (!ticket) throw new NotFoundException('Ticket not found');
      bookingRef = ticket.bookingRef;
    }
    const resolvedValidatorId = validatorId || 'BACKOFFICE';
    const booking = await this.prisma.booking.findUnique({ where: { bookingRef } });
    if (!booking) throw new NotFoundException('Booking not found');
    const ticket = await this.prisma.ticket.findUnique({ where: { bookingId: booking.id } });
    if (!ticket) throw new NotFoundException('Ticket not found');

    const type = booking.bookingType;
    const now  = new Date();

    // ── ONE_WAY / TRANSIT (single scan) ───────────────────────────────────
    if (type === 'ONE_WAY') {
      if (ticket.validatedAt) {
        return { validated: true, ticketId: ticket.id, validatedAt: ticket.validatedAt, alreadyValidated: true };
      }
      await this.prisma.ticket.update({ where: { id: ticket.id }, data: { validatedAt: now, validatorId: resolvedValidatorId } });
      await this.prisma.gateValidationLog.create({ data: { ticketId: ticket.id, validatorId: resolvedValidatorId, gateId, status: 'APPROVED' } });
      return { validated: true, ticketId: ticket.id, validatedAt: now };
    }

    // ── TRANSIT — leg=LEG1 or leg=LEG2 ──────────────────────────────────
    if (type === 'TRANSIT') {
      const resolvedLeg = (leg ?? 'LEG1').toUpperCase();
      if (resolvedLeg !== 'LEG1' && resolvedLeg !== 'LEG2') {
        throw new BadRequestException('For TRANSIT bookings supply leg=LEG1 or leg=LEG2');
      }
      const logs = await this.prisma.gateValidationLog.findMany({ where: { ticketId: ticket.id, status: 'APPROVED' } }) as any[];
      const alreadyValidated = logs.some(l => l.leg === resolvedLeg);
      if (alreadyValidated) {
        await this.prisma.gateValidationLog.create({ data: { ticketId: ticket.id, validatorId: resolvedValidatorId, gateId, leg: resolvedLeg, status: 'REJECTED', reason: `${resolvedLeg}_ALREADY_USED` } as any });
        throw new BadRequestException(`${resolvedLeg} already validated`);
      }
      if (!ticket.validatedAt) await this.prisma.ticket.update({ where: { id: ticket.id }, data: { validatedAt: now, validatorId: resolvedValidatorId } });
      await this.prisma.gateValidationLog.create({ data: { ticketId: ticket.id, validatorId: resolvedValidatorId, gateId, leg: resolvedLeg, status: 'APPROVED' } as any });
      return { validated: true, ticketId: ticket.id, leg: resolvedLeg, validatedAt: now };
    }

    // ── ROUND_TRIP — leg=OUTBOUND or leg=RETURN ────────────────────────
    if (type === 'ROUND_TRIP') {
      let resolvedLeg = (leg ?? '').toUpperCase();
      // Auto-detect next unused leg when called from backoffice without a leg param
      if (!resolvedLeg) {
        resolvedLeg = !(booking as any).outboundBoardedAt ? 'OUTBOUND' : 'RETURN';
      }
      const bookingData: Record<string, any> = {};
      if (resolvedLeg === 'OUTBOUND') {
        if ((booking as any).outboundBoardedAt) {
          await this.prisma.gateValidationLog.create({ data: { ticketId: ticket.id, validatorId: resolvedValidatorId, gateId, leg: resolvedLeg, status: 'REJECTED', reason: 'OUTBOUND_ALREADY_USED' } as any });
          throw new BadRequestException('Outbound leg already used');
        }
        bookingData.outboundBoardedAt = now;
        if (!ticket.validatedAt) await this.prisma.ticket.update({ where: { id: ticket.id }, data: { validatedAt: now, validatorId: resolvedValidatorId } });
      } else if (resolvedLeg === 'RETURN') {
        if ((booking as any).returnBoardedAt) {
          await this.prisma.gateValidationLog.create({ data: { ticketId: ticket.id, validatorId: resolvedValidatorId, gateId, leg: resolvedLeg, status: 'REJECTED', reason: 'RETURN_ALREADY_USED' } as any });
          throw new BadRequestException('Return leg already used');
        }
        bookingData.returnBoardedAt = now;
      } else {
        throw new BadRequestException('For ROUND_TRIP bookings supply leg=OUTBOUND or leg=RETURN');
      }
      const outboundUsed = resolvedLeg === 'OUTBOUND' ? true : !!(booking as any).outboundBoardedAt;
      const returnUsed   = resolvedLeg === 'RETURN'   ? true : !!(booking as any).returnBoardedAt;
      if (outboundUsed && returnUsed)        bookingData.returnLegStatus = 'BOTH_USED';
      else if (outboundUsed && !returnUsed)  bookingData.returnLegStatus = 'OUTBOUND_ONLY';
      else if (!outboundUsed && returnUsed)  bookingData.returnLegStatus = 'INBOUND_ONLY';
      await this.prisma.booking.update({ where: { id: booking.id }, data: bookingData });
      await this.prisma.gateValidationLog.create({ data: { ticketId: ticket.id, validatorId: resolvedValidatorId, gateId, leg: resolvedLeg, status: 'APPROVED' } as any });
      return { validated: true, ticketId: ticket.id, leg: resolvedLeg, validatedAt: now };
    }

    // ── ROUND_TRIP_TRANSIT — leg=OUTBOUND_LEG1|OUTBOUND_LEG2|RETURN_LEG1|RETURN_LEG2
    if (type === 'ROUND_TRIP_TRANSIT') {
      const validLegs = ['OUTBOUND_LEG1', 'OUTBOUND_LEG2', 'RETURN_LEG1', 'RETURN_LEG2'];
      const resolvedLeg = (leg ?? '').toUpperCase();
      if (!validLegs.includes(resolvedLeg)) {
        throw new BadRequestException(`For ROUND_TRIP_TRANSIT supply leg=${validLegs.join('|')}`);
      }
      const logs = await this.prisma.gateValidationLog.findMany({ where: { ticketId: ticket.id, status: 'APPROVED' } }) as any[];
      if (logs.some(l => l.leg === resolvedLeg)) {
        await this.prisma.gateValidationLog.create({ data: { ticketId: ticket.id, validatorId: resolvedValidatorId, gateId, leg: resolvedLeg, status: 'REJECTED', reason: `${resolvedLeg}_ALREADY_USED` } as any });
        throw new BadRequestException(`${resolvedLeg} already validated`);
      }
      const bookingData: Record<string, any> = {};
      if (resolvedLeg.startsWith('OUTBOUND') && !logs.some(l => l.leg?.startsWith('OUTBOUND') && l.status === 'APPROVED')) {
        bookingData.outboundBoardedAt = now;
      }
      if (resolvedLeg.startsWith('RETURN') && !logs.some(l => l.leg?.startsWith('RETURN') && l.status === 'APPROVED')) {
        bookingData.returnBoardedAt = now;
      }
      const allOutboundDone = ['OUTBOUND_LEG1','OUTBOUND_LEG2'].every(l => l === resolvedLeg || logs.some(x => x.leg === l && x.status === 'APPROVED'));
      const allReturnDone   = ['RETURN_LEG1','RETURN_LEG2'].every(l => l === resolvedLeg || logs.some(x => x.leg === l && x.status === 'APPROVED'));
      if (allOutboundDone && allReturnDone)       bookingData.returnLegStatus = 'BOTH_USED';
      else if (allOutboundDone && !allReturnDone) bookingData.returnLegStatus = 'OUTBOUND_ONLY';
      else if (!allOutboundDone && allReturnDone) bookingData.returnLegStatus = 'INBOUND_ONLY';
      if (Object.keys(bookingData).length) await this.prisma.booking.update({ where: { id: booking.id }, data: bookingData });
      if (!ticket.validatedAt) await this.prisma.ticket.update({ where: { id: ticket.id }, data: { validatedAt: now, validatorId: resolvedValidatorId } });
      await this.prisma.gateValidationLog.create({ data: { ticketId: ticket.id, validatorId: resolvedValidatorId, gateId, leg: resolvedLeg, status: 'APPROVED' } as any });
      return { validated: true, ticketId: ticket.id, leg: resolvedLeg, validatedAt: now };
    }

    // Fallback for unknown booking types — single scan
    if (ticket.validatedAt) {
      return { validated: true, ticketId: ticket.id, validatedAt: ticket.validatedAt, alreadyValidated: true };
    }
    await this.prisma.ticket.update({ where: { id: ticket.id }, data: { validatedAt: now, validatorId: resolvedValidatorId } });
    await this.prisma.gateValidationLog.create({ data: { ticketId: ticket.id, validatorId: resolvedValidatorId, gateId, status: 'APPROVED' } });
    return { validated: true, ticketId: ticket.id, validatedAt: now };
  }

  async getValidationLogs(ticketId: string) {
    return this.prisma.gateValidationLog.findMany({
      where: { ticketId },
      orderBy: { validatedAt: 'desc' }
    });
  }

  async exportOfflineData(tripId: string) {
    const bookings = await this.prisma.booking.findMany({
      where: { scheduleId: tripId, status: 'CONFIRMED' },
      include: {
        ticket: true,
        seats: { include: { seat: { include: { coach: { include: { coachType: true } } } } } },
        passenger: { include: { user: true } },
      },
    });

    return bookings.map((b) => ({
      bookingRef: b.bookingRef,
      ticketId: b.ticket?.id,
      passengerName: b.seats[0]?.passengerName,
      seatLabel: b.seats[0]?.seat.seatNumber,
      coachLabel: b.seats[0]?.seat.coach.number,
      qrPayload: b.ticket?.qrPayload,
      status: b.status,
      bookingType: b.bookingType,
      returnLegStatus: (b as any).returnLegStatus ?? null,
      validatedAt: b.ticket?.validatedAt,
    }));
  }

  async validateOfflineBatch(validations: OfflineValidation[]) {
    const results = { success: 0, failed: 0, duplicate: 0, errors: [] as string[] };
    const processedRefs = new Set<string>();

    for (const v of validations) {
      const offlineLeg = v.leg;
      const dedupKey = offlineLeg ? `${v.bookingRef}:${offlineLeg}` : v.bookingRef;
      if (processedRefs.has(dedupKey)) {
        results.duplicate++;
        continue;
      }
      processedRefs.add(dedupKey);

      try {
        const booking = await this.prisma.booking.findUnique({ where: { bookingRef: v.bookingRef } });
        if (!booking) {
          results.failed++;
          results.errors.push(`Booking ${v.bookingRef} not found`);
          continue;
        }

        const ticket = await this.prisma.ticket.findUnique({ where: { bookingId: booking.id } });
        if (!ticket) {
          results.failed++;
          results.errors.push(`Ticket for ${v.bookingRef} not found`);
          continue;
        }

        if (ticket.validatedAt && booking.bookingType !== 'ROUND_TRIP' &&
            booking.bookingType !== 'TRANSIT' && booking.bookingType !== 'ROUND_TRIP_TRANSIT') {
          results.duplicate++;
          continue;
        }

        // For multi-leg bookings, check per-leg duplication
        const isMultiLeg = booking.bookingType === 'ROUND_TRIP' ||
                           booking.bookingType === 'TRANSIT' ||
                           booking.bookingType === 'ROUND_TRIP_TRANSIT';
        if (isMultiLeg && offlineLeg) {
          const existingLogs = await this.prisma.gateValidationLog.findMany({ where: { ticketId: ticket.id, status: 'APPROVED' } }) as any[];
          if (existingLogs.some(l => l.leg === offlineLeg)) {
            results.duplicate++;
            continue;
          }
        }

        await this.prisma.ticket.update({
          where: { id: ticket.id },
          data: { validatedAt: new Date(v.validatedAt), validatorId: v.validatorId },
        });

        await this.prisma.gateValidationLog.create({
          data: {
            ticketId: ticket.id,
            validatorId: v.validatorId,
            gateId: v.gateId,
            leg: v.leg ?? null,
            status: 'APPROVED',
            validatedAt: new Date(v.validatedAt),
          } as any,
        });

        // update boarding timestamps for multi-leg bookings
        const isMultiLegBooking = booking.bookingType === 'ROUND_TRIP' ||
                                  booking.bookingType === 'TRANSIT' ||
                                  booking.bookingType === 'ROUND_TRIP_TRANSIT';
        if (isMultiLegBooking && offlineLeg) {
          const bookingData: Record<string, any> = {};
          const isOutbound = (offlineLeg as string) === 'OUTBOUND' || (offlineLeg as string) === 'OUTBOUND_LEG1' || (offlineLeg as string) === 'LEG1';
          const isReturn   = (offlineLeg as string) === 'RETURN'   || (offlineLeg as string) === 'RETURN_LEG1'   || (offlineLeg as string) === 'RETURN_LEG2';
          if (isOutbound && !(booking as any).outboundBoardedAt) bookingData.outboundBoardedAt = new Date(v.validatedAt);
          if (isReturn   && !(booking as any).returnBoardedAt)   bookingData.returnBoardedAt   = new Date(v.validatedAt);
          if (Object.keys(bookingData).length) {
            await this.prisma.booking.update({ where: { id: booking.id }, data: bookingData });
          }
        }

        results.success++;
      } catch (err) {
        results.failed++;
        results.errors.push(`Error processing ${v.bookingRef}: ${err instanceof Error ? err.message : String(err)}`);
      }
    }

    return results;
  }

  async delete(id: string) {
    const ticket = await this.prisma.ticket.findUnique({ where: { id } });
    if (!ticket) throw new NotFoundException('Ticket not found');
    
    await this.prisma.gateValidationLog.deleteMany({ where: { ticketId: id } });
    
    // Remove seat blocks associated with this ticket
    await this.prisma.seatBlock.deleteMany({
      where: { 
        reason: { contains: id }
      }
    });
    
    await this.prisma.ticket.delete({ where: { id } });
    
    return { deleted: true, ticketId: id };
  }
}
