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.utils.ExceptionUtils;
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.metrics.Counter;
import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.flink.table.data.RowData;
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>> {
    private static final Logger logger = LoggerFactory.getLogger(Record2RowDataFunction.class);

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

    private transient Counter changeFilterCounter;
    private transient Counter changePartitionCounter;

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

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

        // 初始化指标
        changeFilterCounter = MetricsUtils.counter(getRuntimeContext(), flinkJob, tableMeta, Constants.METRICS_SYNC_SOURCE_CHANGE_FILTER);
        changePartitionCounter = MetricsUtils.counter(getRuntimeContext(), flinkJob, tableMeta, Constants.METRICS_SYNC_SOURCE_CHANGE_PARTITION);

        schema = SyncUtils.avroSchemaWithExtraFields(tableMeta);
    }

    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));
            return ExceptionUtils.throwAndPrint(logger, "Current cannot be null");
        }

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

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

        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());
    }
}
