package com.ankurm.redisjson;

import org.springframework.data.redis.core.RedisJsonOperations;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;

/**
 * Every method here is a single Redis round trip. JsonOperations talks
 * the RedisJSON module's own commands (JSON.SET, JSON.GET, JSON.ARRAPPEND,
 * ...), which operate on the document in place server-side - there is no
 * "fetch the whole value, change a field, write it all back" step for
 * Spring Data to hide, because there is no such step to take.
 */
@Service
public class CartJsonService {

    private final RedisJsonOperations<String> json;

    public CartJsonService(RedisJsonOperations<String> json) {
        this.json = json;
    }

    public void saveCart(Cart cart) {
        json.value(key(cart.cartId())).set(cart);
    }

    public Cart getCart(String cartId) {
        return json.value(key(cartId)).get().as(Cart.class);
    }

    /** Reads one field via JSONPath without transferring the rest of the document. */
    public String getCustomerId(String cartId) {
        return json.value(key(cartId)).path("$.customerId").get().as(String.class);
    }

    /**
     * Appends one item to the items array server-side (JSON.ARRAPPEND) -
     * no read, no deserialize-mutate-serialize, no risk of overwriting a
     * concurrent change to a sibling field.
     */
    public List<Long> addItem(String cartId, Cart.CartItem item) {
        return json.array(key(cartId)).path("$.items").append(item);
    }

    public int itemCount(String cartId) {
        return json.value(key(cartId)).path("$.items[*]").get().matches().toList().size();
    }

    public boolean deleteCart(String cartId) {
        return Boolean.TRUE.equals(json.delete(key(cartId)));
    }

    public BigDecimal total(String cartId) {
        List<Cart.CartItem> items = json.value(key(cartId)).path("$.items[*]")
                .get().matches().as(Cart.CartItem.class);
        return items.stream()
                .map(i -> i.unitPrice().multiply(BigDecimal.valueOf(i.quantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    private String key(String cartId) {
        return "cart:" + cartId;
    }
}
