package ru.yandex.webmaster3.storage.util.ydb.querybuilder;

import java.util.Arrays;
import java.util.Collection;
import java.util.function.Function;

import com.yandex.ydb.table.result.ResultSetReader;
import com.yandex.ydb.table.values.Type;

import ru.yandex.webmaster3.storage.util.ydb.query.Assignment;
import ru.yandex.webmaster3.storage.util.ydb.query.Clause;
import ru.yandex.webmaster3.storage.util.ydb.query.DbFieldInsertAssignment;
import ru.yandex.webmaster3.storage.util.ydb.querybuilder.typesafe.AnyField;
import ru.yandex.webmaster3.storage.util.ydb.querybuilder.typesafe.Field;


/**
 * ishalaru
 * 05.06.2020
 **/
public class QueryBuilder {
    private QueryBuilder() {
    }


    /**
     * Simple "value" assignment of a value to a column.
     *
     * @param name  the column name
     * @param value the value to assign
     * @return the correspond assignment (to use in an insert query)
     */
    public static DbFieldInsertAssignment value(String name, Object value) {
        return new DbFieldInsertAssignment(name, value);
    }

    public static DbFieldInsertAssignment value(String name, Type type) {
        return new DbFieldInsertAssignment(name, type);
    }

    /**
     * Simple "set" assignment of a value to a column.
     *
     * <p>This will generate:
     *
     * <pre>
     * name = value
     * </pre>
     *
     * @param name  the column name
     * @param value the value to assign
     * @return the correspond assignment (to use in an update query)
     */
    public static Assignment set(String name, Object value) {
        return new Assignment(name, value);
    }

    /**
     * Creates an "equal" {@code WHERE} clause stating the provided column must be equal to the
     * provided value.
     *
     * @param name  the column name
     * @param value the value
     * @return the corresponding where clause.
     */
    public static Clause eq(String name, Object value) {
        return new Clause.SimpleClause(name, "=", value);
    }


    /**
     * Creates a "true" {@code WHERE} clause
     *
     * @return the corresponding where clause.
     */
    public static Clause addTrue() {
        return new Clause.TrueClause();
    }

    /**
     * Creates a "false" {@code WHERE} clause
     *
     * @return the corresponding where clause.
     */
    public static Clause addFalse() {
        return new Clause.FalseClause();
    }

    /**
     * Creates a "not equal" {@code WHERE} clause stating the provided column must be different from
     * the provided value.
     *
     * @param name  the column name
     * @param value the value
     * @return the corresponding where clause.
     */
    public static Clause ne(String name, Object value) {
        return new Clause.SimpleClause(name, "!=", value);
    }

    /**
     * Creates an "IS NULL" {@code WHERE} clause for the provided column.
     *
     * @param name the column name
     * @return the corresponding where clause.
     */
    public static Clause isNull(String name) {
        return new Clause.IsNullClause(name);
    }

    /**
     * Creates an "IS NOT NULL" {@code WHERE} clause for the provided column.
     *
     * @param name the column name
     * @return the corresponding where clause.
     */
    public static Clause notNull(String name) {
        return new Clause.IsNotNullClause(name);
    }

    /**
     * Creates a "like" {@code WHERE} clause stating that the provided column must be equal to the
     * provided value.
     *
     * @param name  the column name.
     * @param value the value.
     * @return the corresponding where clause.
     */
    public static Clause like(String name, Object value) {
        return new Clause.SimpleClause(name, " LIKE ", "%" + value + "%");
    }

    /**
     * Create an "in" {@code WHERE} clause stating the provided column must be equal to one of the
     * provided values.
     *
     * @param name   the column name
     * @param values the values
     * @return the corresponding where clause.
     */
    public static Clause in(String name, Object... values) {
        return new Clause.InClause(name, Arrays.asList(values));
    }

    /**
     * Create an "in" {@code WHERE} clause stating the provided column must be equal to one of the
     * provided values.
     *
     * @param name   the column name
     * @param values the values
     * @return the corresponding where clause.
     */
    public static Clause in(String name, Collection<?> values) {
        return new Clause.InClause(name, values);
    }


    /**
     * Creates a "lesser than" {@code WHERE} clause stating the provided column must be less than the
     * provided value.
     *
     * @param name  the column name
     * @param value the value
     * @return the corresponding where clause.
     */
    public static Clause lt(String name, Object value) {
        return new Clause.SimpleClause(name, "<", value);
    }

    /**
     * Creates a "lesser than or equal" {@code WHERE} clause stating the provided column must be
     * lesser than or equal to the provided value.
     *
     * @param name  the column name
     * @param value the value
     * @return the corresponding where clause.
     */
    public static Clause lte(String name, Object value) {
        return new Clause.SimpleClause(name, "<=", value);
    }


