package io.wealthwizards.appsynctesting.utils;

import com.google.gson.Gson;
import io.wealthwizards.appsynctesting.utils.transform.Transform;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.util.Map;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

@DisplayName("$util.transform")
public class TransformTest {
    @Nested
    @DisplayName(".toDynamoDBFilterExpression")
    class ToDynamoDBFilterExpression {
        @Test
        @DisplayName("should handle single condition and encode to DDB format")
        public void shouldHandleSingleCondition() {
            var transform = new Transform();

            var filter = Map.of(
                    "hello", Map.of(
                            "contains", "world"
                    )
            );

            var expected = Map.of(
                    "expression", "(contains(#hello,:hello_contains))",
                    "expressionNames", Map.of(
                            "#hello", "hello"
                    ),
                    "expressionValues", Map.of(
                            ":hello_contains", Map.of(
                                    "S", "world"
                            )
                    )
            );

            var actual = transform.toDynamoDBFilterExpression(filter);

            assertEquals(expected, new Gson().fromJson(actual, Map.class));
        }

        @Test
        @DisplayName("should handle and statements")
        public void shouldHandleAndStatements() {
            var transform = new Transform();

            var filter = Map.of(
                    "hello", Map.of(
                            "contains", "World",
                            "beginsWith", "Hello,"
                    ),
                    "another", Map.of(
                            "eq", "major key"
                    )
            );

            // TODO: the nested expression ordering here doesn't match the order from AppSync (tho semantically identical)
            // ... we should revisit if this is important
            var expression = "(#another = :another_eq) AND ((begins_with(#hello,:hello_beginsWith)) AND (contains(#hello,:hello_contains)))";
            var expressionNames = Map.of(
                    "#hello", "hello",
                    "#another", "another"
            );
            var expressionValues = Map.of(
                    ":hello_contains", Map.of(
                            "S", "World"
                    ),
                    ":hello_beginsWith", Map.of(
                            "S", "Hello,"
                    ),
                    ":another_eq", Map.of(
                            "S", "major key"
                    )
            );

            var actual = transform.toDynamoDBFilterExpression(filter);
            var useful = new Gson().fromJson(actual, Map.class);

            assertEquals(expression, useful.get("expression"));
            assertEquals(expressionValues, useful.get("expressionValues"));
            assertEquals(expressionNames, useful.get("expressionNames"));
        }

        @Test
        @DisplayName("should handle lists of conditions (trees)")
        public void shouldHandleStatementTrees() {
            var transform = new Transform();

            var filter = Map.of(
                    "hello", Map.of(
                            "contains", "World",
                            "beginsWith", "Hello,"
                    ),
                    "another", Map.of(
                            "eq", "major key"
                    )
            );

            // TODO: the nested expression ordering here doesn't match the order from AppSync (tho semantically identical)
            // ... we should revisit if this is important
            var expression = "(#another = :another_eq) AND ((begins_with(#hello,:hello_beginsWith)) AND (contains(#hello,:hello_contains)))";
            var expressionNames = Map.of(
                    "#hello", "hello",
                    "#another", "another"
            );
            var expressionValues = Map.of(
                    ":hello_contains", Map.of(
                            "S", "World"
                    ),
                    ":hello_beginsWith", Map.of(
                            "S", "Hello,"
                    ),
                    ":another_eq", Map.of(
                            "S", "major key"
                    )
            );

            var actual = transform.toDynamoDBFilterExpression(filter);
            var useful = new Gson().fromJson(actual, Map.class);

            assertEquals(expression, useful.get("expression"));
            assertEquals(expressionValues, useful.get("expressionValues"));
            assertEquals(expressionNames, useful.get("expressionNames"));
        }

