const { Booking, Schedule, Vehicle, Route, Customer, Stop, RouteStop, SegmentPrice, BookingSegment } = require('../models');
const { generateBookingReference } = require('./ReferenceGenerator');
const { Op, Sequelize } = require('sequelize');
const { notifyBookingStatusChange, notifyNewBooking } = require('./PushNotificationService');
const EmailService = require('./EmailService');

const DEPOSIT_PERCENTAGE = 50;
const CUTOFF_DAY = 'Wednesday';
const CUTOFF_TIME = '17:00';

const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];

class BookingService {

  _isPastCutoff(schedule) {
    const now = new Date();
    const todayIndex = now.getDay();
    const departureIndex = dayNames.indexOf(schedule.departure_day);

    const nextDeparture = new Date(now);
    nextDeparture.setDate(now.getDate() + ((departureIndex - todayIndex + 7) % 7) || 7);
    if (nextDeparture <= now) {
      nextDeparture.setDate(nextDeparture.getDate() + 7);
    }

    const cutoffForDeparture = new Date(nextDeparture);
    cutoffForDeparture.setDate(nextDeparture.getDate() - ((departureIndex - 3 + 7) % 7));
    cutoffForDeparture.setHours(17, 0, 0, 0);

    return now >= cutoffForDeparture;
  }

  async getStopsForRoute(routeId) {
    return RouteStop.findAll({
      where: { route_id: routeId },
      include: [{ model: Stop }],
      order: [['order', 'ASC']],
    });
  }

  async getSegmentPrice(routeId, fromStopId, toStopId, vehicleType) {
    return SegmentPrice.findOne({
      where: {
        route_id: routeId,
        from_stop_id: fromStopId,
        to_stop_id: toStopId,
        vehicle_type: vehicleType,
      },
    });
  }

  async checkAvailability(scheduleId, requestedSeats, fromStopId, toStopId) {
    const schedule = await Schedule.findByPk(scheduleId, {
      include: [{ model: Vehicle }],
    });
    if (!schedule) throw { status: 404, message: 'Schedule not found' };

    const routeStops = await RouteStop.findAll({
      where: { route_id: schedule.route_id },
      order: [['order', 'ASC']],
    });

    const stopOrderMap = {};
    routeStops.forEach((rs) => { stopOrderMap[rs.stop_id] = rs.order; });

    if (!fromStopId && !toStopId) {
      const bookedSeats = await Booking.sum('number_of_passengers', {
        where: {
          schedule_id: scheduleId,
          booking_status: { [Op.notIn]: ['cancelled', 'completed'] },
        },
      });
      const capacity = schedule.max_seats || schedule.vehicle.capacity;
      const booked = bookedSeats || 0;
      const availableSeats = capacity - booked;
      return { available: availableSeats >= requestedSeats, availableSeats, capacity, bookedSeats: booked, schedule };
    }

    const fromOrder = stopOrderMap[fromStopId];
    const toOrder = stopOrderMap[toStopId];

    if (fromOrder === undefined) throw { status: 400, message: 'Invalid pickup stop' };
    if (toOrder === undefined) throw { status: 400, message: 'Invalid dropoff stop' };
    if (fromOrder >= toOrder) throw { status: 400, message: 'Pickup must be before dropoff' };

    const overlappingBookings = await BookingSegment.findAll({
      where: {
        from_stop_id: { [Op.in]: Object.keys(stopOrderMap) },
        to_stop_id: { [Op.in]: Object.keys(stopOrderMap) },
      },
      include: [{
        model: Booking,
        where: {
          schedule_id: scheduleId,
          booking_status: { [Op.notIn]: ['cancelled', 'completed'] },
        },
        required: true,
      }],
    });

    let overlappingPassengers = 0;
    for (const bs of overlappingBookings) {
      const bsFromOrder = stopOrderMap[bs.from_stop_id];
      const bsToOrder = stopOrderMap[bs.to_stop_id];
      if (bsFromOrder < toOrder && bsToOrder > fromOrder) {
        overlappingPassengers += bs.booking.number_of_passengers;
      }
    }

    const capacity = schedule.max_seats || schedule.vehicle.capacity;
    const booked = overlappingPassengers;
    const availableSeats = capacity - booked;

    return {
      available: availableSeats >= requestedSeats,
      availableSeats,
      capacity,
      bookedSeats: booked,
      schedule,
      fromStopOrder: fromOrder,
      toStopOrder: toOrder,
    };
  }

