# coding:utf-8
import traceback
from cbb.frame.context import contextUtil
from cbb.frame.base import baseUtil
from cbb.frame.base.constants import CheckResult
from cbb.frame.adapter.resourceService import ResrouceFactory
from service.resourceService import ResourceXmlUtil


def execute(context):
    context["ALL_RESOURCES"] = ResourceXmlUtil.parse()
    data = dict(context=context, resrouceFactory=ResrouceFactory(context))

    ret = FrontlinkCheck(data).execute()

    return ret.status, ret.errMsg, "\n".join(ret.cliRet)


class ShareData:
    def __init__(self, data):
        self.context = data["context"]
        self.resrouceFactory = data["resrouceFactory"]
        self.logger = contextUtil.getLogger(self.context)
        self.lang = contextUtil.getLang(self.context)
        self.originInfos = []

    def addOriginInfo(self, key, value):
        self.originInfos.append("uri=%s\n%s\n" % (key, value))


class LinkStatus:
    NO_INITIATOR = "noInitiator"  # 主机没有添加启动器
    INITIATOR_ALL_OFFLINE = "allOffline"
    INITIATOR_SOME_OFFLINE = "someoffline"
    INITIATOR_ALL_ONLINE = "allOnline"
    NO_LINK = "noLink"  # 有在线的启动器，未获取到主机的链路
    LINK_NOT_REDUNDANT = "notRedundant"
    LINK_REDUNDANT = "redundant"

    @staticmethod
    def getMergeLinkStatus(allLinkStatus):
        if LinkStatus.LINK_NOT_REDUNDANT in allLinkStatus:
            return LinkStatus.LINK_NOT_REDUNDANT
        if (
            LinkStatus.LINK_REDUNDANT in allLinkStatus
            and LinkStatus.NO_LINK in allLinkStatus
        ):
            return LinkStatus.LINK_NOT_REDUNDANT
        if LinkStatus.LINK_REDUNDANT in allLinkStatus:
            return LinkStatus.LINK_REDUNDANT
        return LinkStatus.NO_LINK

    @staticmethod
    def getMergeStatusByInitiatorType(allStatusList):
        initiatorStatusList = map(lambda x: x[0], allStatusList)
        linkStatusList = map(lambda x: x[1], allStatusList)
        # 启动器状态
        if LinkStatus.INITIATOR_SOME_OFFLINE in initiatorStatusList:
            initiatorStatus = LinkStatus.INITIATOR_SOME_OFFLINE
        elif (
            LinkStatus.INITIATOR_ALL_OFFLINE in initiatorStatusList
            and LinkStatus.INITIATOR_ALL_ONLINE in initiatorStatusList
        ):
            initiatorStatus = LinkStatus.INITIATOR_SOME_OFFLINE
        elif LinkStatus.INITIATOR_ALL_OFFLINE in initiatorStatusList:
            initiatorStatus = LinkStatus.INITIATOR_ALL_OFFLINE
        elif LinkStatus.INITIATOR_ALL_ONLINE in initiatorStatusList:
            initiatorStatus = LinkStatus.INITIATOR_ALL_ONLINE
        else:
            initiatorStatus = LinkStatus.NO_INITIATOR

        # 链路状态
        if LinkStatus.LINK_NOT_REDUNDANT in linkStatusList:
            linkStatus = LinkStatus.LINK_NOT_REDUNDANT
        elif LinkStatus.LINK_REDUNDANT in linkStatusList:
            linkStatus = LinkStatus.LINK_REDUNDANT
        elif initiatorStatus in [
            LinkStatus.INITIATOR_SOME_OFFLINE,
            LinkStatus.INITIATOR_ALL_ONLINE,
        ]:
            linkStatus = LinkStatus.LINK_NOT_REDUNDANT
        else:
            linkStatus = LinkStatus.LINK_REDUNDANT

        # 状态合成
        if initiatorStatus == LinkStatus.INITIATOR_ALL_OFFLINE:
            return True, ""
        if linkStatus == LinkStatus.LINK_REDUNDANT:
            return True, ""
        return False, "frontLinkCheck.notpass"


"""
1 查询所有主机
2 查询主机下面的启动器（iscsi/fc/ib）
3 查询所有业务端口
4 查询每个业务端口链接的启动器
5 检查链路每种类型的链路是否按平面冗余
"""


