package com.lanyuanxiaoyao.service.sync;

import com.lanyuanxiaoyao.service.sync.functions.type.TypeConverterV2;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import org.apache.flink.table.data.DecimalData;
import org.apache.flink.table.data.StringData;
import org.apache.hudi.org.apache.avro.Schema;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import static com.lanyuanxiaoyao.service.sync.functions.type.TypeConverterV2.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

/**
 * @author ZhangJiacheng
 * @date 2023-07-19
 */
public class TypeConvertTest {
    @Test
    public void testConvertType() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        Executor convertType = new Executor("convertType", String.class, String.class, String.class, Long.class, Integer.class);

        // boolean
        assertEquals(NULLABLE_BOOLEAN_SCHEMA, convertType.exec("test", "test", "boolean", 0L, null));
        assertEquals(NULLABLE_BOOLEAN_SCHEMA, convertType.exec("test", "test", "Boolean", 0L, null));
        assertEquals(NULLABLE_BOOLEAN_SCHEMA, convertType.exec("test", "test", "BOOLEAN", 0L, null));
        assertEquals(NULLABLE_BOOLEAN_SCHEMA, convertType.exec("test", "test", "bool", 0L, null));

        // int
        assertEquals(NULLABLE_INT_SCHEMA, convertType.exec("test", "test", "tinyint(1)", 0L, null));
        assertEquals(NULLABLE_INT_SCHEMA, convertType.exec("test", "test", "tinyint", 1L, null));
        assertEquals(NULLABLE_INT_SCHEMA, convertType.exec("test", "test", "tinyint", 0L, null));
        assertEquals(NULLABLE_INT_SCHEMA, convertType.exec("test", "test", "int", 0L, null));
        assertEquals(NULLABLE_INT_SCHEMA, convertType.exec("test", "test", "integer", 0L, null));
        assertEquals(NULLABLE_INT_SCHEMA, convertType.exec("test", "test", "smallint", 0L, null));
        assertEquals(NULLABLE_INT_SCHEMA, convertType.exec("test", "test", "smallserial", 0L, null));
        assertEquals(NULLABLE_INT_SCHEMA, convertType.exec("test", "test", "SMALLSERIAL", 0L, null));

        // long
        assertEquals(NULLABLE_LONG_SCHEMA, convertType.exec("test", "test", "int unsigned", 0L, null));
        assertEquals(NULLABLE_LONG_SCHEMA, convertType.exec("test", "test", "bigint", 0L, null));
        assertEquals(NULLABLE_LONG_SCHEMA, convertType.exec("test", "test", "bigint unsigned", 0L, null));
        assertEquals(NULLABLE_LONG_SCHEMA, convertType.exec("test", "test", "serial", 0L, null));
        assertEquals(NULLABLE_LONG_SCHEMA, convertType.exec("test", "test", "long", 0L, null));
        assertEquals(NULLABLE_LONG_SCHEMA, convertType.exec("test", "test", "Long", 0L, null));

        // float
        assertEquals(NULLABLE_FLOAT_SCHEMA, convertType.exec("test", "test", "float", 0L, null));
        assertEquals(NULLABLE_FLOAT_SCHEMA, convertType.exec("test", "test", "FLOAT", 0L, null));

        // double
        assertEquals(NULLABLE_DOUBLE_SCHEMA, convertType.exec("test", "test", "double", 0L, null));
        assertEquals(NULLABLE_DOUBLE_SCHEMA, convertType.exec("test", "test", "Double", 0L, null));
        assertEquals(NULLABLE_DOUBLE_SCHEMA, convertType.exec("test", "test", "double precision", 6L, null));

        // date
        assertEquals(NULLABLE_STRING_SCHEMA, convertType.exec("test", "test", "date", 0L, null));
        assertEquals(NULLABLE_STRING_SCHEMA, convertType.exec("test", "test", "datetime", 0L, null));
        assertEquals(NULLABLE_STRING_SCHEMA, convertType.exec("test", "test", "timestamp", 0L, null));
        assertEquals(NULLABLE_STRING_SCHEMA, convertType.exec("test", "test", "timestamp without time zone", 0L, null));
        assertEquals(NULLABLE_STRING_SCHEMA, convertType.exec("test", "test", "time", 0L, null));

        // string
        assertEquals(NULLABLE_STRING_SCHEMA, convertType.exec("test", "test", "varchar", 0L, null));
        assertEquals(NULLABLE_STRING_SCHEMA, convertType.exec("test", "test", "char", 0L, null));
        assertEquals(NULLABLE_STRING_SCHEMA, convertType.exec("test", "test", "text", 0L, null));
        assertEquals(NULLABLE_STRING_SCHEMA, convertType.exec("test", "test", "longtext", 0L, null));
        assertEquals(NULLABLE_STRING_SCHEMA, convertType.exec("test", "test", "mediumtext", 0L, null));
        assertEquals(NULLABLE_STRING_SCHEMA, convertType.exec("test", "test", "clob", 0L, null));
        assertEquals(NULLABLE_STRING_SCHEMA, convertType.exec("test", "test", "binary", 0L, null));
        assertEquals(NULLABLE_STRING_SCHEMA, convertType.exec("test", "test", "bit", 0L, null));
        assertEquals(NULLABLE_STRING_SCHEMA, convertType.exec("test", "test", "character varying", 0L, null));

