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 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.
This method can be called by the leaving user.
It checks for the necessary permissions and updates the status accordingly.
@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 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.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.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.
This class is an entity managed by JPA (Java Persistence API), making it persistable in a relational database. */
@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;
}
}
add the reservation requests To the dto