package com.food.order.system.domain.entity;

import com.food.order.sysyem.entity.AggregateRoot;
import com.food.order.system.domain.exception.OrderDomainException;
import com.food.order.system.domain.valueobject.OrderItemId;
import com.food.order.system.domain.valueobject.StreetAddress;
import com.food.order.system.domain.valueobject.TrackingId;
import com.food.order.sysyem.valueobject.*;

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

public class Order extends AggregateRoot<OrderId> {

    public static final String FAILURE_MESSAGE_DELIMITER = ",";

    private final CustomerId customerId;
    private final RestaurantId restaurantId;
    private final StreetAddress deliveryAddress;
    private final Money price;
    private final List<OrderItem> items;

    private TrackingId trackingId;
    private OrderStatus status;
    private List<String> failureMessages;

    public void initializeOrder(){
        setId(new OrderId(UUID.randomUUID()));
        trackingId = new TrackingId(UUID.randomUUID());
        status = OrderStatus.PENDING;
        initializeOrderItems();
    }

    public void pay(){
        if(!Objects.equals(status, OrderStatus.PENDING))
            throw new OrderDomainException("Order status is not pending !");
        status = OrderStatus.PAID;

    }

    public void approve(){
        if(!Objects.equals(status, OrderStatus.PAID))
            throw new OrderDomainException("Order status is not paid !");
        status = OrderStatus.APPROVED;
    }

    public void initCancel(List<String> failureMessages){
        if(!Objects.equals(status, OrderStatus.PAID))
            throw new OrderDomainException("Order status is not correct for initCancel operation !");
        status = OrderStatus.CANCELLED;
        updateFailureMessages(failureMessages);
    }

    public void cancel(List<String> failureMessages){
        if(Objects.equals(status, OrderStatus.CANCELLING) || Objects.equals(status, OrderStatus.PENDING))
            throw new OrderDomainException("Order status is not correct for cancel operation !");
        status = OrderStatus.CANCELLED;
        updateFailureMessages(failureMessages);
    }

    private void updateFailureMessages(List<String> failureMessages) {
        if (Objects.nonNull(this.failureMessages) && Objects.nonNull(failureMessages)) {
            this.failureMessages.addAll(failureMessages.stream().filter(Objects::nonNull).toList());
        } else {
            this.failureMessages = failureMessages;
        }
    }

    public void validateOrder(){
        validateInitialOrder();
        validateTotalPrice();
        validateItemsPrice();
    }

    private void validateItemsPrice() {
        Money orderItemsTotal = items
                .stream()
                .map(orderItem -> {
                   validateItemPrice(orderItem);
                   return orderItem.getSubTotal();
                })
                .reduce(Money.ZERO, Money::add);

        if(!Objects.equals(orderItemsTotal, price)){
            throw new OrderDomainException("Order total price is not equal to the sum of order items prices");
        }
    }

    private void validateItemPrice(OrderItem orderItem) {
        if(!orderItem.isPriceValid()){
            throw new OrderDomainException("Order item price is not valid");
        }
    }

    private void validateTotalPrice() {
        if(Objects.nonNull(price) && !price.isGreaterThanZero()){
            throw new OrderDomainException("Total price cannot be less than zero");
        }
    }

    private void validateInitialOrder() {
        if (Objects.nonNull(status) && Objects.nonNull(getId()))
            throw new OrderDomainException("Order is not in correct state to be initialized");

    }

    private void initializeOrderItems() {
        long itemId = 1;
        for (OrderItem item : items) {
            item.initializeOrderItem(super.getId(), new OrderItemId(itemId++));
        }
    }

    private Order(Builder builder) {
        super.setId(builder.orderId);
        customerId = builder.customerId;
        restaurantId = builder.restaurantId;
        deliveryAddress = builder.deliveryAddress;
        price = builder.price;
        items = builder.items;
        trackingId = builder.trackingId;
        status = builder.status;
        failureMessages = builder.failureMessages;
    }

    public static Builder builder() {
        return new Builder();
    }

    public CustomerId getCustomerId() {
        return customerId;
    }

    public RestaurantId getRestaurantId() {
        return restaurantId;
    }

    public StreetAddress getDeliveryAddress() {
        return deliveryAddress;
    }

    public Money getPrice() {
        return price;
    }

    public List<OrderItem> getItems() {
        return items;
    }

    public TrackingId getTrackingId() {
        return trackingId;
    }

    public OrderStatus getStatus() {
        return status;
    }

    public List<String> getFailureMessages() {
        return failureMessages;
    }


    public static final class Builder {
        private OrderId orderId;
        private CustomerId customerId;
        private RestaurantId restaurantId;
        private StreetAddress deliveryAddress;
        private Money price;
        private List<OrderItem> items;
        private TrackingId trackingId;
        private OrderStatus status;
        private List<String> failureMessages;

        private Builder() {
        }

        public Builder orderId(OrderId val) {
            orderId = val;
            return this;
        }

        public Builder customerId(CustomerId val) {
            customerId = val;
            return this;
        }

        public Builder restaurantId(RestaurantId val) {
            restaurantId = val;
            return this;
        }

        public Builder deliveryAddress(StreetAddress val) {
            deliveryAddress = val;
            return this;
        }

        public Builder price(Money val) {
            price = val;
            return this;
        }

        public Builder items(List<OrderItem> val) {
            items = val;
            return this;
        }

        public Builder trackingId(TrackingId val) {
            trackingId = val;
            return this;
        }

        public Builder status(OrderStatus val) {
            status = val;
            return this;
        }

        public Builder failureMessages(List<String> val) {
            failureMessages = val;
            return this;
        }

        public Order build() {
            return new Order(this);
        }
    }
}