        @Test
        @DisplayName("should handle nested lists of conditions (nested trees)")
        public void shouldHandleNestedStatementTrees() {
            var transform = new Transform();

            var filter = Map.of(
                    "hello", Map.of(
                            "contains", "World",
                            "beginsWith", "Hello,"
                    ),
                    "OR", List.of(
                            Map.of(
                                    "another", Map.of(
                                            "contains", "fu",
                                            "beginsWith", "abcde"
                                    ),
                                    "andAnother", Map.of(
                                            "eq", "Whitney"
                                    )
                            ),
                            Map.of(
                                    "alternative", Map.of(
                                            "eq", "Alt-J"
                                    )
                            ),
                            Map.of(
                                    "anotherAlt", Map.of(
                                            "eq", "Just Fine"
                                    )
                            )
                    )
            );

            var expression = "((((contains(#another,:OR_0_another_contains)) AND (begins_with(#another,:OR_0_another_beginsWith))) AND (#andAnother = :OR_0_andAnother_eq)) OR (#alternative = :OR_1_alternative_eq) OR (#anotherAlt = :OR_2_anotherAlt_eq)) AND ((begins_with(#hello,:hello_beginsWith)) AND (contains(#hello,:hello_contains)))";
            var expressionNames = Map.of(
                    "#hello", "hello",
                    "#another", "another",
                    "#andAnother", "andAnother",
                    "#alternative", "alternative",
                    "#anotherAlt", "anotherAlt"
            );
            var expressionValues = Map.of(
                    ":hello_contains", Map.of(
                            "S", "World"
                    ),
                    ":hello_beginsWith", Map.of(
                            "S", "Hello,"
                    ),
                    ":OR_0_another_contains", Map.of(
                            "S", "fu"
                    ),
                    ":OR_0_another_beginsWith", Map.of(
                            "S", "abcde"
                    ),
                    ":OR_0_andAnother_eq", Map.of(
                            "S", "Whitney"
                    ),
                    ":OR_1_alternative_eq", Map.of(
                            "S", "Alt-J"
                    ),
                    ":OR_2_anotherAlt_eq", Map.of(
                            "S", "Just Fine"
                    )
            );

            var actual = transform.toDynamoDBFilterExpression(filter);
            var useful = new Gson().fromJson(actual, Map.class);

            assertEquals(expression, useful.get("expression"));
            assertEquals(expressionValues, useful.get("expressionValues"));
            assertEquals(expressionNames, useful.get("expressionNames"));
        }

        @Test
        @DisplayName("should badly handle empty lists (empty sub-trees)")
        public void shouldBadlyHandleEmptyTrees() {
            var transform = new Transform();

            var filter = Map.of(
                    "OR", List.of(),
                    "hello", Map.of(
                            "eq", "world"
                    )
            );

            // this represents what AppSync spits out
            var expression = "(#hello = :hello_eq) AND ";
            var expressionNames = Map.of(
                    "#hello", "hello"
            );
            var expressionValues = Map.of(
                    ":hello_eq", Map.of(
                            "S", "world"
                    )
            );

            var actual = transform.toDynamoDBFilterExpression(filter);
            var useful = new Gson().fromJson(actual, Map.class);

            assertEquals(expression, useful.get("expression"));
            assertEquals(expressionValues, useful.get("expressionValues"));
            assertEquals(expressionNames, useful.get("expressionNames"));
        }

        @Test
        @DisplayName("should return null for invalid input to output original VTL")
        public void shouldReturnNull() {
            var transform = new Transform();

            var invalidFilter = Map.of(
                    "hello", "world"
            );

            var actual = transform.toDynamoDBFilterExpression(invalidFilter);

            assertNull(actual);
        }

        @Test
        @DisplayName("should handle `in` and `between` lists")
        public void shouldHandleParamLists() {
            var transform = new Transform();

            var filter = Map.of(
                    "hello", Map.of(
                            "in", List.of("hello", "hi", "hi but in french"),
                            "between", List.of("ha", "hz", "ignored")
                    )
            );

            var expression = "((#hello BETWEEN :hello_between_start AND :hello_between_end) AND (#hello IN (:hello_in_0, :hello_in_1, :hello_in_2)))";
            var expressionNames = Map.of(
                    "#hello", "hello"
            );
            var expressionValues = Map.of(
                    ":hello_in_0", Map.of(
                            "S", "hello"
                    ),
                    ":hello_in_1", Map.of(
                            "S", "hi"
                    ),
                    ":hello_in_2", Map.of(
                            "S", "hi but in french"
                    ),
                    ":hello_between_start", Map.of(
                            "S", "ha"
                    ),
                    ":hello_between_end", Map.of(
                            "S", "hz"
                    )
            );

            var actual = transform.toDynamoDBFilterExpression(filter);
            var useful = new Gson().fromJson(actual, Map.class);

            assertEquals(expression, useful.get("expression"));
            assertEquals(expressionValues, useful.get("expressionValues"));
            assertEquals(expressionNames, useful.get("expressionNames"));
        }
    }
}