        // decimal
        assertEquals(NULLABLE_DECIMAL_SCHEMA("test", 2, 0), convertType.exec("test", "test", "number", 2L, null));
        assertEquals(NULLABLE_DECIMAL_SCHEMA("test", 4, 0), convertType.exec("test", "test", "money", 4L, null));
        assertEquals(NULLABLE_DECIMAL_SCHEMA("test", 6, 0), convertType.exec("test", "test", "bigserial", 6L, null));
        assertEquals(NULLABLE_DECIMAL_SCHEMA("test", 6, 3), convertType.exec("test", "test", "decimal(6,3)", 6L, 3));
        assertEquals(NULLABLE_DECIMAL_SCHEMA("test", 6, 6), convertType.exec("test", "test", "decimal(6)", 6L, null));
        assertEquals(NULLABLE_DECIMAL_SCHEMA("test", 6, 6), convertType.exec("test", "test", "decimal", 6L, null));
        assertEquals(NULLABLE_DECIMAL_SCHEMA("test", 2, 1), convertType.exec("test", "test", "numeric(2,1)", 2L, 1));
        assertEquals(NULLABLE_DECIMAL_SCHEMA("test", 2, 1), convertType.exec("test", "test", "numeric", 2L, 1));
    }

    @Test
    public void testConvertValue() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Executor convertValue = new Executor("covertValue", Schema.class, String.class, Object.class);

        // null
        assertNull(convertValue.exec(NULLABLE_BOOLEAN_SCHEMA, "test", null));

        // boolean
        assertEquals(true, convertValue.exec(NULLABLE_BOOLEAN_SCHEMA, "test", "true"));
        assertEquals(false, convertValue.exec(NULLABLE_BOOLEAN_SCHEMA, "test", "false"));
        assertEquals(false, convertValue.exec(NULLABLE_BOOLEAN_SCHEMA, "test", "1"));
        assertEquals(false, convertValue.exec(NULLABLE_BOOLEAN_SCHEMA, "test", "hello"));

        // int
        assertEquals(1, convertValue.exec(NULLABLE_INT_SCHEMA, "test", "1"));
        assertEquals(10, convertValue.exec(NULLABLE_INT_SCHEMA, "test", "10"));
        Assertions.assertThrows(Exception.class, () -> convertValue.exec(NULLABLE_INT_SCHEMA, "test", "10.0"));

        // long
        assertEquals(1L, convertValue.exec(NULLABLE_LONG_SCHEMA, "test", "1"));
        assertEquals(10L, convertValue.exec(NULLABLE_LONG_SCHEMA, "test", "10"));
        Assertions.assertThrows(Exception.class, () -> convertValue.exec(NULLABLE_LONG_SCHEMA, "test", "10.0"));

        // float
        assertEquals(1.0f, convertValue.exec(NULLABLE_FLOAT_SCHEMA, "test", "1.0"));
        assertEquals(10.0f, convertValue.exec(NULLABLE_FLOAT_SCHEMA, "test", "10.0"));
        assertEquals(100f, convertValue.exec(NULLABLE_FLOAT_SCHEMA, "test", "100"));

        // double
        assertEquals(1.0, convertValue.exec(NULLABLE_DOUBLE_SCHEMA, "test", "1.0"));
        assertEquals(100d, convertValue.exec(NULLABLE_DOUBLE_SCHEMA, "test", "100"));

        // string
        assertEquals(StringData.fromString("2020-10-10 01:01:01"), convertValue.exec(NULLABLE_STRING_SCHEMA, "test", "2020-10-10 01:01:01"));
        assertEquals(StringData.fromString("123"), convertValue.exec(NULLABLE_STRING_SCHEMA, "test", "123"));
        assertEquals(StringData.fromString("123.456"), convertValue.exec(NULLABLE_STRING_SCHEMA, "test", "123.456"));

        // decimal
        assertEquals(DecimalData.fromBigDecimal(new BigDecimal("123.0"), 10, 0), convertValue.exec(NULLABLE_DECIMAL_SCHEMA("test", 10, 0), "test", "123.0"));
        assertEquals(DecimalData.fromBigDecimal(new BigDecimal("123456789987654321.123456789987654321"), 37, 18), convertValue.exec(NULLABLE_DECIMAL_SCHEMA("test", 37, 18), "test", "123456789987654321.123456789987654321"));
    }

    private static final class Executor {
        private final TypeConverterV2 converter = new TypeConverterV2();
        private final Method method;

        Executor(String method, Class<?>... params) throws NoSuchMethodException {
            this.method = converter.getClass().getDeclaredMethod(method, params);
            this.method.setAccessible(true);
        }

        public Object exec(Object... params) throws InvocationTargetException, IllegalAccessException {
            return method.invoke(this.converter, params);
        }
    }
}
