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

import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

import com.yandex.ydb.table.values.ListType;
import com.yandex.ydb.table.values.PrimitiveValue;
import com.yandex.ydb.table.values.TupleType;
import com.yandex.ydb.table.values.Type;
import com.yandex.ydb.table.values.Value;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.ReadableInstant;
import org.springframework.util.unit.DataSize;

import ru.yandex.webmaster3.core.util.enums.IntEnum;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.storage.util.yt.YtPath;

/**
 * ishalaru
 * 11.06.2020
 **/
public class Utils {
    public abstract static class Appendeable {
        //abstract void appendTo(StringBuilder sb, List<Object> values);
        public abstract void appendTo(StringBuilder sb);
    }

    static StringBuilder appendName(String name, StringBuilder sb) {
        name = name.trim();
        // TODO очень грубый костыль для работы tuple-ов
        if (name.contains(",")) {
            sb.append(name);
        } else {
            sb.append("`").append(name).append("`");
        }
        return sb;
    }

    static StringBuilder appendValue(int parameterIndex, StringBuilder sb) {
        return appendParameterName(parameterIndex, sb);
    }

    static StringBuilder appendParameterName(int parameterIndex, StringBuilder sb) {
        return sb.append("$parameter_").append(parameterIndex);
    }

    static String generateParameterName(int parameterIndex) {
        return "$parameter_" + parameterIndex;
    }

    public static Pair<Type, Value> defineType(Object arg) {
        if (arg instanceof Collection<?>) {
            return valueTupleForList((Collection<?>) arg);
        } else if (arg instanceof PrimitiveValue) {
            return primitiveValueTuple((PrimitiveValue) arg);
        } else if (arg instanceof String) {
            return primitiveValueTuple(PrimitiveValue.utf8(((String) arg)));
        } else if (arg instanceof Integer) {
            return primitiveValueTuple(PrimitiveValue.int32((Integer) arg));
        } else if (arg instanceof Long) {
            return primitiveValueTuple(PrimitiveValue.int64((Long) arg));
        } else if (arg instanceof Float) {
            return primitiveValueTuple(PrimitiveValue.float32((Float) arg));
        } else if (arg instanceof Double) {
            return primitiveValueTuple(PrimitiveValue.float64((Double) arg));
        } else if (arg instanceof org.joda.time.LocalDate) {
            return primitiveValueTuple(PrimitiveValue.date(convert((org.joda.time.LocalDate) arg)));
        } else if (arg instanceof LocalDate) {
            return primitiveValueTuple(PrimitiveValue.date((LocalDate) arg));
        } else if (arg instanceof IntEnum) {
            IntEnum intEnum = (IntEnum) arg;
            return primitiveValueTuple(PrimitiveValue.int32(intEnum.value()));
        } else if (arg instanceof Enum) {
            Enum plainEnum = (Enum) arg;
            return primitiveValueTuple(PrimitiveValue.string(plainEnum.name().getBytes()));
        } else if (arg instanceof Boolean) {
            return primitiveValueTuple(PrimitiveValue.bool((Boolean) arg));
        } else if (arg instanceof ReadableInstant) {
            ReadableInstant dateTime = (ReadableInstant) arg;
            return primitiveValueTuple(PrimitiveValue.timestamp(Instant.ofEpochMilli(dateTime.getMillis())));
        } else if (arg instanceof java.time.Instant) {
            Timestamp timestamp = Timestamp.from((java.time.Instant) arg);
            return primitiveValueTuple(PrimitiveValue.timestamp(timestamp.toInstant()));
        } else if (arg instanceof byte[]) {
            byte[] bytes = (byte[]) arg;
            return primitiveValueTuple(PrimitiveValue.string(bytes));
        } else if (arg instanceof DataSize) {
            return primitiveValueTuple(PrimitiveValue.int64(((DataSize) arg).toBytes()));
        } else if (arg instanceof UUID) {
            return primitiveValueTuple(PrimitiveValue.utf8((arg.toString())));
        } else if (arg instanceof WebmasterHostId) {
            return primitiveValueTuple(PrimitiveValue.utf8((arg.toString())));
        } else if (arg instanceof YtPath) {
            return primitiveValueTuple(PrimitiveValue.utf8((arg.toString())));
        } else if (arg instanceof Pair) {
            return valueTupleForPair((Pair<?, ?>) arg);
        } else {
            throw new IllegalStateException("Not supported " + arg);
        }
    }

    private static Pair<Type, Value> valueTupleForList(Collection<?> arg) {
        List<Pair<Type, Value>> items = arg.stream().map(Utils::defineType).collect(Collectors.toList());
        if (items.isEmpty()) {
            throw new IllegalStateException("Empty collections not supported");
        }
        ListType listType = ListType.of(items.get(0).getKey());
        return ImmutablePair.of(
                listType,
                listType.newValue(items.stream().map(Pair::getValue).collect(Collectors.toList()))
        );
    }

    private static Pair<Type, Value> valueTupleForPair(Pair<?, ?> arg) {
        var left = Utils.defineType(arg.getLeft());
        var right = Utils.defineType(arg.getRight());
        TupleType tupleType = TupleType.of(Arrays.asList(left.getLeft(), right.getLeft()));
        return ImmutablePair.of(
                tupleType,
                tupleType.newValue(left.getRight(), right.getRight())
        );
    }

    private static Pair<Type, Value> primitiveValueTuple(PrimitiveValue value) {
        return ImmutablePair.of(value.getType(), value);
    }

    private static LocalDate convert(org.joda.time.LocalDate jDate) {
        return LocalDate.of(jDate.getYear(), jDate.getMonthOfYear(), jDate.getDayOfMonth());
    }



}
