package com.ankurm.redisjson;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;

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

import static org.assertj.core.api.Assertions.assertThat;

/**
 * Runs the JsonOperations behaviour against a real Redis 8.8.3 server.
 * Redis 8 ships the RedisJSON (ReJSON) module built in - confirmed with
 * `MODULE LIST` on a plain redis:8.8.3 container, no redis-stack image
 * needed - so a GenericContainer of the plain redis image is enough for
 * spring-boot-testcontainers' @ServiceConnection to wire up.
 */
@Testcontainers
@SpringBootTest
class CartJsonServiceTest {

    @Container
    @ServiceConnection(name = "redis")
    static GenericContainer<?> redis =
            new GenericContainer<>(DockerImageName.parse("redis:8.8.3")).withExposedPorts(6379);

    @Autowired CartJsonService cartJsonService;

    @Test
    void wholeDocumentRoundTrip() {
        Cart cart = new Cart("cart-1", "cust-77", List.of(
                new Cart.CartItem("SKU-1", 2, new BigDecimal("19.99")),
                new Cart.CartItem("SKU-2", 1, new BigDecimal("49.00"))));
        cartJsonService.saveCart(cart);

        Cart read = cartJsonService.getCart("cart-1");
        Transcript t = new Transcript("28-whole-document-roundtrip.txt");
        t.line("saved cart-1 with %d items, customerId=%s", cart.items().size(), cart.customerId());
        t.line("read back: cartId=%s customerId=%s items=%d", read.cartId(), read.customerId(), read.items().size());
        t.write();

        // Jackson round-trips BigDecimal through JSON's plain number syntax,
        // which does not carry scale - 49.00 comes back as 49.0. They are
        // numerically equal (compareTo == 0) but record equals() treats
        // different scales as different values, so the fields are checked
        // individually with isEqualByComparingTo rather than one isEqualTo
        // on the whole record.
        assertThat(read.cartId()).isEqualTo(cart.cartId());
        assertThat(read.customerId()).isEqualTo(cart.customerId());
        assertThat(read.items()).hasSize(cart.items().size());
        for (int i = 0; i < cart.items().size(); i++) {
            assertThat(read.items().get(i).sku()).isEqualTo(cart.items().get(i).sku());
            assertThat(read.items().get(i).quantity()).isEqualTo(cart.items().get(i).quantity());
            assertThat(read.items().get(i).unitPrice()).isEqualByComparingTo(cart.items().get(i).unitPrice());
        }
    }

    @Test
    void pathReadFetchesOneFieldWithoutTheWholeDocument() {
        cartJsonService.saveCart(new Cart("cart-2", "cust-88", List.of(
                new Cart.CartItem("SKU-9", 3, new BigDecimal("5.00")))));

        String customerId = cartJsonService.getCustomerId("cart-2");
        Transcript t = new Transcript("28-path-read.txt");
        t.line("JSON.GET cart:cart-2 $.customerId -> \"%s\"", customerId);
        t.write();

        assertThat(customerId).isEqualTo("cust-88");
    }

    @Test
    void arrayAppendAddsAnItemWithoutReadingTheDocumentFirst() {
        cartJsonService.saveCart(new Cart("cart-3", "cust-99", List.of(
                new Cart.CartItem("SKU-1", 1, new BigDecimal("10.00")))));

        List<Long> newLength = cartJsonService.addItem("cart-3",
                new Cart.CartItem("SKU-2", 2, new BigDecimal("15.00")));

        int countAfter = cartJsonService.itemCount("cart-3");
        BigDecimal total = cartJsonService.total("cart-3");

        Transcript t = new Transcript("28-array-append.txt");
        t.line("JSON.ARRAPPEND cart:cart-3 $.items <new item> -> new array length = %s", newLength.getFirst());
        t.line("item_count_after_append=%d", countAfter);
        t.line("total=%s (10.00 x1 + 15.00 x2, computed from the array in one JSONPath read)", total);
        t.write();

        assertThat(newLength).containsExactly(2L);
        assertThat(countAfter).isEqualTo(2);
        assertThat(total).isEqualByComparingTo(new BigDecimal("40.00"));
    }

    @Test
    void deleteRemovesTheWholeDocument() {
        cartJsonService.saveCart(new Cart("cart-4", "cust-100", List.of()));
        boolean deletedFirstTime = cartJsonService.deleteCart("cart-4");
        boolean deletedSecondTime = cartJsonService.deleteCart("cart-4");

        Transcript t = new Transcript("28-delete.txt");
        t.line("delete cart-4 (exists) -> %b", deletedFirstTime);
        t.line("delete cart-4 (already gone) -> %b", deletedSecondTime);
        t.write();

        assertThat(deletedFirstTime).isTrue();
        assertThat(deletedSecondTime).isFalse();
    }
}
