package com.mvrent.carrental.service;

import com.mvrent.carrental.model.*;
import com.mvrent.carrental.repository.BookingRepository;
import com.mvrent.carrental.repository.DriverRepository;
import com.mvrent.carrental.repository.InvoiceRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.temporal.ChronoUnit;
import java.util.List;

@Service
@RequiredArgsConstructor
public class BookingService {

    private static final List<BookingStatus> ACTIVE_STATUSES =
            List.of(BookingStatus.PENDING, BookingStatus.CONFIRMED);
    private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);

    private final BookingRepository bookingRepository;
    private final CarService carService;
    private final DriverRepository driverRepository;
    private final ConfigService configService;
    private final CommissionService commissionService;
    private final InvoiceRepository invoiceRepository;
    private final NotificationService notificationService;
    private final SmsService smsService;

    @Transactional
    public Booking createBooking(User user, Long carId, Booking form, Long driverId) {
        if (!form.getReturnDate().isAfter(form.getPickupDate())) {
            throw new IllegalArgumentException("Return date must be after pickup date");
        }

        Car car = carService.findById(carId);
        if (!car.isBookable()) {
            throw new IllegalStateException(
                    car.getStatus() == CarStatus.MAINTENANCE
                            ? "Car is under maintenance and cannot be booked"
                            : "Car is not available for booking");
        }
        if (carOverlaps(carId, form)) {
            throw new IllegalStateException("Car is already booked for the selected dates");
        }

        SystemConfig cfg = configService.getSystemConfig();
        RentalUnit unit = form.getRentalUnit() != null ? form.getRentalUnit() : RentalUnit.DAY;

        // ----- Driver assignment -----
        Driver driver = null;
        boolean withDriver = form.isWithDriver();
        if (withDriver) {
            if (driverId == null) {
                throw new IllegalArgumentException("Please select a driver for a with-driver booking");
            }
            driver = driverRepository.findById(driverId)
                    .orElseThrow(() -> new IllegalArgumentException("Driver not found"));
            if (!driver.isAssignable()) {
                throw new IllegalStateException("Selected driver is not available (off duty or licence expired)");
            }
            boolean driverBusy = bookingRepository
                    .existsByDriverIdAndStatusInAndPickupDateLessThanEqualAndReturnDateGreaterThanEqual(
                            driverId, ACTIVE_STATUSES, form.getReturnDate(), form.getPickupDate());
            if (driverBusy) {
                throw new IllegalStateException("Driver is already assigned for the selected dates");
            }
        }

        // ----- Period & vehicle charge -----
        long qty;
        BigDecimal vehicleCharge;
        if (unit == RentalUnit.MONTH) {
            qty = Math.max(1, ChronoUnit.MONTHS.between(form.getPickupDate(), form.getReturnDate()));
            BigDecimal monthly = car.getMonthlyRate() != null
                    ? car.getMonthlyRate()
                    : car.getPricePerDay().multiply(BigDecimal.valueOf(30));
            vehicleCharge = monthly.multiply(BigDecimal.valueOf(qty));
        } else { // DAY (HOUR not offered in v1)
            qty = Math.max(1, ChronoUnit.DAYS.between(form.getPickupDate(), form.getReturnDate()));
            vehicleCharge = car.getPricePerDay().multiply(BigDecimal.valueOf(qty));
        }

        // ----- Driver charge -----
        BigDecimal driverCharge = BigDecimal.ZERO;
        if (withDriver && driver != null) {
            if (unit == RentalUnit.MONTH) {
                BigDecimal monthly = driver.getMonthlyCharge() != null ? driver.getMonthlyCharge()
                        : (driver.getDailyCharge() != null ? driver.getDailyCharge() : cfg.getDefaultDriverDailyCharge())
                        .multiply(BigDecimal.valueOf(30));
                driverCharge = monthly.multiply(BigDecimal.valueOf(qty));
            } else {
                BigDecimal daily = driver.getDailyCharge() != null
                        ? driver.getDailyCharge() : cfg.getDefaultDriverDailyCharge();
                driverCharge = daily.multiply(BigDecimal.valueOf(qty));
            }
        }

        // ----- Tax & total -----
        BigDecimal taxPercent = cfg.getDefaultTaxPercent();
        BigDecimal subtotal = vehicleCharge.add(driverCharge);
        BigDecimal taxAmount = subtotal.multiply(taxPercent)
                .divide(HUNDRED, 2, RoundingMode.HALF_UP);
        BigDecimal total = subtotal.add(taxAmount);

        // ----- Revenue attribution -----
        BigDecimal commissionPercent = commissionService.resolvePercent(car);
        BigDecimal commissionAmount = commissionService.commissionAmount(car, vehicleCharge);

        Booking booking = Booking.builder()
                .user(user).car(car).driver(driver).withDriver(withDriver)
                .pickupDate(form.getPickupDate()).returnDate(form.getReturnDate())
                .pickupLocation(form.getPickupLocation()).dropLocation(form.getDropLocation())
                .rentalUnit(unit).quantity((int) qty)
                .vehicleCharge(vehicleCharge).driverCharge(driverCharge)
                .taxPercent(taxPercent).taxAmount(taxAmount).totalAmount(total)
                .revenueOwner(car.getOwnership()).vendor(car.getVendor())
                .commissionPercent(commissionPercent).commissionAmount(commissionAmount)
                .status(BookingStatus.PENDING)
                .build();
        booking = bookingRepository.save(booking);

        // ----- Invoice -----
        Invoice invoice = Invoice.builder()
                .booking(booking)
                .invoiceNumber(cfg.getInvoicePrefix() + "-" + String.format("%05d", booking.getId()))
                .subtotal(subtotal).taxAmount(taxAmount).total(total)
                .build();
        invoiceRepository.save(invoice);

        // Notify the customer (fail-safe; no-ops if email/SMS not configured).
        notificationService.sendBookingConfirmation(booking);
        smsService.sendBookingConfirmation(booking);

        return booking;
    }

    private boolean carOverlaps(Long carId, Booking form) {
        return bookingRepository
                .existsByCarIdAndStatusInAndPickupDateLessThanEqualAndReturnDateGreaterThanEqual(
                        carId, ACTIVE_STATUSES, form.getReturnDate(), form.getPickupDate());
    }

    public List<Booking> findAll() {
        return bookingRepository.findAllByOrderByCreatedAtDesc();
    }

    /** Owned vs vendor rental-revenue split across all bookings. */
    public record RevenueSplit(BigDecimal owned, BigDecimal vendor) {
        public BigDecimal total() { return owned.add(vendor); }
    }

    public RevenueSplit revenueSplit() {
        BigDecimal owned = BigDecimal.ZERO;
        BigDecimal vendor = BigDecimal.ZERO;
        for (Booking b : bookingRepository.findAllByOrderByCreatedAtDesc()) {
            BigDecimal amt = b.getVehicleCharge() != null ? b.getVehicleCharge()
                    : (b.getTotalAmount() == null ? BigDecimal.ZERO : b.getTotalAmount());
            if (b.getRevenueOwner() == CarOwnership.VENDOR) {
                vendor = vendor.add(amt);
            } else {
                owned = owned.add(amt);
            }
        }
        return new RevenueSplit(owned, vendor);
    }

    public List<Booking> findByUser(User user) {
        return bookingRepository.findByUserOrderByCreatedAtDesc(user);
    }

    public Booking findById(Long id) {
        return bookingRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Booking not found: " + id));
    }

    public Invoice invoiceFor(Long bookingId) {
        return invoiceRepository.findByBookingId(bookingId)
                .orElseThrow(() -> new IllegalArgumentException("Invoice not found for booking " + bookingId));
    }

    @Transactional
    public Booking updateStatus(Long id, BookingStatus status) {
        Booking booking = findById(id);
        booking.setStatus(status);
        notificationService.sendBookingStatusUpdate(booking);
        return booking;
    }
}
