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


    }

}