"""
用来计算健康雷达指数评分、用电健康指数评分
"""
from math import sqrt
import pendulum
import datetime
from pot_libs.es_util.es_utils import EsUtil
from pot_libs.logger import log
from pot_libs.mysql_util.mysql_util import MysqlUtil
from unify_api.modules.common.dao.health_score_dao import \
    health_score_points_aggs, get_point_dats_dao, get_mean_datas_dao
from unify_api.modules.common.procedures.points import get_points
from unify_api.modules.electric.procedures.electric_util import \
    batch_get_wiring_type
from unify_api.modules.home_page.procedures import point_inlines
from unify_api.constants import FREQ_STANDARD, POINT_15MIN_INDEX
from unify_api.modules.home_page.procedures.dev_grade import get_dev_score


async def load_health_radar(cid, param_point_id=None):
    """获取健康指数雷达-取15min数据
        注意:
        1.负载率的数据在写入ES15min数据时有做变压器判断逻辑，这里直接读取即可
        2.功率因数：有进线级功率因数时，只计算进线级功率因数
        3.电压谐波畸变：只计算三表法计量点，如果所有监测点都是二表法，则取其他所有指标均值
        """
    # 先从redis取，没有则重新计算，过期时间戳为当天0点
    # redis_key = "elec_score:health:%s" % cid
    # json_score = await RedisClient().get(redis_key)
    # if json_score:
    #     score_info = json.loads(json_score)
    #     return score_info

    # 计算最近7天时间起始
    today = pendulum.today()
    start_time = str(today.subtract(days=7))
    end_time = str(today.subtract(seconds=1))

    inline_point_ids = []
    point_ids = []
    # 1. 获取该工厂所有进线数据
    inline_infos = await point_inlines.get_point_inlines(cid)
    for pid, inline in inline_infos.items():
        if param_point_id and pid != param_point_id:
            # 指定了监测点, 只算监测点的健康指数
            continue
        if inline:
            inline_point_ids.append(pid)
        else:
            point_ids.append(pid)

    # 对如下性能差代码做修改
    stats = {point_id: {} for point_id in inline_point_ids + point_ids}
    point_info_map = await batch_get_wiring_type(inline_point_ids + point_ids)
    es_health = await health_score_points_aggs(
        start_time, end_time, inline_point_ids + point_ids
    )
    es_dic = {i["pid"]: i for i in es_health if es_health}
    # 统计所有点所有平均值
    for point_id in inline_point_ids + point_ids:
        ctnum = point_info_map[point_id]["ctnum"]
        if ctnum == 3:
            stats_items = ["ua_mean", "freq_mean", "ubl_mean", "costtl_mean",
                           "thdua_mean", "lf_mean"]
        else:
            stats_items = ["uab_mean", "freq_mean", "ubl_mean", "costtl_mean",
                           "lf_mean"]

        for item in stats_items:
            point_v = es_dic.get(point_id)
            if not point_v:
                stats[point_id][item] = None
            else:
                stats[point_id][item] = point_v[item + '_avg']
    '''
    range = Range(field="quarter_time", start=start_time, end=end_time)
    stats = {point_id: {} for point_id in inline_point_ids + point_ids}
    # 统计所有点所有平均值
    for point_id in inline_point_ids + point_ids:
        ctnum, mid = await get_wiring_type(point_id)
        if ctnum not in [2, 3]:
            log.warn(f"health_radar point_id={point_id} ctnum={ctnum} 找不到ctnum")
            continue

        if ctnum == 3:
            stats_items = [
                "ua_mean",
                "freq_mean",
                "ubl_mean",
                "costtl_mean",
                "thdua_mean",
                "lf_mean",
            ]
        else:
            stats_items = ["uab_mean", "freq_mean", "ubl_mean", "costtl_mean", "lf_mean"]
        equal = Equal(field="pid", value=point_id)
        filter = Filter(equals=[equal], ranges=[range], in_groups=[], keywords=[])
        page_request = PageRequest(page_size=1, page_num=1, sort=None, filter=filter)
        query_body = EsQuery.aggr_index(page_request, stats_items=stats_items)
        print(query_body)
        async with EsUtil() as es:
            es_results = await es.search_origin(body=query_body, index=POINT_15MIN_INDEX)

        if not es_results:
            log.warning("can not find data on es(index: %s): %s" % (POINT_15MIN_INDEX, query_body))

        aggregations = es_results.get("aggregations", {})
        for item in stats_items:
            avg = aggregations.get("%s_avg" % item, {}).get("value")
            stats[point_id][item] = avg
    '''
    # 获取所有poin_id和mtid对应关系
    all_point_ids = inline_point_ids + point_ids
    point_mid_map = {}
    if all_point_ids:
        sql = (
            "SELECT pid, mtid FROM point WHERE pid IN %s order by pid, create_time asc"
        )
        async with MysqlUtil() as conn:
            change_meter_records = await conn.fetchall(sql, args=(tuple(all_point_ids),))
            point_mid_map = {
                i["pid"]: i["mtid"] for i in change_meter_records if i["mtid"] is not None
            }

    # 获取meter_param_record中的标准电压
    all_mids = list(point_mid_map.values())
    meter_param_map = {}
    if all_mids:
        async with MysqlUtil() as conn:
            sql = "SELECT mtid, vc, voltage_side, ctnum FROM point WHERE mtid IN %s order by mtid, create_time asc"
            meter_param_records = await conn.fetchall(sql, args=(tuple(all_mids),))
            meter_param_map = {i["mtid"]: i for i in meter_param_records}

    log.info(f"all_mids={all_mids}")
    # 电压偏差评分
    total, total_score = 0, 0
    for point_id in inline_point_ids + point_ids:
        ua_mean = stats.get(point_id, {}).get("ua_mean")
        if ua_mean is None:
            continue

        mtid = point_mid_map.get(point_id)
        if not mtid:
            # pid没有mid，拆了
            log.warning(f"pid={point_id} mtid={mtid} mid无效")
            continue
        meter_param = meter_param_map.get(mtid)
        if not meter_param:
            log.warning(f"pid={point_id} mtid={mtid} 没有表参数")
            continue
        meter_vc, ctnum = meter_param.get("vc"), meter_param.get("ctnum") or 3
        if meter_vc:
            stand_voltage = meter_vc / sqrt(3) if ctnum == 3 else meter_vc
        else:
            stand_voltage = 400 if ctnum == 3 else 10000

        v_dev = (ua_mean - stand_voltage) / stand_voltage
        score = get_dev_score(dev_type="v", cur=v_dev)
        if score is None:
            continue

        total_score += score
        total += 1
    v_score = total_score / total if total else 100

    # 频率偏差评分
    total, total_score = 0, 0
    for point_id in inline_point_ids + point_ids:
        freq_mean = stats.get(point_id, {}).get("freq_mean")
        if freq_mean is None:
            continue

        freq_dev = freq_mean - FREQ_STANDARD
        score = get_dev_score(dev_type="freq", cur=freq_dev)
        if score is None:
            continue

        total_score += score
        total += 1
    freq_score = total_score / total if total else 100

    # 三相[电压]不平衡评分
    total, total_score = 0, 0
    for point_id in inline_point_ids + point_ids:
        ubl_avg = stats.get(point_id, {}).get("ubl_mean")
        if ubl_avg is None:
            continue

        score = get_dev_score(dev_type="ubl", cur=ubl_avg)
        if score is None:
            continue

        total_score += score
        total += 1
    ubl_score = total_score / total if total else 100

    # 功率因数：有进线级功率因数时，只计算进线级功率因数
    total, total_score = 0, 0
    if inline_point_ids:
        ids = inline_point_ids
    else:
        ids = point_ids
    for point_id in ids:
        costtl_mean = stats.get(point_id, {}).get("costtl_mean")
        if costtl_mean is None:
            continue

        score = get_dev_score(dev_type="costtl", cur=costtl_mean)
        if score is None:
            continue

        total_score += score
        total += 1
    costtl_score = total_score / total if total else 100

    # (电压)谐波畸变率
    # 电压谐波畸变：只计算三表法计量点，如果所有监测点都是二表法，则取其他所有指标均值
    total, total_score = 0, 0
    for point_id in inline_point_ids + point_ids:
        thdua_mean = stats.get(point_id, {}).get("thdua_mean")
        if thdua_mean is None:
            continue

        score = get_dev_score(dev_type="thdu", cur=thdua_mean)
        if score is None:
            continue

        total_score += score
        total += 1
    thdu_score = total_score / total if total else 100

    # 负载率
    total, total_score = 0, 0
    for point_id in inline_point_ids + point_ids:
        lf_mean = stats.get(point_id, {}).get("lf_mean")
        if lf_mean is None:
            score = 100
        else:
            score = get_dev_score(dev_type="lf", cur=lf_mean)
        if score is None:
            continue

        total_score += score
        total += 1
    lf_score = total_score / total if total else 100
    log.info(
        "v_score:%s, freq_score:%s, ubl_score:%s, costtl_score:%s, " "thdu_score:%s, lf_score:%s",
        v_score,
        freq_score,
        ubl_score,
        costtl_score,
        thdu_score,
        lf_score,
    )
    if not thdu_score:
        thdu_score = (v_score + freq_score + ubl_score + costtl_score + lf_score) / 5.0

    # 存入redis
    score_info = {
        "v_score": v_score,
        "freq_score": freq_score,
        "ubl_score": ubl_score,
        "costtl_score": costtl_score,
        "thdu_score": thdu_score,
        "lf_score": lf_score,
    }

    # now_ts = pendulum.now().int_timestamp
    # tomorrow_ts = pendulum.tomorrow().int_timestamp
    # exp_ts = tomorrow_ts - now_ts
    #
    # await RedisClient().setex(redis_key, exp_ts, json.dumps(score_info))

    return score_info