    /**
     * Creates a "greater than" {@code WHERE} clause stating the provided column must be greater to
     * the provided value.
     *
     * @param name  the column name
     * @param value the value
     * @return the corresponding where clause.
     */
    public static Clause gt(String name, Object value) {
        return new Clause.SimpleClause(name, ">", value);
    }

    /**
     * Creates a "greater than or equal" {@code WHERE} clause stating the provided column must be
     * greater than or equal to the provided value.
     *
     * @param name  the column name
     * @param value the value
     * @return the corresponding where clause.
     */
    public static Clause gte(String name, Object value) {
        return new Clause.SimpleClause(name, ">=", value);
    }

    public static Clause or(Clause a, Clause b) {
        return new Clause.BinaryClause(a, b, "OR");
    }

    public static Clause or(Collection<Clause> clauses) {
        return new Clause.MultiClause(clauses, "OR");
    }

    public static Clause and(Clause a, Clause b) {
        return new Clause.BinaryClause(a, b, "AND");
    }

    public static Clause and(Collection<Clause> clauses) {
        return new Clause.MultiClause(clauses, "AND");
    }

    /**
     * Ascending ordering for the provided column.
     *
     * @param columnName the column name
     * @return the corresponding ordering
     */
    public static Ordering asc(String columnName) {
        return new Ordering(columnName, false);
    }

    /**
     * Descending ordering for the provided column.
     *
     * @param columnName the column name
     * @return the corresponding ordering
     */
    public static Ordering desc(String columnName) {
        return new Ordering(columnName, true);
    }


    /**
     * Creates a function call.
     *
     * @param name       the name of the function to call.
     * @param parameters the parameters for the function.
     * @return the function call.
     */
    public static Object fcall(String alias, String name, Object... parameters) {
        return new Utils.FCall(alias, name, parameters);
    }


    public static <T, R> AnyField<R> MCall(String alias, Field<T> field, String operation, Function<ResultSetReader, R> mapper) {
        return new Utils.FMapCall<>(alias, operation, field, mapper);
    }

    public static AnyField<String> toLower(Field<String> field) {
        return new Utils.FMapCall<>(null, "String::ToLower", field, e -> e.getColumn(field.getName()).getUtf8());
    }


    /**
     * Creates a Cast of a column using the given dataType.
     *
     * @param column   the column to cast.
     * @param dataType the data type to cast to.
     * @return the casted column.
     */
    public static Object cast(Object column, Type dataType) {
        return new Utils.Cast(column, dataType);
    }


    /**
     * Declares that the name in argument should be treated as a column name.
     *
     * @param name the name of the column.
     * @return the name as a column name.
     */
    public static Object column(String name) {
        return new Utils.CName(name);
    }

    /**
     * Creates a {@code count(x)} built-in function call.
     *
     * @return the function call.
     */
    public static <T> AnyField<Long> count(String alias, Field<T> column) {

        return new Utils.FMapCall<>(alias, "count", column, e -> e.getColumn(alias).getInt64());
    }

    /**
     * Creates a {@code max(x)} built-in function call.
     *
     * @return the function call.
     */
    public static Object max(String alias, Object column) {
        // consider a String literal as a column name for user convenience,
        // as CQL literals are not allowed here.
        if (column instanceof String) {
            column = column(((String) column));
        }
        return new Utils.FCall(alias, "max", column);
    }

    /**
     * Creates a {@code min(x)} built-in function call.
     *
     * @return the function call.
     */
    public static Object min(String alias, Object column) {
        // consider a String literal as a column name for user convenience,
        // as CQL literals are not allowed here.
        if (column instanceof String) {
            column = column(((String) column));
        }
        return new Utils.FCall(alias, "min", column);
    }

    /**
     * Creates a {@code sum(x)} built-in function call.
     *
     * @return the function call.
     */
    public static Object sum(String alias, Object column) {
        // consider a String literal as a column name for user convenience,
        // as CQL literals are not allowed here.
        if (column instanceof String) {
            column = column(((String) column));
        }
        return new Utils.FCall(alias, "sum", column);
    }

    /**
     * Creates an {@code avg(x)} built-in function call.
     *
     * @return the function call.
     */
    public static Object avg(String alias, Object column) {
        // consider a String literal as a column name for user convenience,
        // as CQL literals are not allowed here.
        if (column instanceof String) {
            column = column(((String) column));
        }
        return new Utils.FCall(alias, "avg", column);
    }
}
