package com.parkingexchange.parking.application.dto;


import java.time.LocalDateTime;
import java.util.UUID;

public class AvailabilityRequest {

    private UUID leavingUserId;
    private double latitude;
    private double longitude;
    private LocalDateTime estimatedLeavingTime;


    // Getters and Setters

    public UUID getLeavingUserId() {
        return leavingUserId;
    }

    public void setLeavingUserId(UUID leavingUserId) {
        this.leavingUserId = leavingUserId;
    }

    public double getLatitude() {
        return latitude;
    }

    public void setLatitude(double latitude) {
        this.latitude = latitude;
    }

    public double getLongitude() {
        return longitude;
    }

    public void setLongitude(double longitude) {
        this.longitude = longitude;
    }

    public LocalDateTime getEstimatedLeavingTime() {
        return estimatedLeavingTime;
    }

    public void setEstimatedLeavingTime(LocalDateTime estimatedLeavingTime) {
        this.estimatedLeavingTime = estimatedLeavingTime;
    }

}package com.parkingexchange.parking.application.dto;


import com.parkingexchange.parking.domain.model.AvailabilityStatus;

import java.time.LocalDateTime;
import java.util.UUID;

public class AvailabilityResponse {
    private UUID availabilityId;
    private UUID leavingUserId;
    private double latitude;
    private double longitude;
    private LocalDateTime estimatedLeavingTime;
    private AvailabilityStatus status;
    private UUID reservedByUserId;

    // Getters and setters


    // Getters and Setters

    public UUID getAvailabilityId() {
        return availabilityId;
    }

    public void setAvailabilityId(UUID availabilityId) {
        this.availabilityId = availabilityId;
    }

    public UUID getLeavingUserId() {
        return leavingUserId;
    }

    public void setLeavingUserId(UUID leavingUserId) {
        this.leavingUserId = leavingUserId;
    }

    public double getLatitude() {
        return latitude;
    }

    public void setLatitude(double latitude) {
        this.latitude = latitude;
    }

    public double getLongitude() {
        return longitude;
    }

    public void setLongitude(double longitude) {
        this.longitude = longitude;
    }

    public LocalDateTime getEstimatedLeavingTime() {
        return estimatedLeavingTime;
    }

    public void setEstimatedLeavingTime(LocalDateTime estimatedLeavingTime) {
        this.estimatedLeavingTime = estimatedLeavingTime;
    }

    public AvailabilityStatus getStatus() {
        return status;
    }

    public void setStatus(AvailabilityStatus status) {
        this.status = status;
    }

    public UUID getReservedByUserId() {
        return reservedByUserId;
    }

    public void setReservedByUserId(UUID reservedByUserId) {
        this.reservedByUserId = reservedByUserId;
    }

}package com.parkingexchange.parking.application.dto;

import java.util.UUID;

public class CancelAvailabilityRequest {

    private UUID userId;

    // Getters and Setters

    public UUID getUserId() {
        return userId;
    }

    public void setUserId(UUID userId) {
        this.userId = userId;
    }
}
package com.parkingexchange.parking.application.dto;

import java.util.UUID;

public class ConfirmSpotTakenRequest {

    private UUID arrivingUserId;

    // Getters and Setters

    public UUID getArrivingUserId() {
        return arrivingUserId;
    }

    public void setArrivingUserId(UUID arrivingUserId) {
        this.arrivingUserId = arrivingUserId;
    }
}
package com.parkingexchange.parking.application.dto.mapper;

import com.parkingexchange.parking.application.dto.AvailabilityResponse;
import com.parkingexchange.parking.application.dto.ReservationRequestResponse;
import com.parkingexchange.parking.domain.model.Availability;
import com.parkingexchange.parking.domain.model.AvailabilityId;
import com.parkingexchange.parking.domain.model.ReservationRequest;
import org.mapstruct.Mapper;

import java.util.UUID;

@Mapper(componentModel = "spring")
public interface AvailabilityMapper {

    AvailabilityResponse toResponse(Availability availability);

    ReservationRequestResponse toReservationResponse(ReservationRequest request);

    default UUID map (AvailabilityId value){
        return value.getValue();
    }
    // Add other mapping methods if needed
}
package com.parkingexchange.parking.application.dto;

import java.util.UUID;

public class ReservationRequestDto {

    private UUID arrivingUserId;
    private String message;

    // Getters and Setters

    public UUID getArrivingUserId() {
        return arrivingUserId;
    }