async def load_health_radar_new15(cid, param_point_id=None):
    # 计算最近7天时间起始
    today = datetime.date.today()
    start_time = (today - datetime.timedelta(days=7)).strftime(
        "%Y-%m-%d %H:%M:%S")
    end_time = (today - datetime.timedelta(days=1)).strftime(
        "%Y-%m-%d 23:59:59")
    points_datas = await get_point_dats_dao(cid, param_point_id)
    items_2 = ("uab_mean", "freq_mean", "ubl_mean", "costtl_mean",
               "thduab_mean", "lf_mean")
    items_3 = ("ua_mean", "freq_mean", "ubl_mean", "costtl_mean",
               "thdua_mean", "lf_mean")
    points_datas = {point["pid"]: point for point in points_datas}
    pids_2, pids_3 = [], []
    for pid, value in points_datas.items():
        if value["ctnum"] == 2:
            pids_2.append(pid)
        else:
            pids_3.append(pid)
    datas_2 = await get_mean_datas_dao(pids_2, items_2, start_time, end_time)
    datas_3 = await get_mean_datas_dao(pids_3, items_3, start_time, end_time)
    datas = {data["pid"]: data for data in list(datas_2) + list(datas_3)}
    if datas and points_datas:
        for p_pid, p_value in points_datas.items():
            for d_pid, d_value in datas.items():
                if p_pid == d_pid:
                    p_value.update(d_value)
    # 电压偏差评分
    total, total_score = 0, 0
    for index, data in points_datas.items():
        ua_mean = data.get("ua_mean")
        if not ua_mean:
            continue
        meter_vc, ctnum = data.get("vc"), data.get("ctnum") or 3
        if meter_vc:
            stand_voltage = meter_vc / sqrt(3) if ctnum == 3 else meter_vc
        else:
            stand_voltage = 400 if ctnum == 3 else 10000
        v_dev = (ua_mean - stand_voltage) / stand_voltage
        score = get_dev_score(dev_type="v", cur=v_dev)
        if score is None:
            continue
        total_score += score
        total += 1
    v_score = total_score / total if total else 100
    # 频率偏差评分
    total, total_score = 0, 0
    for index, data in points_datas.items():
        freq_mean = data.get("freq_mean")
        if not freq_mean:
            continue
        freq_dev = freq_mean - FREQ_STANDARD
        score = get_dev_score(dev_type="freq", cur=freq_dev)
        if score is None:
            continue
        total_score += score
        total += 1
    freq_score = total_score / total if total else 100

    # 三相[电压]不平衡评分
    total, total_score = 0, 0
    for index, data in points_datas.items():
        ubl_avg = data.get("ubl_mean")
        if ubl_avg is None:
            continue
        score = get_dev_score(dev_type="ubl", cur=ubl_avg)
        if score is None:
            continue
        total_score += score
        total += 1
    ubl_score = total_score / total if total else 100

    # 功率因数：有进线级功率因数时，只计算进线级功率因数
    total, total_score = 0, 0
    for index, data in points_datas.items():
        costtl_mean = data.get("costtl_mean")
        if costtl_mean is None:
            continue
        score = get_dev_score(dev_type="costtl", cur=costtl_mean)
        if score is None:
            continue
        total_score += score
        total += 1
    costtl_score = total_score / total if total else 100

    # (电压)谐波畸变率
    # 电压谐波畸变：只计算三表法计量点，如果所有监测点都是二表法，则取其他所有指标均值
    total, total_score = 0, 0
    for index, data in points_datas.items():
        thdua_mean = data.get("thdua_mean")
        if thdua_mean is None:
            continue
        score = get_dev_score(dev_type="thdu", cur=thdua_mean)
        if score is None:
            continue
        total_score += score
        total += 1
    thdu_score = total_score / total if total else 100

    # 负载率
    total, total_score = 0, 0
    for index, data in points_datas.items():
        lf_mean = data.get("lf_mean")
        if lf_mean is None:
            score = 100
        else:
            score = get_dev_score(dev_type="lf", cur=lf_mean)
        if score is None:
            continue
        total_score += score
        total += 1
    lf_score = total_score / total if total else 100

    if not thdu_score:
        thdu_score = (v_score + freq_score + ubl_score + costtl_score +
                      lf_score) / 5.0
    score_info = {
        "v_score": round(v_score, 2),
        "freq_score": round(freq_score, 2),
        "ubl_score": round(ubl_score, 2),
        "costtl_score": round(costtl_score, 2),
        "thdu_score": round(thdu_score, 2),
        "lf_score": round(lf_score, 2),
    }
    return score_info


