/*
 * Copyright (C) 2016-2022 ActionTech.
 * based on code by MyCATCopyrightHolder Copyright (c) 2013, OpenCloudDB/MyCAT.
 * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
 */

package com.actiontech.dble.cluster.logic;

import com.actiontech.dble.DbleServer;
import com.actiontech.dble.backend.datasource.PhysicalDbGroup;
import com.actiontech.dble.cluster.path.ClusterChildMetaUtil;
import com.actiontech.dble.cluster.path.ClusterMetaUtil;
import com.actiontech.dble.cluster.path.ClusterPathUtil;
import com.actiontech.dble.cluster.values.*;
import com.actiontech.dble.cluster.zkprocess.entity.DbGroups;
import com.actiontech.dble.cluster.zkprocess.entity.dbGroups.DBGroup;
import com.actiontech.dble.cluster.zkprocess.entity.dbGroups.DBInstance;
import com.actiontech.dble.config.model.SystemConfig;
import com.actiontech.dble.singleton.HaConfigManager;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

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

import static com.actiontech.dble.backend.datasource.PhysicalDbGroup.JSON_LIST;

/**
 * @author dcy
 * Create Date: 2021-04-30
 */
public class HAClusterLogic extends AbstractClusterLogic {
    private static final Logger LOGGER = LogManager.getLogger(HAClusterLogic.class);

    HAClusterLogic() {
        super(ClusterOperation.HA);
    }

    public void dbGroupChangeEvent(String dbGroupName, RawJson value) {
        int id = HaConfigManager.getInstance().haStart(HaInfo.HaStage.RESPONSE_NOTIFY, HaInfo.HaStartType.CLUSTER_NOTIFY, "");
        PhysicalDbGroup physicalDBPool = DbleServer.getInstance().getConfig().getDbGroups().get(dbGroupName);
        if (null != physicalDBPool) {
            physicalDBPool.changeIntoLatestStatus(value);
        }
        HaConfigManager.getInstance().haFinish(id, null, value);
    }


    public void dbGroupResponseEvent(HaInfo info, String dbGroupName) throws Exception {
        //dbGroup_locks events,we only try to response to the DISABLE,ignore others
        if (info.getLockType() == HaInfo.HaType.DISABLE &&
                !info.getStartId().equals(SystemConfig.getInstance().getInstanceName()) &&
                info.getStatus() == HaInfo.HaStatus.SUCCESS) {
            try {
                //start the log
                int id = HaConfigManager.getInstance().haStart(HaInfo.HaStage.RESPONSE_NOTIFY, HaInfo.HaStartType.CLUSTER_NOTIFY, HaInfo.HaStage.RESPONSE_NOTIFY.toString());
                //try to get the latest status of the dbGroup
                RawJson latestStatus = clusterHelper.getPathValue(ClusterMetaUtil.getHaStatusPath(info.getDbGroupName())).map(ClusterValue::getData).orElse(null);
                //find out the target dbGroup and change it into latest status
                PhysicalDbGroup dbGroup = DbleServer.getInstance().getConfig().getDbGroups().get(info.getDbGroupName());
                dbGroup.changeIntoLatestStatus(latestStatus);
                //response the event ,only disable event has response
                clusterHelper.createSelfTempNode(ClusterPathUtil.getHaResponsePath(dbGroupName), FeedBackType.SUCCESS);
                //ha manager writeOut finish log
                HaConfigManager.getInstance().haFinish(id, null, latestStatus);
            } catch (Exception e) {
                //response the event ,only disable event has response
                clusterHelper.createSelfTempNode(ClusterPathUtil.getHaResponsePath(dbGroupName), FeedBackType.ofError(e.getMessage()));
            }
        }
    }


    public void syncDbGroupStatusToCluster() throws Exception {
        LOGGER.info("syncDbGroupStatusToCluster start");
        HaConfigManager.getInstance().init(false);
        Map<String, RawJson> map = HaConfigManager.getInstance().getSourceJsonList();
        for (Map.Entry<String, RawJson> entry : map.entrySet()) {
            clusterHelper.setKV(ClusterMetaUtil.getHaStatusPath(entry.getKey()), entry.getValue());
        }
        LOGGER.info("syncDbGroupStatusToCluster success");
    }

    void syncHaStatusFromCluster(Gson gson, DbGroups dbs, List<DBGroup> dbGroupList) {
        try {
            List<ClusterEntry<RawJson>> statusKVList = this.getKVBeanOfChildPath(ClusterChildMetaUtil.getHaStatusPath());
            if (statusKVList.size() > 0) {
                Map<String, DBGroup> dbGroupMap = this.changeFromListToMap(dbGroupList);
                for (ClusterEntry<RawJson> kv : statusKVList) {
                    String[] path = kv.getKey().split(ClusterPathUtil.SEPARATOR);
                    String dbGroupName = path[path.length - 1];
                    DBGroup dbGroup = dbGroupMap.get(dbGroupName);
                    changStatusByJson(gson, dbs, dbGroupList, dbGroupName, dbGroup, kv.getValue().getData());
                }
            }
        } catch (Exception e) {
            LOGGER.warn("syncHaStatusFromCluster error :", e);
        }
    }

    public Map<String, DBGroup> changeFromListToMap(List<DBGroup> dbGroupList) {
        Map<String, DBGroup> dbGroupMap = new HashMap<>(dbGroupList.size());
        for (DBGroup dbGroup : dbGroupList) {
            dbGroupMap.put(dbGroup.getName(), dbGroup);
        }
        return dbGroupMap;
    }


    private static void changeDbGroupByStatus(DBGroup dbGroup, List<DbInstanceStatus> statusList) {
        Map<String, DbInstanceStatus> statusMap = new HashMap<>(statusList.size());
        for (DbInstanceStatus status : statusList) {
            statusMap.put(status.getName(), status);
        }
        for (DBInstance instance : dbGroup.getDbInstance()) {
            DbInstanceStatus status = statusMap.get(instance.getName());
            if (null != status) {
                instance.setPrimary(status.isPrimary());
                instance.setDisabled(status.isDisable() ? "true" : "false");
            }
        }
    }

    private static void changStatusByJson(Gson gson, DbGroups dbs, List<DBGroup> dbGroupList, String dbGroupName, DBGroup dbGroup, RawJson data) {
        if (dbGroup != null) {
            JsonObject jsonStatusObject = data.getJsonObject();
            JsonElement instanceJson = jsonStatusObject.get(JSON_LIST);
            if (instanceJson != null) {
                List<DbInstanceStatus> list = gson.fromJson(instanceJson.toString(),
                        new TypeToken<List<DbInstanceStatus>>() {
                        }.getType());
                dbs.setDbGroup(dbGroupList);
                changeDbGroupByStatus(dbGroup, list);
            }
        } else {
            LOGGER.warn("dbGroup " + dbGroupName + " is not found");
        }
    }


}