    public void setArrivingUserId(UUID arrivingUserId) {
        this.arrivingUserId = arrivingUserId;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
package com.parkingexchange.parking.application.dto;

import java.util.UUID;


import com.parkingexchange.parking.domain.model.RequestStatus;
import java.time.LocalDateTime;
import java.util.UUID;

public class ReservationRequestResponse {

    private UUID requestId;
    private UUID availabilityId;
    private UUID arrivingUserId;
    private UUID leavingUserId;
    private RequestStatus status;
    private LocalDateTime requestedAt;
    private LocalDateTime respondedAt;
    private String message;

    // Getters and Setters

    public UUID getRequestId() {
        return requestId;
    }

    public void setRequestId(UUID requestId) {
        this.requestId = requestId;
    }

    public UUID getAvailabilityId() {
        return availabilityId;
    }

    public void setAvailabilityId(UUID availabilityId) {
        this.availabilityId = availabilityId;
    }

    public UUID getArrivingUserId() {
        return arrivingUserId;
    }

    public void setArrivingUserId(UUID arrivingUserId) {
        this.arrivingUserId = arrivingUserId;
    }

    public UUID getLeavingUserId() {
        return leavingUserId;
    }

    public void setLeavingUserId(UUID leavingUserId) {
        this.leavingUserId = leavingUserId;
    }

    public RequestStatus getStatus() {
        return status;
    }

    public void setStatus(RequestStatus status) {
        this.status = status;
    }

    public LocalDateTime getRequestedAt() {
        return requestedAt;
    }

    public void setRequestedAt(LocalDateTime requestedAt) {
        this.requestedAt = requestedAt;
    }

    public LocalDateTime getRespondedAt() {
        return respondedAt;
    }

    public void setRespondedAt(LocalDateTime respondedAt) {
        this.respondedAt = respondedAt;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}package com.parkingexchange.parking.application.dto;

import com.parkingexchange.parking.domain.model.RequestStatus;

import java.time.LocalDateTime;
import java.util.UUID;

public class ReservationResponseDto {

    private UUID leavingUserId;
    private boolean accepted;

    // Getters and Setters

    public UUID getLeavingUserId() {
        return leavingUserId;
    }

    public void setLeavingUserId(UUID leavingUserId) {
        this.leavingUserId = leavingUserId;
    }

    public boolean isAccepted() {
        return accepted;
    }

    public void setAccepted(boolean accepted) {
        this.accepted = accepted;
    }
}package com.parkingexchange.parking.application.dto;

public class SearchCriteria {

    private double latitude;
    private double longitude;
    private double radius;

    // Getters and Setters

    public double getLatitude() {
        return latitude;
    }

    public void setLatitude(double latitude) {
        this.latitude = latitude;
    }

    public double getLongitude() {
        return longitude;
    }

    public void setLongitude(double longitude) {
        this.longitude = longitude;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }
}package com.parkingexchange.parking.application.service;



import com.parkingexchange.common.domain.model.DomainEvent;
import com.parkingexchange.common.domain.model.DomainEventPublisher;
import com.parkingexchange.parking.application.dto.*;
import com.parkingexchange.parking.application.dto.mapper.AvailabilityMapper;
import com.parkingexchange.parking.domain.model.*;
import com.parkingexchange.parking.domain.repository.AvailabilityRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;


@Service
public class AvailabilityApplicationServiceImpl implements AvailabilityApplicationService {

    private final AvailabilityRepository availabilityRepository;
    private final AvailabilityMapper availabilityMapper;
    private final DomainEventPublisher domainEventPublisher;

    @Autowired
    public AvailabilityApplicationServiceImpl(AvailabilityRepository availabilityRepository,
                                              AvailabilityMapper availabilityMapper,
                                              DomainEventPublisher domainEventPublisher) {
        this.availabilityRepository = availabilityRepository;
        this.availabilityMapper = availabilityMapper;
        this.domainEventPublisher = domainEventPublisher;
    }

    @Transactional
    @Override
    public AvailabilityResponse reportAvailability(AvailabilityRequest request) {
        Location location = new Location(request.getLatitude(), request.getLongitude());
        Availability availability = Availability.create(
                request.getLeavingUserId(),
                location,
                request.getEstimatedLeavingTime()
        );

        availabilityRepository.save(availability);

        publishDomainEvents(availability);

        return availabilityMapper.toResponse(availability);
    }

    @Override
    public List<AvailabilityResponse> searchAvailableSpots(SearchCriteria criteria) {
        List<Availability> availabilities = availabilityRepository.findAvailableInArea(
                criteria.getLatitude(),
                criteria.getLongitude(),
                criteria.getRadius()
        );

        return availabilities.stream()
                .map(availabilityMapper::toResponse)
                .collect(Collectors.toList());
    }

    @Transactional
    @Override
    public ReservationRequestResponse requestReservation(AvailabilityId availabilityId, ReservationRequestDto requestDto) {

        Availability availability = availabilityRepository.findByAvailabilityId(availabilityId)
                .orElseThrow(() -> new IllegalArgumentException("Availability not found"));

        // Delegate to the aggregate root
        ReservationRequest reservationRequest = availability.createReservationRequest(
                requestDto.getArrivingUserId(),
                requestDto.getMessage()
        );

        // Save the aggregate
        availabilityRepository.save(availability);

        publishDomainEvents(availability);

        return availabilityMapper.toReservationResponse(reservationRequest);

    }

    @Transactional
    @Override
    public ReservationRequestResponse respondToReservation(UUID requestId, ReservationResponseDto responseDto) {
        Availability availability = availabilityRepository.findByReservationRequestId(requestId)
                .orElseThrow(() -> new IllegalArgumentException("Availability not found"));

        // Delegate to the aggregate root
        availability.respondToReservationRequest(requestId, responseDto.getLeavingUserId(), responseDto.isAccepted());

        // Save the aggregate
        availabilityRepository.save(availability);

        // Retrieve the updated reservation request
        ReservationRequest reservationRequest = availability.getReservationRequests().stream()
                .filter(r -> r.getRequestId().equals(requestId))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Reservation request not found"));

        publishDomainEvents(availability);

        return availabilityMapper.toReservationResponse(reservationRequest);
    }

    @Transactional
    @Override
    public AvailabilityResponse confirmSpotTaken(AvailabilityId availabilityId, ConfirmSpotTakenRequest request) {
        Availability availability = availabilityRepository.findByAvailabilityId(availabilityId)
                .orElseThrow(() -> new IllegalArgumentException("Availability not found"));

        availability.confirmTaken(request.getArrivingUserId());

        availabilityRepository.save(availability);

        publishDomainEvents(availability);

        return availabilityMapper.toResponse(availability);
    }

    @Transactional
    @Override
    public AvailabilityResponse cancelAvailability(AvailabilityId availabilityId, CancelAvailabilityRequest request) {
        Availability availability = availabilityRepository.findByAvailabilityId(availabilityId)
                .orElseThrow(() -> new IllegalArgumentException("Availability not found"));

        availability.cancel(request.getUserId());

        availabilityRepository.save(availability);

        publishDomainEvents(availability);

        return availabilityMapper.toResponse(availability);
    }

    private void publishDomainEvents(Availability availability) {
        List<DomainEvent> events = availability.domainEvents();
        for (DomainEvent event : events) {
            domainEventPublisher.publish(event);
        }
    }
}package com.parkingexchange.parking.application.service;


import com.parkingexchange.parking.application.dto.*;
import com.parkingexchange.parking.domain.model.AvailabilityId;

import java.util.List;
import java.util.UUID;

public interface AvailabilityApplicationService {

    AvailabilityResponse reportAvailability(AvailabilityRequest request);

    List<AvailabilityResponse> searchAvailableSpots(SearchCriteria criteria);

    ReservationRequestResponse requestReservation(AvailabilityId availabilityId, ReservationRequestDto requestDto);

    ReservationRequestResponse respondToReservation(UUID requestId, ReservationResponseDto responseDto);

    AvailabilityResponse confirmSpotTaken(AvailabilityId availabilityId, ConfirmSpotTakenRequest request);

    AvailabilityResponse cancelAvailability(AvailabilityId availabilityId, CancelAvailabilityRequest request);
}package com.parkingexchange.parking.domain.event;

import com.parkingexchange.common.domain.model.DomainEvent;

import java.time.Instant;
import java.util.UUID;

public class AvailabilityCanceledEvent implements DomainEvent {

    private final UUID availabilityId;
    private final UUID arrivingUserId;
    private final Instant occurredOn;
    private final String moduleName = "parking"; // TODO you should't set this manually


    public AvailabilityCanceledEvent(UUID availabilityId, UUID arrivingUserId) {
        this.occurredOn = Instant.now();
        this.availabilityId = availabilityId;
        this.arrivingUserId = arrivingUserId;
    }

    @Override
    public int eventVersion() {
        return 1;
    }

    @Override
    public Instant occurredOn() {
        return occurredOn;
    }

    @Override
    public String moduleName() {
        return moduleName;
    }

    public UUID getAvailabilityId() {
        return availabilityId;
    }

    public UUID getArrivingUserId() {
        return arrivingUserId;
    }

}
package com.parkingexchange.parking.domain.event;

import com.parkingexchange.common.domain.model.DomainEvent;

import java.time.Instant;
import java.util.UUID;

public class ReservationRequestCreatedEvent implements DomainEvent {

    private final UUID availabilityId;
    private final UUID requestId;
    private final UUID arrivingUserId;
    private final String message;
    private final Instant occurredOn;
    private final String moduleName = "parking"; // TODO you should't set this manually

    public ReservationRequestCreatedEvent(UUID availabilityId, UUID requestId, UUID arrivingUserId, String message) {
        this.availabilityId = availabilityId;
        this.requestId = requestId;
        this.arrivingUserId = arrivingUserId;
        this.message = message;
        this.occurredOn = Instant.now();
    }

    @Override
    public int eventVersion() {
        return 1;
    }

    @Override
    public Instant occurredOn() {
        return occurredOn;
    }

    @Override
    public String moduleName() {
        return moduleName;
    }

}

package com.parkingexchange.parking.domain.event;

import com.parkingexchange.common.domain.model.DomainEvent;

import java.time.Instant;
import java.util.UUID;

public class ReservationRequestRespondedEvent implements DomainEvent {

    private final UUID availabilityId;
    private final UUID requestId;
    private final boolean accepted;
    private final Instant occurredOn;
    private final String moduleName = "parking"; // TODO you should't set this manually


    public ReservationRequestRespondedEvent(UUID availabilityId, UUID requestId, boolean accepted) {
        this.occurredOn = Instant.now();
        this.availabilityId = availabilityId;
        this.requestId = requestId;
        this.accepted = accepted;
    }

    @Override
    public int eventVersion() {
        return 1;
    }

    @Override
    public Instant occurredOn() {
        return occurredOn;
    }

    @Override
    public String moduleName() {
        return moduleName;
    }

    public UUID getAvailabilityId() {
        return availabilityId;
    }

    public UUID getRequestId() {
        return requestId;
    }

    public boolean isAccepted() {
        return accepted;
    }
}
package com.parkingexchange.parking.domain.event;

import com.parkingexchange.common.domain.model.DomainEvent;

import java.time.Instant;
import java.util.UUID;

public class SpotTakenConfirmedEvent implements DomainEvent {

    private final UUID availabilityId;
    private final UUID arrivingUserId;
    private final Instant occurredOn;
    private final String moduleName = "parking"; // TODO you should't set this manually


    public SpotTakenConfirmedEvent(UUID availabilityId, UUID arrivingUserId) {
        this.occurredOn = Instant.now();
        this.availabilityId = availabilityId;
        this.arrivingUserId = arrivingUserId;
    }

    @Override
    public int eventVersion() {
        return 1;
    }

    @Override
    public Instant occurredOn() {
        return occurredOn;
    }

    @Override
    public String moduleName() {
        return moduleName;
    }

    public UUID getAvailabilityId() {
        return availabilityId;
    }

    public UUID getArrivingUserId() {
        return arrivingUserId;
    }

}
package com.parkingexchange.parking.domain.model;


import jakarta.persistence.Embeddable;

import java.util.UUID;

@Embeddable
public class AvailabilityId {

    private final UUID id;

    private AvailabilityId() {
        this.id = UUID.randomUUID();
    }

    public AvailabilityId(UUID id) {
        if (id == null) {
            throw new IllegalArgumentException("AvailabilityId cannot be null");
        }
        this.id = id;
    }

    public static AvailabilityId generate() {
        return new AvailabilityId();
    }

    public static AvailabilityId of(UUID id) {
        return new AvailabilityId(id);
    }

    public UUID getValue() {
        return id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        AvailabilityId that = (AvailabilityId) o;

        return id.equals(that.id);
    }

    @Override
    public int hashCode() {
        return id.hashCode();
    }

    // For logging and debugging
    @Override
    public String toString() {
        return id.toString();
    }
}package com.parkingexchange.parking.domain.model;

import com.parkingexchange.common.domain.model.BaseEntity;
import com.parkingexchange.parking.domain.event.AvailabilityCanceledEvent;
import com.parkingexchange.parking.domain.event.ReservationRequestCreatedEvent;
import com.parkingexchange.parking.domain.event.ReservationRequestRespondedEvent;
import com.parkingexchange.parking.domain.event.SpotTakenConfirmedEvent;
import jakarta.persistence.*;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDateTime;
import java.util.*;


/**
 * Aggregate Root Entity representing the availability of a parking spot.
 */
@Entity
public class Availability extends BaseEntity {

    @EmbeddedId
    private AvailabilityId availabilityId;

    @NotNull
    @Column(name = "leaving_user_id", nullable = false)
    private UUID leavingUserId;

    @NotNull
    @Valid
    @Embedded
    private Location location;

    @NotNull
    @Future(message = "Estimated leaving time must be in the future")
    @Column(name = "estimated_leaving_time", nullable = false)
    private LocalDateTime estimatedLeavingTime;

    @NotNull
    @Enumerated(EnumType.STRING)
    @Column(name = "status", nullable = false)
    private AvailabilityStatus status;

    @Column(name = "reserved_by_user_id")
    private UUID reservedByUserId;

    @OneToMany(mappedBy = "availability", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<ReservationRequest> reservationRequests = new ArrayList<>();

    protected Availability() {
        // Required by JPA
    }

    private Availability(AvailabilityId availabilityId,
                         UUID leavingUserId,
                         Location location,
                         LocalDateTime estimatedLeavingTime) {
        this.setAvailabilityId(availabilityId);
        this.setLeavingUserId(leavingUserId);
        this.setLocation(location);
        this.setEstimatedLeavingTime(estimatedLeavingTime);
        this.setStatus(AvailabilityStatus.AVAILABLE);
    }

    // Static factory method for creation
    public static Availability create(UUID leavingUserId, Location location, LocalDateTime estimatedLeavingTime) {
        AvailabilityId availabilityId = AvailabilityId.generate();
        return new Availability(availabilityId, leavingUserId, location, estimatedLeavingTime);
    }

    // Business methods

    public ReservationRequest createReservationRequest(UUID arrivingUserId, String message) {
        // Check if there's already a pending request from this user
        boolean hasExistingRequest = reservationRequests.stream()
                .anyMatch(request -> request.getArrivingUserId().equals(arrivingUserId)
                        && request.getStatus() == RequestStatus.PENDING);

        assertStateFalse(hasExistingRequest, "You already have a pending reservation request for this availability.");

        // Create a new reservation request
        ReservationRequest request = ReservationRequest.create(
                arrivingUserId,
                this.leavingUserId,
                message,
                this
        );

        // Add the request to the collection
        this.reservationRequests.add(request);

        // Update status if necessary
        this.requestReservation(arrivingUserId);

        this.publish(new ReservationRequestCreatedEvent(
                this.availabilityId.getValue(),
                request.getRequestId(),
                arrivingUserId,
                message
        ));

        return request;
    }

    public void respondToReservationRequest(UUID requestId, UUID leavingUserId, boolean accepted) {
        assertArgumentTrue(this.leavingUserId.equals(leavingUserId),
                "You do not have permission to respond to this request");

        ReservationRequest request = this.reservationRequests.stream()
                .filter(r -> r.getRequestId().equals(requestId))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Reservation request not found"));

        request.respond(leavingUserId, accepted);

        this.publish(new ReservationRequestRespondedEvent(
                this.availabilityId.getValue(),
                requestId,
                accepted
        ));

        if (accepted) {
            this.reserve(request.getArrivingUserId());
        } else {
            this.makeAvailableAfterRejection();
        }
    }

    public void requestReservation(UUID arrivingUserId) {
        assertArgumentTrue(this.status.equals(AvailabilityStatus.AVAILABLE),
                "Spot is not available for reservation");
        assertArgumentNotNull(arrivingUserId, "Arriving user ID cannot be null");
        this.setStatus(AvailabilityStatus.REQUEST_PENDING);
    }

    /**
     * Reserves this availability for a given arriving user.
     *
     * @param arrivingUserId the ID of the arriving user
     * @throws IllegalArgumentException if the spot is not available or the arrivingUserId is null
     */
    public void reserve(UUID arrivingUserId) {
        this.assertArgumentTrue(this.getStatus().equals(AvailabilityStatus.AVAILABLE),
                "Only available spots can be reserved");
        this.assertArgumentNotNull(arrivingUserId, "ArrivingUserId cannot be null");
        this.setReservedByUserId(arrivingUserId);
        this.setStatus(AvailabilityStatus.RESERVED);
    }

    private void setStatus(AvailabilityStatus aStatus){
        this.assertArgumentNotNull(aStatus, "status cannot be null");
        this.status = aStatus;
    }

    /**
     * Makes the availability available again after a reservation request is rejected.
     *
     * This method ensures that the availability's status is updated appropriately
     * and any business rules are enforced.
     */
    public void makeAvailableAfterRejection() {
        assertStateTrue(this.status == AvailabilityStatus.REQUEST_PENDING,
                "Availability status must be REQUEST_PENDING to make it available again.");
        this.setStatus(AvailabilityStatus.AVAILABLE);
        this.setReservedByUserId(null);
    }

    /**
     * Confirms that the spot has been taken by the arriving user.
     *
     * @param arrivingUserId the ID of the arriving user
     * @throws IllegalArgumentException if the spot is not reserved or reserved by another user
     */
    public void confirmTaken(UUID arrivingUserId) {
        this.assertArgumentTrue(this.getStatus().equals(AvailabilityStatus.RESERVED),
                "Only reserved spots can be confirmed as taken");
        this.assertArgumentTrue(this.getReservedByUserId().equals(arrivingUserId),
                "This spot is not reserved by the provided user");
        this.setStatus(AvailabilityStatus.TAKEN);

        this.publish(new SpotTakenConfirmedEvent(
                this.availabilityId.getValue(),
                arrivingUserId
        ));
    }

    /**
     * Cancels this availability.
     *
     * <p>This method can be called by the leaving user.
     * It checks for the necessary permissions and updates the status accordingly.</p>
     *
     * @param userId the ID of the user attempting to cancel
     * @throws IllegalStateException if the user does not have permission or the availability is already canceled
     */

    public void cancel(UUID userId) {
        boolean hasPermission = this.leavingUserId.equals(userId);
        assertStateTrue(hasPermission, "You do not have permission to cancel this availability");
        assertStateFalse(this.status == AvailabilityStatus.CANCELED, "Availability is already canceled");
        this.setStatus(AvailabilityStatus.CANCELED);

        // Cancel all pending reservation requests
        for (ReservationRequest request : reservationRequests) {
            if (request.getStatus() == RequestStatus.PENDING) {
                request.cancel();
            }
        }

        this.publish(new AvailabilityCanceledEvent(
                this.availabilityId.getValue(),
                userId
        ));
    }

    // Getters and Setters with validation where applicable

    /**
     * Returns the unique identifier of this {@code Availability}.
     *
     * @return the {@code AvailabilityId}
     */
    public AvailabilityId getAvailabilityId() {
        return availabilityId;
    }

    private void setAvailabilityId(AvailabilityId availabilityId) {
        this.assertArgumentNotNull(availabilityId, "AvailabilityId cannot be null");
        this.availabilityId = availabilityId;
    }

    /**
     * Returns the ID of the user who is leaving the parking spot.
     *
     * @return the leaving user's ID
     */
    public UUID getLeavingUserId() {
        return leavingUserId;
    }

    private void setLeavingUserId(UUID leavingUserId) {
        this.assertArgumentNotNull(leavingUserId, "LeavingUserId cannot be null");
        this.leavingUserId = leavingUserId;
    }

    /**
     * Returns the ID of the user who has reserved this spot, if any.
     *
     * @return the reserving user's ID, or null if not reserved
     */
    public UUID getReservedByUserId() {
        return reservedByUserId;
    }

    private void setReservedByUserId(UUID reservedByUserId) {
        this.assertArgumentNotNull(reservedByUserId, "ReservedByUserId cannot be null");
        this.reservedByUserId = reservedByUserId;
    }

    /**
     * Returns the location of the parking spot.
     *
     * @return the {@code Location}
     */
    public Location getLocation() {
        return location;
    }

    private void setLocation(Location location) {
        this.assertArgumentNotNull(location, "Location cannot be null");
        this.location = location;
    }

    /**
     * Returns the estimated time when the user will leave the spot.
     *
     * @return the estimated leaving time
     */
    public LocalDateTime getEstimatedLeavingTime() {
        return estimatedLeavingTime;
    }

    private void setEstimatedLeavingTime(LocalDateTime estimatedLeavingTime) {
        this.assertArgumentNotNull(estimatedLeavingTime, "Estimated leaving time cannot be null");
        this.assertArgumentTrue(estimatedLeavingTime.isAfter(LocalDateTime.now()),
                "Estimated leaving time must be in the future");
        this.estimatedLeavingTime = estimatedLeavingTime;
    }

    public List<ReservationRequest> getReservationRequests() {
        return Collections.unmodifiableList(reservationRequests);
    }

    /**
     * Returns the current status of this availability.
     *
     * @return the {@code AvailabilityStatus}
     */
    public AvailabilityStatus getStatus() {
        return status;
    }

    // equals and hashCode based on 'availabilityId'

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Availability that = (Availability) o;

        return availabilityId.equals(that.availabilityId);
    }

    @Override
    public int hashCode() {
        return availabilityId.hashCode();
    }

    @Override
    public String toString() {
        return "Availability{" +
                "availabilityId=" + availabilityId +
                ", leavingUserId=" + leavingUserId +
                ", location=" + location +
                ", estimatedLeavingTime=" + estimatedLeavingTime +
                ", status=" + status +
                ", reservedByUserId=" + reservedByUserId +
                '}';
    }
}package com.parkingexchange.parking.domain.model;

public enum AvailabilityStatus {
    AVAILABLE,
    REQUEST_PENDING,
    RESERVED,
    TAKEN,
    CANCELED
}
package com.parkingexchange.parking.domain.model;


import com.parkingexchange.common.AssertionConcern;
import jakarta.persistence.Embeddable;

import java.util.Objects;

/**
 * Immutable Value Object representing a geographical location.
 *
 * <p>This class encapsulates latitude and longitude coordinates, ensuring they are within valid ranges.</p>
 *
 * <p>Instances of this class are immutable and can be safely shared. The class uses assertion methods from
 * {@link AssertionConcern} to validate the input parameters.</p>
 */
@Embeddable
public class Location extends AssertionConcern {

    private final double latitude;
    private final double longitude;

    /**
     * Protected no-argument constructor for JPA.
     *
     * <p>Sets the latitude and longitude to 0.0 by default. This constructor should not be used directly.</p>
     */
    protected Location() {
        // Required by JPA
        this.latitude = 0.0;
        this.longitude = 0.0;
    }

    /**
     * Constructs a new {@code Location} with the specified latitude and longitude.
     *
     * @param latitude  the latitude coordinate, must be between -90 and 90 degrees
     * @param longitude the longitude coordinate, must be between -180 and 180 degrees
     * @throws IllegalArgumentException if the latitude or longitude are out of valid ranges
     */
    public Location(double latitude, double longitude) {
        // Validate latitude range
        this.assertArgumentRange(latitude, -90.0, 90.0,
                "Latitude must be between -90 and 90 degrees");

        // Validate longitude range
        this.assertArgumentRange(longitude, -180.0, 180.0,
                "Longitude must be between -180 and 180 degrees");

        this.latitude = latitude;
        this.longitude = longitude;
    }

    /**
     * Returns the latitude coordinate of this {@code Location}.
     *
     * @return the latitude value
     */
    public double getLatitude() {
        return latitude;
    }

    /**
     * Returns the longitude coordinate of this {@code Location}.
     *
     * @return the longitude value
     */
    public double getLongitude() {
        return longitude;
    }

    /**
     * Determines whether this location is equal to another object.
     *
     * <p>Two locations are considered equal if they have the same latitude and longitude values.</p>
     *
     * @param o the reference object with which to compare
     * @return {@code true} if this location is the same as the argument; {@code false} otherwise
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Location location = (Location) o;

        return Double.compare(location.latitude, latitude) == 0 &&
                Double.compare(location.longitude, longitude) == 0;
    }

    /**
     * Returns a hash code value for this location.
     *
     * <p>This method is supported for the benefit of hash tables such as those provided by
     * {@link java.util.HashMap}.</p>
     *
     * @return the hash code value for this location
     */
    @Override
    public int hashCode() {
        return Objects.hash(latitude, longitude);
    }

    /**
     * Returns a string representation of this location.
     *
     * <p>The string representation consists of the latitude and longitude enclosed in parentheses.</p>
     *
     * @return a string representation of this location
     */
    @Override
    public String toString() {
        return "Location(" + "latitude=" + latitude + ", longitude=" + longitude + ')';
    }
}package com.parkingexchange.parking.domain.model;

public enum RequestStatus {
    PENDING,
    ACCEPTED,
    REJECTED,
    CANCELED
}package com.parkingexchange.parking.domain.model;

import com.parkingexchange.common.AssertionConcern;
import jakarta.persistence.*;

import java.time.LocalDateTime;
import java.util.UUID;

/**
 * The {@code ReservationRequest} class represents a reservation request in the parking exchange system.
 * It models the interaction between an arriving user who wants to reserve a parking spot and a leaving user
 * who is offering the spot. The class includes details such as the users involved, the status of the request,
 * timestamps of when the request was made and responded to, any messages included with the request, and
 * the associated parking availability.
 *
 * <p>This class is an entity managed by JPA (Java Persistence API), making it persistable in a relational database.</p>
 */

@Entity
public class ReservationRequest extends AssertionConcern {

    @Id
    @GeneratedValue
    private UUID requestId;

    /**
     * Identifier of the user who is arriving and making the reservation request.
     */
    @Column(nullable = false)
    private UUID arrivingUserId;

    /**
     * Identifier of the user who is leaving and offering the parking spot.
     */
    @Column(nullable = false)
    private UUID leavingUserId;

    /**
     * Current status of the reservation request.
     */
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private RequestStatus status;

    /**
     * Timestamp when the reservation request was created.
     */
    @Column(nullable = false)
    private LocalDateTime requestedAt;

    /**
     * Timestamp when the reservation request was responded to.
     */
    private LocalDateTime respondedAt;

    /**
     * Optional message included with the reservation request.
     */
    private String message;

    /**
     * The associated parking availability related to this request.
     */
    @ManyToOne
    private Availability availability;

    /**
     * Protected constructor for JPA use only.
     */
    protected ReservationRequest() {
    }

    /**
     * Creates a new reservation request.
     *
     * @param arrivingUserId the UUID of the arriving user
     * @param leavingUserId  the UUID of the leaving user
     * @param message        an optional message for the request
     * @param availability   the associated parking availability
     * @return a new instance of {@code ReservationRequest}
     */
    public static ReservationRequest create(UUID arrivingUserId, UUID leavingUserId,
                                            String message, Availability availability) {
        return new ReservationRequest(arrivingUserId, leavingUserId, message, availability);
    }

    /**
     * Private constructor to initialize a new reservation request with the provided details.
     *
     * @param arrivingUserId the UUID of the arriving user
     * @param leavingUserId  the UUID of the leaving user
     * @param message        an optional message for the request
     * @param availability   the associated parking availability
     */
    private ReservationRequest(UUID arrivingUserId, UUID leavingUserId,
                               String message, Availability availability) {
        this.setArrivingUserId(arrivingUserId);
        this.setLeavingUserId(leavingUserId);
        this.setStatus(RequestStatus.PENDING);
        this.setRequestedAt(LocalDateTime.now());
        this.setMessage(message);
        this.setAvailability(availability);
    }
    // Business methods

    /**
     * Allows the leaving user to respond to the reservation request, updating the status to accepted or rejected
     * and recording the response time.
     *
     * @param leavingUserId the UUID of the leaving user responding to the request
     * @param accepted      {@code true} if the request is accepted; {@code false} otherwise
     * @throws IllegalArgumentException if the leavingUserId does not match or the request is not pending
     */
    public void respond(UUID leavingUserId, boolean accepted) {
        assertArgumentTrue(this.leavingUserId.equals(leavingUserId), "You do not have permission to respond to this request");
        assertArgumentTrue(this.status.equals(RequestStatus.PENDING), "Request has already been responded to");

        this.setRespondedAt(LocalDateTime.now());
        this.setStatus(accepted ? RequestStatus.ACCEPTED : RequestStatus.REJECTED);
    }

    /**
     * Allows the arriving user to cancel the reservation request if it's still pending, updating the status
     * to canceled and recording the cancellation time.
     *
     * @throws IllegalArgumentException if the request is not pending
     */
    public void cancel() {
        assertArgumentTrue(this.status == RequestStatus.PENDING, "Only pending requests can be canceled.");
        this.setStatus(RequestStatus.CANCELED);
        this.setRespondedAt(LocalDateTime.now());
    }

    // Getters and Setters with validation

    /**
     * Returns the unique identifier for the reservation request.
     *
     * @return the {@code requestId}
     */
    public UUID getRequestId() {
        return requestId;
    }

    private void setAvailability(Availability availability) {
        this.availability = availability;
    }

    /**
     * Returns the associated availability related to this request.
     *
     * @return the {@code availability}
     */
    public Availability getAvailability() {
        return availability;
    }

    /**
     * Returns the UUID of the arriving user.
     *
     * @return the {@code arrivingUserId}
     */
    public UUID getArrivingUserId() {
        return arrivingUserId;
    }

    private void setArrivingUserId(UUID arrivingUserId) {
        assertArgumentNotNull(arrivingUserId, "ArrivingUserId cannot be null");
        this.arrivingUserId = arrivingUserId;
    }

    /**
     * Returns the UUID of the leaving user.
     *
     * @return the {@code leavingUserId}
     */
    public UUID getLeavingUserId() {
        return leavingUserId;
    }

    private void setLeavingUserId(UUID leavingUserId) {
        assertArgumentNotNull(leavingUserId, "LeavingUserId cannot be null");
        this.leavingUserId = leavingUserId;
    }

    /**
     * Returns the current status of the reservation request.
     *
     * @return the {@code status}
     */
    public RequestStatus getStatus() {
        return status;
    }

    private void setStatus(RequestStatus status) {
        assertArgumentNotNull(status, "Status cannot be null");
        this.status = status;
    }

    /**
     * Returns the timestamp when the reservation request was created.
     *
     * @return the {@code requestedAt} timestamp
     */
    public LocalDateTime getRequestedAt() {
        return requestedAt;
    }

    private void setRequestedAt(LocalDateTime requestedAt) {
        this.requestedAt = requestedAt;
    }

    /**
     * Returns the timestamp when the reservation request was responded to or canceled.
     *
     * @return the {@code respondedAt} timestamp
     */
    public LocalDateTime getRespondedAt() {
        return respondedAt;
    }

    private void setRespondedAt(LocalDateTime respondedAt) {
        this.respondedAt = respondedAt;
    }

    /**
     * Returns the optional message included with the reservation request.
     *
     * @return the {@code message}
     */
    public String getMessage() {
        return message;
    }

    private void setMessage(String message) {
        this.message = message;
    }
}package com.parkingexchange.parking.domain.repository;

import com.parkingexchange.parking.domain.model.Availability;
import com.parkingexchange.parking.domain.model.AvailabilityId;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

public interface AvailabilityRepository {

    Availability save(Availability availability);

    Optional<Availability> findByAvailabilityId(AvailabilityId availabilityId);

    List<Availability> findAvailableInArea(double latitude, double longitude, double radius);

    Optional<Availability> findByReservationRequestId(@Param("requestId") UUID requestId);
    // Additional methods as needed
}package com.parkingexchange.parking.infrastructure.config;

import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ModelMapperConfig {

    @Bean
    public ModelMapper modelMapper() {
        return new ModelMapper();
    }
}package com.parkingexchange.parking.infrastructure.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {

    @Bean
    public WebClient userServiceWebClient(@Value("${user.context.base.url}") String baseUrl) {
        return WebClient.builder()
                .baseUrl(baseUrl)
                .build();
    }
}package com.parkingexchange.parking.infrastructure.interfaces.controller;

import com.parkingexchange.parking.application.service.AvailabilityApplicationService;
import com.parkingexchange.parking.application.dto.*;
import com.parkingexchange.parking.domain.model.AvailabilityId;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/api/availability")
public class AvailabilityController {

    private final AvailabilityApplicationService availabilityService;

    public AvailabilityController(AvailabilityApplicationService availabilityService) {
        this.availabilityService = availabilityService;
    }

    @PostMapping
    public AvailabilityResponse reportAvailability(@RequestBody AvailabilityRequest request) {
        return availabilityService.reportAvailability(request);
    }

    @GetMapping("/search")
    public List<AvailabilityResponse> searchAvailableSpots(@RequestParam double latitude,
                                                           @RequestParam double longitude,
                                                           @RequestParam double radius) {
        SearchCriteria criteria = new SearchCriteria();
        criteria.setLatitude(latitude);
        criteria.setLongitude(longitude);
        criteria.setRadius(radius);
        return availabilityService.searchAvailableSpots(criteria);
    }

    @PostMapping("/{availabilityId}/request")
    public ReservationRequestResponse requestReservation(@PathVariable UUID availabilityId,
                                                         @RequestBody ReservationRequestDto requestDto) {
        return availabilityService.requestReservation(AvailabilityId.of(availabilityId), requestDto);
    }

    @PostMapping("/requests/{requestId}/respond")
    public ReservationRequestResponse respondToReservation(@PathVariable UUID requestId,
                                                           @RequestBody ReservationResponseDto responseDto) {
        return availabilityService.respondToReservation(requestId, responseDto);
    }

    @PostMapping("/{availabilityId}/confirm")
    public AvailabilityResponse confirmSpotTaken(@PathVariable UUID availabilityId,
                                                 @RequestBody ConfirmSpotTakenRequest request) {
        return availabilityService.confirmSpotTaken(AvailabilityId.of(availabilityId), request);
    }

    @PostMapping("/{availabilityId}/cancel")
    public AvailabilityResponse cancelAvailability(@PathVariable UUID availabilityId,
                                                   @RequestBody CancelAvailabilityRequest request) {
        return availabilityService.cancelAvailability(AvailabilityId.of(availabilityId), request);
    }
}package com.parkingexchange.parking.infrastructure.interfaces.dto;

public class UserExistsResponse {

    private boolean exists;

    // Getters and Setters

    public boolean isExists() {
        return exists;
    }

    public void setExists(boolean exists) {
        this.exists = exists;
    }
}package com.parkingexchange.parking.infrastructure.interfaces.dto;


public class UserReputationResponse {

    private double reputation;

    // Getters and Setters

    public double getReputation() {
        return reputation;
    }

    public void setReputation(double reputation) {
        this.reputation = reputation;
    }
}
package com.parkingexchange.parking.infrastructure.resporitory;

import com.parkingexchange.parking.domain.model.Availability;
import com.parkingexchange.parking.domain.repository.AvailabilityRepository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Repository
public interface JpaAvailabilityRepository extends AvailabilityRepository, JpaRepository<Availability, UUID> {

    @Query("SELECT a FROM Availability a WHERE "
            + "(6371 * acos(cos(radians(:latitude)) * cos(radians(a.location.latitude)) "
            + "* cos(radians(a.location.longitude) - radians(:longitude)) "
            + "+ sin(radians(:latitude)) * sin(radians(a.location.latitude)))) < :radius")
    List<Availability> findAvailableInArea(
            @Param("latitude") double latitude,
            @Param("longitude") double longitude,
            @Param("radius") double radius);

    @Query("SELECT a FROM Availability a JOIN a.reservationRequests r WHERE r.requestId = :requestId")
    Optional<Availability> findByReservationRequestId(@Param("requestId") UUID requestId);


}package com.parkingexchange.parking;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@ComponentScan(basePackages = {"com.parkingexchange"})
@EnableJpaRepositories(basePackages = "com.parkingexchange")
@EntityScan(basePackages = "com.parkingexchange")
public class ParkingApplication {

	public static void main(String[] args) {
		SpringApplication.run(ParkingApplication.class, args);
	}

}
package com.parkingexchange.parking;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ParkingApplicationTests {

	@Test
	void contextLoads() {
	}

}
package com.parkingexchange.parking.application.dto.mapper;

import com.parkingexchange.parking.application.dto.AvailabilityResponse;
import com.parkingexchange.parking.application.dto.ReservationRequestResponse;
import com.parkingexchange.parking.domain.model.Availability;
import com.parkingexchange.parking.domain.model.ReservationRequest;
import javax.annotation.processing.Generated;
import org.springframework.stereotype.Component;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2024-09-29T18:05:30+0200",
    comments = "version: 1.6.2, compiler: javac, environment: Java 21 (Oracle Corporation)"
)
@Component
public class AvailabilityMapperImpl implements AvailabilityMapper {

    @Override
    public AvailabilityResponse toResponse(Availability availability) {
        if ( availability == null ) {
            return null;
        }

        AvailabilityResponse availabilityResponse = new AvailabilityResponse();

        availabilityResponse.setAvailabilityId( map( availability.getAvailabilityId() ) );
        availabilityResponse.setLeavingUserId( availability.getLeavingUserId() );
        availabilityResponse.setEstimatedLeavingTime( availability.getEstimatedLeavingTime() );
        availabilityResponse.setStatus( availability.getStatus() );
        availabilityResponse.setReservedByUserId( availability.getReservedByUserId() );

        return availabilityResponse;
    }

    @Override
    public ReservationRequestResponse toReservationResponse(ReservationRequest request) {
        if ( request == null ) {
            return null;
        }

        ReservationRequestResponse reservationRequestResponse = new ReservationRequestResponse();

        reservationRequestResponse.setRequestId( request.getRequestId() );
        reservationRequestResponse.setArrivingUserId( request.getArrivingUserId() );
        reservationRequestResponse.setLeavingUserId( request.getLeavingUserId() );
        reservationRequestResponse.setStatus( request.getStatus() );
        reservationRequestResponse.setRequestedAt( request.getRequestedAt() );
        reservationRequestResponse.setRespondedAt( request.getRespondedAt() );
        reservationRequestResponse.setMessage( request.getMessage() );

        return reservationRequestResponse;
    }
}
Consider the above and consider that i want to add the feature to get the requests such that i can confirm/deny the different requests(otherwise i dont' hae the id)