  async createBooking(data) {
    const { scheduleId, customerName, customerEmail, customerPhone, numberOfPassengers, pickupAddress, dropoffAddress, specialRequests, isDoorToDoor, fromStopId, toStopId } = data;

    const availability = await this.checkAvailability(scheduleId, numberOfPassengers, fromStopId, toStopId);

    if (!availability.available) {
      throw {
        status: 400,
        message: `Only ${availability.availableSeats} seat(s) available on this segment. Requested ${numberOfPassengers}.`,
      };
    }

    if (this._isPastCutoff(availability.schedule)) {
      throw {
        status: 400,
        message: `Booking window closed. Cutoff is ${CUTOFF_DAY} at ${CUTOFF_TIME} for this departure.`,
      };
    }

    const { schedule } = availability;

    let pricePerSeat = parseFloat(schedule.price_per_seat);
    if (fromStopId && toStopId) {
      const segmentPrice = await this.getSegmentPrice(
        schedule.route_id, fromStopId, toStopId,
        schedule.vehicle?.vehicle_type || 'economy'
      );
      if (segmentPrice) {
        const currency = schedule.currency || 'NAD';
        pricePerSeat = parseFloat(currency === 'BWP' ? segmentPrice.price_bwp : segmentPrice.price_nad);
      }
    }

    const totalAmount = pricePerSeat * numberOfPassengers;
    const depositAmount = Math.round((totalAmount * DEPOSIT_PERCENTAGE) / 100);
    const balanceAmount = totalAmount - depositAmount;

    let customer = await Customer.findOne({ where: { email: customerEmail } });
    if (customer) {
      customer.total_bookings += 1;
      customer.total_spent = parseFloat(customer.total_spent) + totalAmount;
      await customer.save();
    } else {
      customer = await Customer.create({
        email: customerEmail,
        full_name: customerName,
        phone: customerPhone,
      });
    }

    const reference = await generateBookingReference(Booking);

    const booking = await Booking.create({
      booking_reference: reference,
      schedule_id: scheduleId,
      customer_id: customer.id,
      customer_name: customerName,
      customer_email: customerEmail,
      customer_phone: customerPhone,
      pickup_address: pickupAddress || null,
      dropoff_address: dropoffAddress || null,
      number_of_passengers: numberOfPassengers,
      total_amount: totalAmount,
      deposit_amount: depositAmount,
      balance_amount: balanceAmount,
      booking_status: 'pending',
      payment_status: 'unpaid',
      special_requests: specialRequests || null,
      is_door_to_door: isDoorToDoor || false,
    });

    if (fromStopId && toStopId) {
      await BookingSegment.create({
        booking_id: booking.id,
        from_stop_id: fromStopId,
        to_stop_id: toStopId,
      });
    }

    notifyNewBooking(booking).catch((e) => console.error('[Push] New booking notify error:', e.message));
    EmailService.notifyBookingConfirmation(booking).catch((e) => console.error('[Email] Confirmation error:', e.message));

    return {
      booking,
      availability: {
        availableSeats: availability.availableSeats - numberOfPassengers,
        totalCapacity: availability.capacity,
      },
    };
  }

  async getBookingByReference(reference) {
    const booking = await Booking.findOne({
      where: { booking_reference: reference },
      include: [
        {
          model: Schedule,
          include: [
            { model: Route },
            { model: Vehicle },
          ],
        },
        {
          model: BookingSegment,
          include: [
            { as: 'fromStop', model: Stop },
            { as: 'toStop', model: Stop },
          ],
        },
      ],
    });

    if (!booking) throw { status: 404, message: 'Booking not found' };
    return booking;
  }