class FrontlinkCheck(ShareData):
    def __init__(self, data):
        ShareData.__init__(self, data)
        self.hosts = []
        self.checkResult = []

    def execute(self):
        try:
            hosts = self.resrouceFactory.executeInterface("getHost")
            self.addOriginInfo("getHost", hosts)
            self.hosts = self.filterCheckHost(hosts)
            if len(self.hosts) == 0:
                return CheckResult(True, self.originInfos, "")
            self.getHost2PortRelation()
            fcResults = self.checkRedundantByFc()
            iscsiResults = self.checkRedundantByIscsi()
            ibResults = self.checkRedundantByIb()
            return self.mergeResults(fcResults, iscsiResults, ibResults)
        except Exception:
            self.logger.error(
                "checkbyPortLink excute exception:[%s]"
                % traceback.format_exc()
            )
            errMsg = baseUtil.getMsg(self.lang, "query.result.abnormal")
            return CheckResult(False, self.originInfos, errMsg)

    def mergeResults(self, fcResults, iscsiResults, ibResults):
        allResults = {}
        for host in self.hosts:
            hostId = host.get("ID")
            results = []
            fcResult = fcResults.get(hostId)
            if fcResult:
                results.append(fcResult)
            iscsiResult = iscsiResults.get(hostId)
            if iscsiResult:
                results.append(iscsiResult)
            ibResult = ibResults.get(hostId)
            if ibResult:
                results.append(ibResult)

            oneHostResult = LinkStatus.getMergeStatusByInitiatorType(results)
            self.logger.info(
                "merge result. hostId=%s, mergebefore=%s, mergeafter=%s"
                % (hostId, results, oneHostResult)
            )
            allResults[hostId] = oneHostResult

        # 提示信息：链路不冗余的主机有：%s
        status, errMsg = CheckResult.getMergeResultStr(allResults, self.lang)
        return CheckResult(status, self.originInfos, errMsg)

    def filterCheckHost(self, hosts):
        checkHosts = []
        for host in hosts:
            # 主机没有加给主机组不需要检查，启动器数量字段不准确，不用作判断
            if host["ISADD2HOSTGROUP"] != "true":
                continue

            # 主机没有映射的lun，不需要检查
            lunNumObj = self.resrouceFactory.executeInterface(
                "getHostLunAndSnapCount", host.get("ID")
            )
            self.addOriginInfo("getHostLunCount#" + host.get("ID"), lunNumObj)
            lunNum = int(lunNumObj.get("COUNT", 0))
            if lunNum > 0:
                checkHosts.append(host)
        return checkHosts

    def checkRedundantByFc(self):
        # 查询主机的启动器
        fcInitiators = self.resrouceFactory.executeInterface("getFcInitiators")
        if not fcInitiators:
            return {}

        fcPortLinks = {}
        fcPorts = self.resrouceFactory.executeInterface("getFcProts")
        self.addOriginInfo("getFcPorts", fcPorts)
        fcoePorts = self.resrouceFactory.executeInterface("getFcoeProts")
        self.addOriginInfo("getFcoePorts", fcoePorts)

        for port in fcPorts:
            if port.get("RUNNINGSTATUS") != "10":
                # 端口不在线不用查链路
                continue
            portId = port.get("ID")
            tmpfcLinks = self.resrouceFactory.executeInterface(
                "getFcPortLink", portId
            )
            # 过滤掉不在线的链路
            tmpfcLinks = filter(
                lambda x: x.get("RUNNINGSTATUS") == "27", tmpfcLinks
            )
            tmpfcLinks = map(lambda x: dict(ID=x.get("ID")), tmpfcLinks)
            self.addOriginInfo("getFcPortLink#" + portId, tmpfcLinks)
            if tmpfcLinks:
                fcPortLinks[portId] = tmpfcLinks

        for port in fcoePorts:
            if port.get("RUNNINGSTATUS") != "10":
                # 端口不在线不用查链路
                continue
            portId = port.get("ID")
            tmpfcLinks = self.resrouceFactory.executeInterface(
                "getFcoePortLink", portId
            )
            # 过滤掉不在线的链路
            tmpfcLinks = filter(
                lambda x: x.get("RUNNINGSTATUS") == "27", tmpfcLinks
            )
            tmpfcLinks = map(lambda x: dict(ID=x.get("ID")), tmpfcLinks)
            self.addOriginInfo("getFcoePortLink#" + portId, tmpfcLinks)
            if tmpfcLinks:
                fcPortLinks[portId] = tmpfcLinks

        fcPorts.extend(fcoePorts)
        return self.checkHostRedundant(fcInitiators, fcPorts, fcPortLinks)

    def checkRedundantByIscsi(self):
        iscsiInitiators = self.resrouceFactory.executeInterface(
            "getIscsiInitiators"
        )
        if not iscsiInitiators:
            return {}
        ethPortLinks = {}
        ethPorts = self.resrouceFactory.executeInterface("getEthProts")
        self.addOriginInfo("getEthProts", ethPorts)
        for port in ethPorts:
            if port.get("RUNNINGSTATUS") != "10":
                # 端口不在线不用查链路
                continue
            portId = port.get("ID")
            tmpethLinks = self.resrouceFactory.executeInterface(
                "getIscsiPortLink", portId
            )
            # 过滤掉不在线的链路
            tmpethLinks = filter(
                lambda x: x.get("RUNNINGSTATUS") == "27", tmpethLinks
            )
            tmpethLinks = map(lambda x: dict(ID=x.get("ID")), tmpethLinks)
            self.addOriginInfo("getIscsiPortLink#" + portId, tmpethLinks)
            if tmpethLinks:
                ethPortLinks[portId] = tmpethLinks
        return self.checkHostRedundant(iscsiInitiators, ethPorts, ethPortLinks)

    def checkRedundantByIb(self):
        ibInitiators = self.resrouceFactory.executeInterface("getIbInitiators")
        if not ibInitiators:
            return {}
        ibPortLinks = {}
        ibPorts = self.resrouceFactory.executeInterface("getIbProts")
        self.addOriginInfo("getIbProts", ibPorts)
        for port in ibPorts:
            if port.get("RUNNINGSTATUS") != "10":
                # 端口不在线不用查链路
                continue
            portId = port.get("ID")
            tmpibLinks = self.resrouceFactory.executeInterface(
                "getIbPortLink", portId
            )
            # 过滤掉不在线的链路
            tmpibLinks = filter(
                lambda x: x.get("RUNNINGSTATUS") == "27", tmpibLinks
            )
            tmpibLinks = map(lambda x: dict(ID=x.get("ID")), tmpibLinks)
            self.addOriginInfo("getIbPortLink#" + portId, tmpibLinks)
            if tmpibLinks:
                ibPortLinks[portId] = tmpibLinks

        return self.checkHostRedundant(ibInitiators, ibPorts, ibPortLinks)

    def getHost2InitiatorRelation(self, initiators):
        ret = {}
        for initiator in initiators:
            hostId = initiator.get("PARENTID", "")
            if hostId == "":
                continue
            if hostId not in ret:
                ret[hostId] = []
            ret[hostId].append(initiator)
        self.logger.info("host2InitiatorRelation:[%s]" % ret)
        self.addOriginInfo("host2InitiatorRelation", ret)
        return ret

    def getPort2ControllerRelation(self, ports):
        ret = {}
        for port in ports:
            portId = port.get("ID")
            parentId = port.get("PARENTID")
            ctrlId = parentId.split(".")[0]
            ret[portId] = ctrlId
        self.logger.info("port2ControllerRelation:[%s]" % ret)
        self.addOriginInfo("port2ControllerRelation", ret)
        return ret

    # 获取一个启动器的链接的端口
    def getInitiator2PortRelation(self, port2InitiatorDict):
        ret = {}
        for portId, initiators in port2InitiatorDict.items():
            for initiator in initiators:
                initiator_id = initiator.get("ID")
                if initiator_id not in ret:
                    ret[initiator_id] = []
                ret[initiator_id].append(portId)
        self.logger.info("initiator2PortRelation:[%s]" % ret)
        self.addOriginInfo("initiator2PortRelation", ret)
        return ret

    def getHost2PortRelation(self):
        # 查所有映射视图
        # 关联查映射视图的端口组
        # 如果存在端口组，关联查映射视图的主机组
        # 查主机组下的主机、查端口组下面的端口
        # 记录主机与端口的映射关系
        self.host2PortDict = {}
        mapping_views = self.resrouceFactory.executeInterface("getMappingViews")
        for mapping_view in mapping_views:
            mappingviewId = mapping_view.get("ID")
            port_groups = self.resrouceFactory.executeInterface(
                "getPortgroupByMapping", mappingviewId
            )
            if not port_groups:
                continue
            host_groups = self.resrouceFactory.executeInterface(
                "getHostgroupByMapping", mappingviewId
            )
            if not host_groups:
                continue
            hostGroupId = host_groups[0].get("ID")
            hosts = self.resrouceFactory.executeInterface(
                "getHostByHostGroup", hostGroupId
            )
            portGroupId = port_groups[0].get("ID")
            ports = self.resrouceFactory.executeInterface(
                "getPortByPortGroup", portGroupId
            )
            ports = map(lambda x: x.get("ID", ""), ports)
            for host in hosts:
                hostId = host.get("ID")
                if hostId not in self.host2PortDict:
                    self.host2PortDict[hostId] = []
                self.host2PortDict[hostId].append(ports)
        self.logger.info("host2PortRelation:[%s]" % self.host2PortDict)
        self.addOriginInfo("host2PortDict", self.host2PortDict)

    def checkHostRedundant(self, initiators, ports, port2InitiatorDict):
        # 存放检查结果
        checkResults = {}
        self.hostId2InitiatorDict = self.getHost2InitiatorRelation(initiators)
        self.initiator2PortDict = self.getInitiator2PortRelation(
            port2InitiatorDict
        )
        self.port2ControllerDict = self.getPort2ControllerRelation(ports)

        for host in self.hosts:
            hostId = host["ID"]
            # 找主机的启动器
            one_host_initiators = self.hostId2InitiatorDict.get(hostId)
            if not one_host_initiators:
                continue
            initiatorStatus = self.checkInitiatorStatus(one_host_initiators)
            one_host_ports = self.host2PortDict.get(hostId)
            if not one_host_ports:
                self.logger.info("host mapping not by ports")
                linkStatus = self.checkLinkStatus(one_host_initiators)
                checkResults[hostId] = [initiatorStatus, linkStatus]
                continue
            link_status_list = []
            for one_host_one_mapping_ports in one_host_ports:
                self.logger.info(
                    "host mapping by ports, ports=[%s]"
                    % one_host_one_mapping_ports
                )
                tmplink_status = self.checkLinkStatus(
                    one_host_initiators, one_host_one_mapping_ports
                )
                link_status_list.append(tmplink_status)
            self.logger.info("link_status_list=%s" % link_status_list)
            linkStatus = LinkStatus.getMergeLinkStatus(link_status_list)
            checkResults[hostId] = [initiatorStatus, linkStatus]
        return checkResults

    def checkInitiatorStatus(self, one_host_initiators):
        if not one_host_initiators:
            return LinkStatus.NO_INITIATOR

        # 检查启动器状态
        has_online_initiator = False
        has_offline_initiator = False
        for one_initiator in one_host_initiators:
            if one_initiator.get("RUNNINGSTATUS") == "27":
                has_online_initiator = True
            else:
                has_offline_initiator = True

        # 无在线的启动器
        if not has_online_initiator:
            return LinkStatus.INITIATOR_ALL_OFFLINE
        if has_offline_initiator:
            return LinkStatus.INITIATOR_SOME_OFFLINE
        return LinkStatus.INITIATOR_ALL_ONLINE

    def checkLinkStatus(self, oneHostInitiators, onehostPorts=None):
        ctrl_ids = []
        # 找启动器对应的端口
        for one_initiator in oneHostInitiators:
            initiatorId = one_initiator.get("ID")
            port_ids = self.initiator2PortDict.get(initiatorId, [])
            if onehostPorts:
                port_ids = list(set(port_ids).intersection(set(onehostPorts)))

            # 找端口对应的平面
            for port_id in port_ids:
                ctrl_id = self.port2ControllerDict.get(port_id)
                ctrl_ids.append(ctrl_id)

        # 根据主机与控制器的关系，判断主机是否按平面冗余
        if len(ctrl_ids) == 0:
            return LinkStatus.NO_LINK
        elif not self.isCtrlRedundant(ctrl_ids):
            return LinkStatus.LINK_NOT_REDUNDANT
        else:
            return LinkStatus.LINK_REDUNDANT

    # 检查主机
    def isCtrlRedundant(self, ctrlIds):
        CTRL_A_PLAN = ["A", "C"]
        CTRL_B_PLAN = ["B", "D"]
        ctrlIds = map(lambda x: x[-1].upper(), ctrlIds)
        alist = list(set(ctrlIds).intersection(set(CTRL_A_PLAN)))
        blist = list(set(ctrlIds).intersection(set(CTRL_B_PLAN)))
        return len(alist) != 0 and len(blist) != 0