async def load_health_index(cid, point_id=None):
    """用电健康指数"""
    # score_info = await load_health_radar(cid, point_id)
    score_info = await load_health_radar_new15(cid, point_id)

    if score_info is None:
        log.error("load_health_index fail")
        return 0

    v_score = score_info["v_score"]
    freq_score = score_info["freq_score"]
    if v_score <= 60 or freq_score <= 60:
        # 电压偏差/频率偏差评分，有一个低于60分，则整体健康指数为0
        log.info("v_score or freq_score less 60")
        return 0

    sub_dev = (1 - (v_score + freq_score) / 200.0) * 20
    sub_lf = (1 - score_info["lf_score"] / 100.0) * 20
    sub_costtl = (1 - score_info["costtl_score"] / 100.0) * 20
    sub_thdu = (1 - score_info["thdu_score"] / 100.0) * 20
    sub_ubl = (1 - score_info["ubl_score"] / 100.0) * 20
    return 100 - sub_dev - sub_lf - sub_costtl - sub_thdu - sub_ubl


async def load_manage_health_radar(cids, recent_days=30):
    """获取健康指数雷达-取15min数据
        注意:
        1.负载率的数据在写入ES15min数据时有做变压器判断逻辑，这里直接读取即可
        2.功率因数：有进线级功率因数时，只计算进线级功率因数
        3.电压谐波畸变：只计算三表法计量点，如果所有监测点都是二表法，则取其他所有指标均值
        """
    # 先从redis取，没有则重新计算，过期时间戳为当天0点
    # redis_key = "elec_score:health:%s" % cid
    # json_score = await RedisClient().get(redis_key)
    # if json_score:
    #     score_info = json.loads(json_score)
    #     return score_info

    # 计算最近30天时间起始
    today = pendulum.today()
    start_time = str(today.subtract(days=recent_days))
    end_time = str(today.subtract(seconds=1))
    company_point_map = await get_points(cids)

    all_point_map = {
        point_id: point_info
        for i in company_point_map.values()
        for point_id, point_info in i.items()
    }
    all_point_ids = list(all_point_map.keys())
    query_body = {
        "query": {
            "bool": {
                "filter": [
                    {"terms": {"pid": all_point_ids}},
                    {"range": {"quarter_time": {"gte": start_time, "lte": end_time,}}},
                ],
            }
        },
        "size": 0,
        "aggs": {},
    }

    for point_id in all_point_ids:
        ctnum = all_point_map[point_id]["meter_param"]["ctnum"]
        if ctnum == 3:
            stats_items = [
                "ua_mean",
                "freq_mean",
                "ubl_mean",
                "costtl_mean",
                "thdua_mean",
                "lf_mean",
            ]
        else:
            stats_items = ["uab_mean", "freq_mean", "ubl_mean", "costtl_mean", "lf_mean"]

        aggs_stats = {}
        for stats_item in stats_items:
            aggs_stats[stats_item] = {"stats": {"field": stats_item}}

        query_body["aggs"][f"point_id_{point_id}_aggs"] = {
            "filter": {"term": {"pid": point_id}},
            "aggs": aggs_stats,
        }

    async with EsUtil() as es:
        es_result = await es.search_origin(body=query_body, index=POINT_15MIN_INDEX)

    # 单独计算每个公司的健康指数
    company_score_map = {}
    for cid in cids:
        point_map = company_point_map.get(cid)
        # 公司没有监测点
        if not point_map:
            # 默认没有监测点，各项分数都满分
            company_score_map[cid] = {
                "v_score": 100,
                "freq_score": 100,
                "ubl_score": 100,
                "costtl_score": 100,
                "thdu_score": 100,
                "lf_score": 100,
            }
            continue
        inline_point_ids, point_ids = [], []
        for point_id, point_item in point_map.items():
            if point_item["inlid_belongedto"]:
                inline_point_ids.append(point_id)
            else:
                point_ids.append(point_id)

        # 1. 电压偏差评分
        total, total_score = 0, 0
        for point_id in inline_point_ids + point_ids:
            ua_mean = (
                es_result.get("aggregations", {})
                .get(f"point_id_{point_id}_aggs", {})
                .get("ua_mean", {})
                .get("avg")
            )
            if ua_mean is None:
                continue
            point_info = all_point_map[point_id]
            if not point_info["meter_param"]:
                # 没有参数的装置， 拆了?
                continue
            meter_param = point_info["meter_param"]
            meter_vc, ctnum = meter_param.get("vc"), meter_param.get("ctnum") or 3
            if meter_vc:
                stand_voltage = meter_vc / sqrt(3) if ctnum == 3 else meter_vc
            else:
                stand_voltage = 400 if ctnum == 3 else 10000
            v_dev = (ua_mean - stand_voltage) / stand_voltage
            score = get_dev_score(dev_type="v", cur=v_dev)
            if score is None:
                continue

            total_score += score
            total += 1
        v_score = total_score / total if total else 100

        # 2. 频率偏差评分
        total, total_score = 0, 0
        for point_id in inline_point_ids + point_ids:
            freq_mean = (
                es_result.get("aggregations", {})
                .get(f"point_id_{point_id}_aggs", {})
                .get("freq_mean", {})
                .get("avg")
            )
            if freq_mean is None:
                continue

            freq_dev = freq_mean - FREQ_STANDARD
            score = get_dev_score(dev_type="freq", cur=freq_dev)
            if score is None:
                continue
            total_score += score
            total += 1
        freq_score = total_score / total if total else 100

        # 3. 三相[电压]不平衡评分
        total, total_score = 0, 0
        for point_id in inline_point_ids + point_ids:
            ubl_avg = (
                es_result.get("aggregations", {})
                .get(f"point_id_{point_id}_aggs", {})
                .get("ubl_mean", {})
                .get("avg")
            )
            if ubl_avg is None:
                continue
            score = get_dev_score(dev_type="ubl", cur=ubl_avg)
            if score is None:
                continue
            total_score += score
            total += 1
        ubl_score = total_score / total if total else 100

        # 4. 功率因数：有进线级功率因数时，只计算进线级功率因数
        total, total_score = 0, 0
        if inline_point_ids:
            ids = inline_point_ids
        else:
            ids = point_ids
        for point_id in ids:
            costtl_mean = (
                es_result.get("aggregations", {})
                .get(f"point_id_{point_id}_aggs", {})
                .get("costtl_mean", {})
                .get("avg")
            )
            if costtl_mean is None:
                continue
            score = get_dev_score(dev_type="costtl", cur=costtl_mean)
            if score is None:
                continue
            total_score += score
            total += 1
        costtl_score = total_score / total if total else 100

        # 4.(电压)谐波畸变率
        # 电压谐波畸变：只计算三表法计量点，如果所有监测点都是二表法，则取其他所有指标均值
        total, total_score = 0, 0
        for point_id in inline_point_ids + point_ids:
            thdua_mean = (
                es_result.get("aggregations", {})
                .get(f"point_id_{point_id}_aggs", {})
                .get("thdua_mean", {})
                .get("avg")
            )
            if thdua_mean is None:
                continue
            score = get_dev_score(dev_type="thdu", cur=thdua_mean)
            if score is None:
                continue
            total_score += score
            total += 1
        thdu_score = total_score / total if total else 100

        # 5. 负载率
        total, total_score = 0, 0
        for point_id in inline_point_ids + point_ids:
            lf_mean = (
                es_result.get("aggregations", {})
                .get(f"point_id_{point_id}_aggs", {})
                .get("lf_mean", {})
                .get("avg")
            )
            if lf_mean is None:
                score = 100
            else:
                score = get_dev_score(dev_type="lf", cur=lf_mean)
            if score is None:
                continue
            total_score += score
            total += 1
        lf_score = total_score / total if total else 100
        log.info(
            "company_id:%s v_score:%s, freq_score:%s, ubl_score:%s, costtl_score:%s, "
            "thdu_score:%s, lf_score:%s",
            cid,
            v_score,
            freq_score,
            ubl_score,
            costtl_score,
            thdu_score,
            lf_score,
        )
        if not thdu_score:
            thdu_score = (v_score + freq_score + ubl_score + costtl_score + lf_score) / 5.0

        company_score_map[cid] = {
            "v_score": v_score,
            "freq_score": freq_score,
            "ubl_score": ubl_score,
            "costtl_score": costtl_score,
            "thdu_score": thdu_score,
            "lf_score": lf_score,
        }
    print("company_score_map = {}".format(company_score_map))
    return company_score_map


async def load_manage_health_index(company_score_info):
    """
    根据load_manage_health_radar函数返回的company_score_map计算公司的指数
    :param company_score_info:
    :return:
    """
    company_index_map = {}
    for cid, score_info in company_score_info.items():
        if score_info is None:
            log.error(f"cid = {cid}load_health_index fail")
            company_index_map[cid] = 0
            continue

        v_score = score_info["v_score"]
        freq_score = score_info["freq_score"]
        if v_score <= 60 or freq_score <= 60:
            # 电压偏差/频率偏差评分，有一个低于60分，则整体健康指数为0
            log.info(f"cid = {cid} v_score or freq_score less 60")
            company_index_map[cid] = 0
            continue

        sub_dev = (1 - (v_score + freq_score) / 200.0) * 20
        sub_lf = (1 - score_info["lf_score"] / 100.0) * 20
        sub_costtl = (1 - score_info["costtl_score"] / 100.0) * 20
        sub_thdu = (1 - score_info["thdu_score"] / 100.0) * 20
        sub_ubl = (1 - score_info["ubl_score"] / 100.0) * 20
        company_index_map[cid] = 100 - sub_dev - sub_lf - sub_costtl - sub_thdu - sub_ubl
    return company_index_map