  async updateBookingStatus(bookingId, status, adminUser) {
    const validTransitions = {
      pending: ['POP_uploaded', 'cancelled'],
      POP_uploaded: ['pending_approval', 'cancelled'],
      pending_approval: ['confirmed', 'cancelled'],
      confirmed: ['completed', 'cancelled'],
      completed: [],
      cancelled: [],
    };

    const booking = await Booking.findByPk(bookingId);
    if (!booking) throw { status: 404, message: 'Booking not found' };

    if (!validTransitions[booking.booking_status].includes(status)) {
      throw {
        status: 400,
        message: `Cannot transition from '${booking.booking_status}' to '${status}'`,
      };
    }

    booking.booking_status = status;

    if (status === 'pending_approval' || status === 'confirmed') {
      booking.payment_status = 'deposit_only';
    } else if (status === 'completed') {
      booking.payment_status = 'fully_paid';
    } else if (status === 'cancelled') {
      booking.payment_status = 'unpaid';
    }

    await booking.save();

    notifyBookingStatusChange(booking).catch((e) => console.error('[Push] Status change notify error:', e.message));

    if (adminUser) {
      const EmailService = require('./EmailService');
      await EmailService.sendEmail(
        adminUser.email,
        `Booking ${status} by You`,
        `<p>Hi ${adminUser.full_name},</p><p>You updated booking <strong>${booking.booking_reference}</strong> to <strong>${status}</strong>.</p>`
      ).catch(() => {});
    }

    return booking;
  }

  async getAllBookings(filters = {}) {
    const where = {};
    if (filters.status) where.booking_status = filters.status;
    if (filters.date) {
      where.created_at = { [Op.gte]: new Date(filters.date) };
    }

    return Booking.findAll({
      where,
      include: [
        {
          model: Schedule,
          include: [
            { model: Route },
            { model: Vehicle },
          ],
        },
        {
          model: BookingSegment,
          include: [
            { as: 'fromStop', model: Stop },
            { as: 'toStop', model: Stop },
          ],
        },
      ],
      order: [['created_at', 'DESC']],
    });
  }

  async updateBooking(bookingId, data) {
    const booking = await Booking.findByPk(bookingId);
    if (!booking) throw { status: 404, message: 'Booking not found' };

    const allowed = [
      'customer_name', 'customer_email', 'customer_phone',
      'pickup_address', 'pickup_landmark', 'dropoff_address', 'dropoff_landmark',
      'number_of_passengers', 'special_requests', 'is_door_to_door',
    ];

    const updates = {};
    for (const field of allowed) {
      if (data[field] !== undefined) updates[field] = data[field];
    }

    if (Object.keys(updates).length === 0) {
      throw { status: 400, message: 'No valid fields to update' };
    }

    await booking.update(updates);
    return booking.reload();
  }

  async deleteBooking(bookingId, user) {
    const booking = await Booking.findByPk(bookingId);
    if (!booking) throw { status: 404, message: 'Booking not found' };

    const isAdmin = user?.role === 'admin';
    const isOwner = user?.email && user.email === booking.customer_email;

    if (!isAdmin) {
      if (!isOwner) {
        throw { status: 403, message: 'Insufficient permissions. You can only delete your own bookings.' };
      }
      if (booking.booking_status !== 'cancelled') {
        throw { status: 403, message: 'Only cancelled bookings can be deleted.' };
      }
    }

    await booking.destroy();
    return { message: 'Booking deleted' };
  }

  async uploadBookingImage(bookingId, imagePath) {
    const booking = await Booking.findByPk(bookingId);
    if (!booking) throw { status: 404, message: 'Booking not found' };
    booking.image_url = imagePath;
    await booking.save();
    return booking;
  }

  async uploadProofOfPayment(bookingId, filePath) {
    const booking = await Booking.findByPk(bookingId);
    if (!booking) throw { status: 404, message: 'Booking not found' };

    booking.proof_of_payment = filePath;

    if (booking.booking_status === 'pending') {
      booking.booking_status = 'POP_uploaded';
    }

    await booking.save();
    return booking;
  }
}

module.exports = new BookingService();
