using BookingMicroservice.Clients; using BookingMicroservice.Exceptions; using BookingMicroservice.Models; using System.Linq; using System.Text.Json; namespace BookingMicroservice.Services { public class ReservationComplianceService : IReservationComplianceService { private readonly IBookingService bookingService; private readonly IFlightServiceClient flightServiceClient; private const string NO_AVAILABLE_SEAT_MSG = "Unfortunately, there are no {0} seats available for this flight. Please try selecting a different class, flight, or date."; private const string BOOKED_SEAT_MSG = "The selected seat is already booked. Please choose a different seat."; public ReservationComplianceService(IBookingService bookingService, IFlightServiceClient flightServiceClient) { this.bookingService = bookingService; this.flightServiceClient = flightServiceClient; } public async Task<Booking?> TryCreateBookingAsync(int flightId, int userId, BookingClass bookingClass, int? seatId) { HttpResponseMessage capacityResponse = await flightServiceClient.GetFlightCapacityAsync(flightId, (int)bookingClass); if (!capacityResponse.IsSuccessStatusCode) throw new InvalidOperationException("Could not retrieve flight capacity."); string capacityString = await capacityResponse.Content.ReadAsStringAsync(); if (!int.TryParse(capacityString, out int capacity)) throw new InvalidOperationException("Invalid capacity value received from flight service."); // Get current bookings for the flight and class int currentBookings = bookingService.GetBookings(flightId: flightId, bookingClass: bookingClass).Count(); if (currentBookings >= capacity) throw new BookingException(string.Format(NO_AVAILABLE_SEAT_MSG, bookingClass.ToString().ToLower())); Booking booking = bookingService.CreateBooking(flightId, userId, bookingClass); if(seatId.HasValue && booking?.Id != null) { try { await TryBookSeatAsync(booking.Id, seatId.Value); booking.SetSeatNumber(seatId.Value); } catch (Exception exception) { // delete booking as there has been a failure in booking the seat bookingService.DeleteBooking(booking.Id); throw new InvalidOperationException($@"Booking has been cancelled as there has been an issue with selecting the seat.{Environment.NewLine}{exception.Message}{Environment.NewLine}Please try again."); } } return booking; } public async Task TryBookSeatAsync(int bookingId, int seatId) { HttpResponseMessage seatAvailabilityResponse = await flightServiceClient.IsSeatAvailableAsync(seatId); if (!seatAvailabilityResponse.IsSuccessStatusCode) throw new InvalidOperationException("Could not retrieve seat availability."); string seatAvailabilityString = await seatAvailabilityResponse.Content.ReadAsStringAsync(); if (!bool.TryParse(seatAvailabilityString, out bool isSeatAvailable)) throw new InvalidOperationException("Invalid seat availability value received from flight service."); if(!isSeatAvailable) throw new BookingException(BOOKED_SEAT_MSG); HttpResponseMessage seatBookingRespone = await flightServiceClient.BookSeatAsync(seatId); if (!seatBookingRespone.IsSuccessStatusCode) { string? failureMessage = seatBookingRespone.ReasonPhrase; throw new InvalidOperationException($"Error booking seat; {failureMessage}"); } bookingService.UpdateBooking(bookingId, seatId); } public async Task<IEnumerable<FlightBookingInfo>> TryGetUpcomingFlightsAsync(int userId) { return await GetFlightsAsync(userId, true); } public async Task<IEnumerable<FlightBookingInfo>> TryGetPreviousFlightsAsync(int userId) { return await GetFlightsAsync(userId, false); } private async Task<IEnumerable<FlightBookingInfo>> GetFlightsAsync(int userId, bool isUpcoming) { List<Booking> bookings = bookingService.GetBookings(userId: userId); List<int> flightIds = bookings.Select(booking => booking.FlightId).ToList(); FlightIdCollection flightIdCollection = new FlightIdCollection { FlightIds = flightIds }; HttpResponseMessage flightsResponse = await flightServiceClient.GetFlightsByIdAsync(flightIdCollection); if (!flightsResponse.IsSuccessStatusCode) throw new InvalidOperationException("Could not retrieve flights."); string flightsString = await flightsResponse.Content.ReadAsStringAsync(); FlightResponseWrapper? flightResponse = JsonSerializer.Deserialize<FlightResponseWrapper>(flightsString, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); if (flightResponse?.Values == null) throw new InvalidOperationException("Deserialization of flights failed."); List<Flight> flights = flightResponse.Values; DateTime comparisonTime = DateTime.UtcNow; IEnumerable<Flight> filteredFlights = isUpcoming ? flights.Where(flight => flight.DepartureTime > comparisonTime) : flights.Where(flight => flight.DepartureTime <= comparisonTime); List<FlightBookingInfo> result = filteredFlights.Select(flight => { Booking? matchingBooking = bookings.FirstOrDefault(booking => booking.FlightId == flight.Id); if (matchingBooking == null) throw new InvalidOperationException($"No booking found for flight ID {flight.Id}."); return new FlightBookingInfo(flight, matchingBooking); }).ToList(); return result; } } }