package com.lanyuanxiaoyao.service.sync.functions;

import cn.hutool.core.collection.ListUtil;
import com.lanyuanxiaoyao.service.common.Constants;
import com.lanyuanxiaoyao.service.common.entity.FlinkJob;
import com.lanyuanxiaoyao.service.common.entity.Record;
import com.lanyuanxiaoyao.service.common.entity.TableMeta;
import com.lanyuanxiaoyao.service.common.utils.LogHelper;
import com.lanyuanxiaoyao.service.common.utils.MapHelper;
import com.lanyuanxiaoyao.service.common.utils.RecordHelper;
import com.lanyuanxiaoyao.service.common.utils.TableMetaHelper;
import com.lanyuanxiaoyao.service.sync.configuration.GlobalConfiguration;
import com.lanyuanxiaoyao.service.sync.functions.type.TypeConverter;
import com.lanyuanxiaoyao.service.sync.metrics.CountMetric;
import com.lanyuanxiaoyao.service.sync.utils.JacksonUtils;
import com.lanyuanxiaoyao.service.sync.utils.MetricsUtils;
import com.lanyuanxiaoyao.service.sync.utils.StatusUtils;
import com.lanyuanxiaoyao.service.sync.utils.SyncUtils;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.runtime.state.FunctionInitializationContext;
import org.apache.flink.runtime.state.FunctionSnapshotContext;
import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.flink.streaming.api.checkpoint.CheckpointedFunction;
import org.apache.flink.table.data.GenericRowData;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.data.StringData;
import org.apache.hudi.org.apache.avro.Schema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.lanyuanxiaoyao.service.common.utils.LogHelper.LogPoint.VERSION_UPDATE;

/**
 * Record 转 Rowdata
 *
 * @author ZhangJiacheng
 * @date 2022-06-13
 */
public class Record2RowDataFunction extends RichMapFunction<Record, List<RowData>> implements CheckpointedFunction {
    private static final Logger logger = LoggerFactory.getLogger(Record2RowDataFunction.class);

    private final GlobalConfiguration globalConfiguration;
    private final TableMeta tableMeta;
    private final CountMetric changeFilterMetric;
    private final CountMetric changePartitionMetric;
    private final ObjectMapper mapper = JacksonUtils.getMapper();
    private final FlinkJob flinkJob;
    private Schema schema;

    public Record2RowDataFunction(GlobalConfiguration globalConfiguration, FlinkJob flinkJob, TableMeta tableMeta) {
        this.globalConfiguration = globalConfiguration;
        this.flinkJob = flinkJob;
        this.tableMeta = tableMeta;

        changeFilterMetric = new CountMetric(
                globalConfiguration,
                Constants.METRICS_SYNC_SOURCE_CHANGE_FILTER,
                flinkJob, tableMeta
        );
        changePartitionMetric = new CountMetric(
                globalConfiguration,
                Constants.METRICS_SYNC_SOURCE_CHANGE_PARTITION,
                flinkJob, tableMeta
        );
    }

    @Override
    public void initializeState(FunctionInitializationContext context) throws Exception {
        schema = SyncUtils.avroSchemaWithExtraFields(tableMeta);
    }

    @Override
    public void open(Configuration parameters) throws Exception {
        super.open(parameters);

        // 初始化指标
        MetricsUtils.createMakePointTimer(globalConfiguration, changeFilterMetric);
        MetricsUtils.createMakePointTimer(globalConfiguration, changePartitionMetric);
    }

    @Override
    public void snapshotState(FunctionSnapshotContext context) throws Exception {
        MetricsUtils.publishAllMetrics(changeFilterMetric);
        MetricsUtils.publishAllMetrics(changePartitionMetric);
    }

    private GenericRowData covert2RowData(Schema schema, Map<String, Object> current) {
        List<Schema.Field> fields = schema.getFields();
        GenericRowData data = new GenericRowData(fields.size());
        for (int index = 0; index < fields.size(); index++) {
            Schema.Field field = fields.get(index);
            // 如果是telepg的话，字段名就要统一改成小写，上游不规范，下游擦屁股
            Object value = current.getOrDefault(Constants.FIELD_COVERT.apply(tableMeta, field.name()), null);
            if (field.schema().getType().equals(Schema.Type.STRING)
                || (field.schema().isUnion() && field.schema().getTypes().contains(Schema.create(Schema.Type.STRING)))
                || value instanceof String) {
                data.setField(index, StringData.fromString((String) value));
            } else {
                data.setField(index, value);
            }
        }
        return data;
    }

    private Boolean isFilterOut(TableMeta tableMeta, Map<String, Object> current) {
        if (!tableMeta.getFilterType().equals(TableMeta.FilterType.NONE)) {
            if (current.containsKey(tableMeta.getFilterField())) {
                String fieldValue = MapHelper.getStringWithoutCase(current, tableMeta.getFilterField());
                if (tableMeta.getFilterType().equals(TableMeta.FilterType.EXCLUDE)) {
                    return tableMeta.getFilterValues().contains(fieldValue);
                } else if (tableMeta.getFilterType().equals(TableMeta.FilterType.INCLUDE)) {
                    return !tableMeta.getFilterValues().contains(fieldValue);
                }
            }
        }
        return false;
    }

    @Override
    public List<RowData> map(Record record) throws Exception {
        List<Map<String, Object>> result = ListUtil.list(false);

        if (RecordHelper.isVersionUpdateRecord(record)) {
            Record.Statement statement = record.getStatement();
            LogHelper.info(logger, VERSION_UPDATE, "{} {} version: {}", mapper.writeValueAsString(statement.getSchema()), statement.getVersion(), statement.getVersion());
            LogHelper.info(logger, VERSION_UPDATE, "Raw: {}", mapper.writeValueAsString(record));
            StatusUtils.versionUpdate(globalConfiguration, flinkJob, tableMeta, record.getStatement().getVersion(), statement.getOpTs());
            return ListUtil.empty();
        }

        Map<String, Object> current = RecordHelper.getCurrentStatement(record);
        if (Objects.isNull(current)) {
            logger.error("Record: {}", mapper.writeValueAsString(record));
            throw new RuntimeException("Current cannot be null");
        }

        // 如果 update 改变了过滤字段的值也需要先删除
        boolean isChangeFilter = RecordHelper.isChangeField(tableMeta, record, TableMetaHelper::getFilterField);
        if (isChangeFilter) {
            logger.info("Change filter: {}", mapper.writeValueAsString(record));
            changeFilterMetric.increment();
        }

        // 如果是 update 且 city_id 不相等就先删除旧记录
        boolean isChangePartition = RecordHelper.isChangeField(tableMeta, record, TableMetaHelper::getPartitionField);
        if (isChangePartition) {
            logger.info("Change partition field: {}", mapper.writeValueAsString(record));
            changePartitionMetric.increment();
        }

        if (isChangeFilter || isChangePartition) {
            Map<String, Object> before = record.getStatement().getBefore();
            result.add(0, RecordHelper.addExtraMetadata(before, tableMeta, record, true));
        }

        // 增加 Hudi 特有字段信息
        result.add(RecordHelper.addExtraMetadata(current, tableMeta, record));

        return result.stream()
                // 按过滤字段过滤
                .filter(r -> !isFilterOut(tableMeta, r))
                .map(r -> TypeConverter.getInstance(tableMeta)
                        .convertToGenericRowData(tableMeta, schema, r))
                .collect(Collectors.toList());
    }
}
