package io.wealthwizards.appsynctesting.utils;

import com.google.common.collect.Streams;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import io.wealthwizards.appsynctesting.utils.dynamodb.FieldNamingStrategy;
import lombok.Getter;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public final class Rds {
    private final Gson gson;

    public Rds() {
        gson = new GsonBuilder()
                .serializeNulls()
                .setFieldNamingStrategy(new FieldNamingStrategy())
                .create();
    }

    @Getter
    private static final class RdsColumnMetadata {
        private boolean isSigned;
        private boolean isCurrency;
        private String label;
        private int precision;
        private String typeName;
        private int scale;
        private boolean isAutoIncrement;
        private boolean isCaseSensitive;
        private String schemaName;
        private String tableName;
        private int type;
        private int nullable;
        private int arrayBaseColumnType;
        private String name;
    }

    @Getter
    private static final class RdsStatementRecordField {
        private List<RdsStatementRecordField> arrayValue;
        private String blobValue;
        private Boolean booleanValue;
        private Double doubleValue;
        private Boolean isNull;
        private Long longValue;
        private String stringValue;
    }

    @Getter
    private static final class RdsStatementResult {
        private int numberOfRecordsUpdated;
        private List<List<RdsStatementRecordField>> records;
        private List<RdsColumnMetadata> columnMetadata;
    }

    @Getter
    private static final class RdsApiResults {
        private List<RdsStatementResult> sqlStatementResults;
    }

    private static Object getFieldValue(RdsStatementRecordField field) {
        if (field.getArrayValue() != null) {
            return field.getArrayValue()
                    .stream()
                    .map(Rds::getFieldValue)
                    .collect(Collectors.toList());
        }

        if (field.getBlobValue() != null) {
            return field.getBlobValue();
        }

        if (field.getBooleanValue() != null) {
            return field.getBooleanValue();
        }

        if (field.getDoubleValue() != null) {
            return field.getDoubleValue();
        }

        if (field.getLongValue() != null) {
            return field.getLongValue();
        }

        if (field.getStringValue() != null) {
            return field.getStringValue();
        }

        if (field.getIsNull() != null && field.getIsNull().equals(false)) {
            throw new RuntimeException("Unknown value for RDS record result field");
        }

        return null;
    }

    private List<Map<String, ?>> convertStatementResult(RdsStatementResult result) {
        var columns = result.getColumnMetadata()
                .stream()
                .map(column -> column.name)
                .collect(Collectors.toList());

        var records = result.getRecords();

        return records.stream()
                .map(
                        fields -> Streams.zip(columns.stream(), fields.stream(), Tuple::of)
                                .map(tuple -> tuple.map2(Rds::getFieldValue))
                                .collect(Collectors.toMap(Tuple2::_1, Tuple2::_2))
                )
                .map(HashMap::new)
                .collect(Collectors.toList());
    }

    public List<List<Map<String, ?>>> toJsonObject(String json) {
        return gson.fromJson(json, RdsApiResults.class)
                .getSqlStatementResults()
                .stream()
                .map(this::convertStatementResult)
                .collect(Collectors.toList());
    }

    public String toJsonString(String json) {
        return gson.toJson(toJsonObject(json));
    }
}
