# coding:utf-8
import traceback
from cbb.frame.adapter.resourceService import ResrouceFactory
from cbb.frame.context import contextUtil
from cbb.frame.base import baseUtil
from cbb.frame.base.constants import CheckResult
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.nolink"


"""
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 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 {}

        fcHostLinks = {}
        for host in self.hosts:
            hostId = host.get("ID")
            tmpfcLinks = self.resrouceFactory.executeInterface(
                "getHostLinkByType", hostId, "FC"
            )
            tmpfcLinks = filter(
                lambda x: str(x.get("RUNNINGSTATUS", "")) == "27", tmpfcLinks
            )
            self.addOriginInfo("getFcPortLink#" + hostId, tmpfcLinks)
            if tmpfcLinks:
                fcHostLinks[hostId] = tmpfcLinks

        return self.checkHostRedundant(fcInitiators, fcHostLinks)

    def checkRedundantByIscsi(self):
        iscsiInitiators = self.resrouceFactory.executeInterface(
            "getIscsiInitiators"
        )
        if not iscsiInitiators:
            return {}
        iscsiHostLinks = {}
        for host in self.hosts:
            hostId = host.get("ID")
            tmpethLinks = self.resrouceFactory.executeInterface(
                "getHostLinkByType", hostId, "ISCSI"
            )
            tmpethLinks = filter(
                lambda x: str(x.get("RUNNINGSTATUS", "")) == "27", tmpethLinks
            )
            self.addOriginInfo("getIscsiHostLink#" + hostId, tmpethLinks)
            if tmpethLinks:
                iscsiHostLinks[hostId] = tmpethLinks
        return self.checkHostRedundant(iscsiInitiators, iscsiHostLinks)

    def checkRedundantByIb(self):
        ibInitiators = self.resrouceFactory.executeInterface("getIbInitiators")
        if not ibInitiators:
            return {}
        ibHostLinks = {}
        for host in self.hosts:
            hostId = host.get("ID")
            tmpibLinks = self.resrouceFactory.executeInterface(
                "getHostLinkByType", hostId, "IB"
            )
            tmpibLinks = filter(
                lambda x: str(x.get("RUNNINGSTATUS", "")) == "27", tmpibLinks
            )
            self.addOriginInfo("getIbHostLink#" + hostId, tmpibLinks)
            if tmpibLinks:
                ibHostLinks[hostId] = tmpibLinks

        return self.checkHostRedundant(ibInitiators, ibHostLinks)

    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 getHost2PortRelation(self):
        # 查所有映射视图
        # 关联查映射视图的端口组
        # 如果存在端口组，关联查映射视图的主机组
        # 查主机组下的主机、查端口组下面的端口
        # 记录主机与端口的映射关系
        self.host2PortDict = {}
        mappingviews = self.resrouceFactory.executeInterface("getMappingViews")
        for mappingview in mappingviews:
            mappingviewId = mappingview.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
            host_group_id = host_groups[0].get("ID")
            hosts = self.resrouceFactory.executeInterface(
                "getHostByHostGroup", host_group_id
            )
            portGroupId = port_groups[0].get("ID")
            ports = self.resrouceFactory.executeInterface(
                "getPortByPortGroup", portGroupId
            )
            ports = map(lambda x: x.get("WWN", ""), 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, hostLinkDict):
        # 存放检查结果
        checkResults = {}
        self.hostId2InitiatorDict = self.getHost2InitiatorRelation(initiators)

        for host in self.hosts:
            # 存放主机连接的控制器ID
            hostId = host["ID"]
            # 找主机的启动器
            oneHostInitiators = self.hostId2InitiatorDict.get(hostId)
            if not oneHostInitiators:
                # 主机下没有启动器
                continue
            initiatorStatus = self.checkInitiatorStatus(oneHostInitiators)
            one_host_links = hostLinkDict.get(hostId, [])
            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_links)
                checkResults[hostId] = [initiatorStatus, linkStatus]
                continue
            linkStatusList = []
            for one_host_one_mapping_ports in one_host_ports:
                self.logger.info(
                    "host mapping by ports, ports=[%s]"
                    % one_host_one_mapping_ports
                )
                tmplinkStatus = self.checkLinkStatus(
                    one_host_links, one_host_one_mapping_ports
                )
                linkStatusList.append(tmplinkStatus)
            self.logger.info("linkStatusList=%s" % linkStatusList)
            linkStatus = LinkStatus.getMergeLinkStatus(linkStatusList)
            checkResults[hostId] = [initiatorStatus, linkStatus]
        return checkResults

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

        # 检查启动器状态
        has_online_initiator = False
        has_offline_initiator = False
        for one_initiator in oneHostInitiators:
            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, onehostLinks, onehostPorts=None):

        # 过滤这个主机映射的端口的主机链路
        ctrlIds = []
        for one_link in onehostLinks:
            tgtPort = one_link.get("TARGET_PORT_WWN")
            if not onehostPorts or tgtPort in onehostPorts:
                ctrlId = one_link.get("CTRL_ID")
                ctrlIds.append(ctrlId)

        # 根据主机与控制器的关系，判断主机是否按平面冗余
        if len(ctrlIds) == 0:
            return LinkStatus.NO_LINK
        elif not self.isCtrlRedundant(ctrlIds):
            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)
        ctrl_a_list = list(set(ctrlIds).intersection(set(CTRL_A_PLAN)))
        ctrl_b_list = list(set(ctrlIds).intersection(set(CTRL_B_PLAN)))
        return len(ctrl_a_list) != 0 and len(ctrl_b_list) != 0
