"""爆管定位主模块。"""

import copy
import math
import os
import sys
from datetime import datetime
from time import perf_counter

import networkx as nx
import numpy as np
import pandas as pd

from .leak_simulator import cal_signature_pipe_multi_pf
from .network_partitioner import (
    cal_group_num,
    metis_grouping_pipe_weight,
    visualize_metis_partition,
)
from .similarity_calculator import (
    adjust_ratio,
    cal_similarity_all_multi_new_sq_improve_double_lzr,
    decode_mode,
    extra_judge,
    update_similarity,
)


def _ensure_signatures_for_centers(
    wn,
    wn_inp_path,
    center_list,  # 本轮要用到的中心（list[str]）
    pressure_leak_all,
    flow_leak_all,  # 全量缓存（可为空 DF）
    timestep_list,  # 你现有的时序列表
    pressure_monitor,
    flow_monitor,  # 用来推断传感器列名
    leak_mag,
    n_workers=1,
):
    """
    只为缺失的中心补算 SLF（调用你现有的 cal_signature_pipe_multi_pf），
    并把补算结果并回缓存。返回：
      pressure_leak_subset, flow_leak_subset, pressure_leak_all_new, flow_leak_all_new
    其中 subset 只包含 center_list 的行（顺序与 center_list 保持一致）。
    """

    center_list = _dedupe_preserve_order(center_list)

    # 1) 推断传感器列名（与现有数据保持一致）
    sensor_name_all = list(pressure_monitor.columns)
    sensor_f_name_all = (
        list(flow_monitor.columns)
        if (flow_monitor is not None and hasattr(flow_monitor, "columns"))
        else []
    )

    # 2) 取出缓存里已经有的中心（考虑 MultiIndex 的第 0 层为 pipe）
    def _existing_pipes(df):
        if df is None or len(df) == 0:
            return set()
        idx = df.index
        if isinstance(idx, pd.MultiIndex):
            return set(idx.get_level_values(0))
        else:
            return set(idx)

    exist_p = _existing_pipes(pressure_leak_all)
    need = [p for p in center_list if p not in exist_p]

    # 3) 若有缺失中心，仅为这些中心补算一次
    if len(need) > 0:
        p_new, _ = cal_signature_pipe_multi_pf(
            wn,
            leak_mag,
            need,
            timestep_list,
            sensor_name_all,
            n_workers=n_workers,
            wn_inp_path=wn_inp_path,
        )
        # 初始化空缓存时，做一次“同构化”
        if pressure_leak_all is None or len(pressure_leak_all) == 0:
            pressure_leak_all = p_new
        else:
            pressure_leak_all = pd.concat([pressure_leak_all, p_new], axis=0)
        # if (flow_leak_all is None or len(flow_leak_all) == 0) and f_new is not None:
        #     flow_leak_all = f_new
        # elif f_new is not None:
        #     flow_leak_all = pd.concat([flow_leak_all, f_new], axis=0)

        # 去重（如果既有缓存里不小心有重复中心）
        if isinstance(pressure_leak_all.index, pd.MultiIndex):
            pressure_leak_all = pressure_leak_all[
                ~pressure_leak_all.index.duplicated(keep="last")
            ]
            if flow_leak_all is not None and len(flow_leak_all) > 0:
                flow_leak_all = flow_leak_all[
                    ~flow_leak_all.index.duplicated(keep="last")
                ]
        else:
            pressure_leak_all = pressure_leak_all[
                ~pressure_leak_all.index.duplicated(keep="last")
            ]
            if flow_leak_all is not None and len(flow_leak_all) > 0:
                flow_leak_all = flow_leak_all[
                    ~flow_leak_all.index.duplicated(keep="last")
                ]

    # 4) 从更新后的缓存里，取出这轮需要的中心子集（顺序与 center_list 一致）
    if isinstance(pressure_leak_all.index, pd.MultiIndex):
        pressure_subset = pressure_leak_all.loc[center_list]
        flow_subset = (
            flow_leak_all.loc[center_list]
            if (flow_leak_all is not None and len(flow_leak_all) > 0)
            else None
        )
    else:
        pressure_subset = pressure_leak_all.loc[center_list, :]
        flow_subset = (
            flow_leak_all.loc[center_list, :]
            if (flow_leak_all is not None and len(flow_leak_all) > 0)
            else None
        )

    return pressure_subset, flow_subset, pressure_leak_all, flow_leak_all


def area_output_num_ki_improve(
    candidate_center,
    candidate_group,
    similarity,
    new_all_node,
    top_group_ratio,
    top_pipe_num_max,
    top_pipe_num_min,
    cut_ratio,
):
    final_area = []
    final_center = []
    all_node_iter = []
    if similarity.index.is_unique == False:
        total_center_num = len(set(similarity.index))
    else:
        total_center_num = len(similarity.index)

    next_group_num = min(
        total_center_num, math.ceil(total_center_num / cut_ratio * top_group_ratio)
    )

    for i in range(next_group_num):
        top_center = similarity.index[i]
        top_center_index = find_list_repeat(candidate_center, top_center)
        for j in range(len(top_center_index)):
            final_area = final_area + candidate_group[top_center_index[j]]
            all_node_iter = all_node_iter + list(new_all_node[top_center_index[j]])
        final_center.append(top_center)
        final_area = sorted(set(final_area))

    if len(final_area) > top_pipe_num_max:
        if_end = 0
    elif len(final_area) > top_pipe_num_min:
        if_end = 1
    elif total_center_num == next_group_num:
        if_end = 1
    else:
        if_end = 1
        for i in np.arange(next_group_num, total_center_num, 1):

            before_list = copy.deepcopy(final_area)
            top_center = similarity.index[i]
            top_center_index = candidate_center.index(top_center)
            temp_group = final_area + candidate_group[top_center_index]
            temp_area = sorted(set(temp_group))

            if len(temp_area) < top_pipe_num_min:

                final_center.append(top_center)
                all_node_iter = all_node_iter + list(new_all_node[top_center_index])
                final_area = temp_area

            elif len(temp_area) < top_pipe_num_max:
                final_center.append(top_center)
                all_node_iter = all_node_iter + list(new_all_node[top_center_index])
                final_area = temp_area
                break
            else:
                a = len(temp_area) - top_pipe_num_max
                b = top_pipe_num_min - len(before_list)

                if a >= b:
                    final_area = before_list
                else:
                    final_center.append(top_center)
                    all_node_iter = all_node_iter + list(new_all_node[top_center_index])
                    final_area = temp_area
                break

    final_center = sorted(set(final_center))
    all_node_iter = sorted(set(all_node_iter))
    return final_area, final_center, all_node_iter, if_end


def find_list_repeat(candidate_center, target):
    repeated_list = []
    for index, nums in enumerate(candidate_center):
        if nums == target:
            repeated_list.append(index)
    return repeated_list


def _dedupe_preserve_order(items):
    seen = set()
    output = []
    for item in items:
        if item in seen:
            continue
        seen.add(item)
        output.append(item)
    return output


def _accumulate_stage(stage_timing, stage_name, started_at):
    stage_timing[stage_name] = stage_timing.get(stage_name, 0.0) + (
        perf_counter() - started_at
    )


def _write_last_round_candidates_csv(
    csv_path,
    exit_condition,
    iteration_count,
    similarity_mode,
    candidate_details,
    fallback_similarity,
):
    if not csv_path:
        return None
    timestamp_suffix = datetime.now().strftime("%Y%m%d_%H%M%S")
    base_path, ext = os.path.splitext(csv_path)
    ext = ext or ".csv"
    output_path = f"{base_path}_{timestamp_suffix}{ext}"
    if candidate_details is not None and len(candidate_details) > 0:
        export_df = candidate_details.copy()
        if export_df.index.name == "pipe_id":
            export_df = export_df.reset_index()
    else:
        export_df = pd.DataFrame(
            {
                "pipe_id": [str(pipe_id) for pipe_id in fallback_similarity.index],
                "final_similarity": [float(value) for value in fallback_similarity.values],
            }
        )
    export_df["exit_condition"] = exit_condition
    export_df["iterations"] = int(iteration_count)
    export_df["similarity_mode"] = similarity_mode
    parent_dir = os.path.dirname(output_path)
    if parent_dir:
        os.makedirs(parent_dir, exist_ok=True)
    export_df.to_csv(output_path, index=False, encoding="utf-8-sig")
    return output_path


def cal_DtoTop1(
    G0, pipe_leak, located_pipe, pipe_start_node_all, pipe_end_node_all, pipe_length
):
    if pipe_leak == located_pipe:
        result_DtoTop1 = 0
        result_DtoTop1_num = 0
    else:
        pipe_leak_start_node = pipe_start_node_all[pipe_leak]
        pipe_leak_end_node = pipe_end_node_all[pipe_leak]
        located_pipe_start_node = pipe_start_node_all[located_pipe]
        located_pipe_end_node = pipe_end_node_all[located_pipe]
        DtoTop1_series = pd.Series(dtype=object)
        DtoTop1_num_series = pd.Series(dtype=object)
        DtoTop1_series["ss"] = nx.shortest_path_length(
            G0, pipe_leak_start_node, located_pipe_start_node, weight="weight"
        )
        DtoTop1_series["se"] = nx.shortest_path_length(
            G0, pipe_leak_start_node, located_pipe_end_node, weight="weight"
        )
        DtoTop1_series["es"] = nx.shortest_path_length(
            G0, pipe_leak_end_node, located_pipe_start_node, weight="weight"
        )
        DtoTop1_series["ee"] = nx.shortest_path_length(
            G0, pipe_leak_end_node, located_pipe_end_node, weight="weight"
        )
        DtoTop1_num_series["ss"] = nx.shortest_path_length(
            G0, pipe_leak_start_node, located_pipe_start_node
        )
        DtoTop1_num_series["se"] = nx.shortest_path_length(
            G0, pipe_leak_start_node, located_pipe_end_node
        )
        DtoTop1_num_series["es"] = nx.shortest_path_length(
            G0, pipe_leak_end_node, located_pipe_start_node
        )
        DtoTop1_num_series["ee"] = nx.shortest_path_length(
            G0, pipe_leak_end_node, located_pipe_end_node
        )

        if DtoTop1_num_series.min() == 0:
            result_DtoTop1_num = 1
            result_DtoTop1 = DtoTop1_series.max() / 2
        else:
            result_DtoTop1_num = DtoTop1_num_series.min() + 1
            DtoTop1_type = DtoTop1_series.argmin()
            result_DtoTop1 = (
                DtoTop1_series[DtoTop1_type]
                + (pipe_length[pipe_leak] + pipe_length[located_pipe]) / 2
            )
    return result_DtoTop1, result_DtoTop1_num


def cal_RR(located_pipe, similarity_sp):
    if located_pipe in similarity_sp.index:
        rank = similarity_sp.index.get_loc(located_pipe)
        RR = rank / len(similarity_sp.index)
    else:
        RR = 1.1
    return RR


def cal_cover(similarity, leak_pipe):
    if leak_pipe in list(similarity.index):
        cover = 1
    else:
        cover = 0
    return cover


def cal_SD(located_pipe, real_pipe, pipe_x, pipe_y):
    dx = pipe_x[located_pipe] - pipe_x[real_pipe]
    dy = pipe_y[located_pipe] - pipe_y[real_pipe]
    SD = math.sqrt(dx * dx + dy * dy)
    return SD


def DN_search_multi_simple_add_flow_count_new(
    wn,
    wn_inp_path,
    G0,
    all_node,
    node_x,
    node_y,
    pipe_start_node_all,
    pipe_end_node_all,
    pipe_diameter,
    couple_node_length,
    node_pipe_dic,
    all_node_series,
    top_group_ratio,
    top_pipe_num_max,
    top_pipe_num_min,
    candidate_pipe_input_initial,
    similarity_mode,
    pressure_monitor,
    pressure_predict,
    pressure_normal,
    pressure_leak_all,
    flow_monitor,
    flow_predict,
    flow_normal,
    flow_leak_all,
    timestep_list,
    max_flow,
    group_basic_num,
    Top_sensor_num,
    if_gy,
    pressure_threshold,
    leak_mag,
    n_workers=1,
    stage_timing=None,
    partition_on_full_graph=True,
    visualize_partition=False,
    visualize_pause_seconds=0.3,
    final_candidates_csv_path=None,
):
    if stage_timing is None:
        stage_timing = {}
    exit_condition = "unknown"
    final_candidates_csv = None
    iter_count = 0
    all_node_iter = copy.deepcopy(all_node)
    candidate_pipe_input = copy.deepcopy(candidate_pipe_input_initial)  # 可能漏损管段
    t1 = datetime.now()
    if_flow, if_only_cos, if_only_flow = decode_mode(similarity_mode)  # 定位方法
    # threshold
    if if_only_flow == 1:
        dpressure = (flow_predict - flow_monitor).mean()
        dpressure = dpressure.abs()
        effective_sensor = list(dpressure.index)

    else:
        dpressure = (pressure_predict - pressure_monitor).mean()
        dpressure = dpressure.abs()
        dpressure = dpressure[dpressure > pressure_threshold]
        effective_sensor = list(dpressure.index)
    simulation_times = 0  # 模拟次数
    if len(dpressure) > 0:
        break_flag = 0
        last_round_candidate_details = None

        cos_h = 0
        dis_h = 0
        dis_f_h = 0
        if_compalsive = 0
        record_center_dataset = []
        record_center_set = set()
        # iter
        while 1:
            final_area = []
            final_center = []
            group_num = cal_group_num(candidate_pipe_input, group_basic_num)
            partition_nodes = all_node if partition_on_full_graph else all_node_iter

            # group 分组，得出候选漏损中心
            stage_start = perf_counter()
            (
                candidate_center_list,
                candidate_group_list,
                new_all_node,
                candidate_center_candidates,
            ) = (
                metis_grouping_pipe_weight(
                    G0,
                    wn,
                    partition_nodes,
                    candidate_pipe_input,
                    group_num,
                    node_x,
                    node_y,
                    pipe_start_node_all,
                    pipe_end_node_all,
                    node_pipe_dic,
                    all_node_series,
                    couple_node_length,
                    pipe_diameter,
                )
            )
            _accumulate_stage(stage_timing, "group_partitioning", stage_start)
            if visualize_partition:
                visualize_metis_partition(
                    G0,
                    candidate_center_list,
                    candidate_group_list,
                    node_x,
                    node_y,
                    pipe_start_node_all,
                    pipe_end_node_all,
                    title=(
                        f"METIS Partition Iteration {iter_count + 1} | "
                        f"candidate pipes={len(candidate_pipe_input)} "
                        f"groups={len(candidate_group_list)}"
                    ),
                    block=False,
                    pause_seconds=visualize_pause_seconds,
                )
            simulation_times = simulation_times + len(candidate_center_list)
            # pick_pressure_leak
            # pressure_leak = pressure_leak_all.loc[candidate_center_list].loc[:, :]
            # flow_leak = flow_leak_all.loc[candidate_center_list].loc[:, :]
            # —— 新增泄漏量（保持你现在的一致，或从外部传入）——
            # —— 只为缺失中心补算，然后取本轮需要的中心子集 ——
            stage_start = perf_counter()
            pressure_leak, flow_leak, pressure_leak_all, flow_leak_all = (
                _ensure_signatures_for_centers(
                    wn=wn,
                    wn_inp_path=wn_inp_path,
                    center_list=candidate_center_list,
                    pressure_leak_all=pressure_leak_all,
                    flow_leak_all=flow_leak_all,
                    timestep_list=timestep_list,
                    pressure_monitor=pressure_monitor,
                    flow_monitor=flow_monitor,
                    leak_mag=leak_mag,
                    n_workers=n_workers,
                )
            )
            _accumulate_stage(stage_timing, "signature_for_candidates", stage_start)

            # pressure_leak_f= pressure_leak.swaplevel()

            # --------------------------------------------------------

            add_center = []
            leak_center_dict = dict()
            for i in range(len(candidate_center_list)):
                primary_center = candidate_center_list[i]
                houxuan_center = [
                    center
                    for center in candidate_center_candidates[i]
                    if center != primary_center
                ]
                candidate_group_set = set(candidate_group_list[i])
                for each_center in record_center_dataset:
                    if (
                        each_center in candidate_group_set
                        and each_center != primary_center
                    ):
                        houxuan_center.append(each_center)
                add_center = add_center + houxuan_center
                leak_center_dict[primary_center] = _dedupe_preserve_order(
                    houxuan_center + [primary_center]
                )
            add_center = _dedupe_preserve_order(add_center)
            for each_group_centers in candidate_center_candidates:
                for each_center in each_group_centers:
                    if each_center not in record_center_set:
                        record_center_dataset.append(each_center)
                        record_center_set.add(each_center)
            for each_center in add_center:
                if each_center not in record_center_set:
                    record_center_dataset.append(each_center)
                    record_center_set.add(each_center)

            # --------------------------------------------------------
            # --------------------------------------------------------
            # if len(add_center) > 0:
            #     s3 = pressure_leak_all.loc[add_center]
            #     pressure_leak = pd.concat([pressure_leak, s3])
            #     s4 = flow_leak_all.loc[add_center]
            #     flow_leak = pd.concat([flow_leak, s4])
            # --------------------------------------------------------
            # 只为 add_center 里还没算过的中心补算，并与本轮中心合并
            if len(add_center) > 0:
                stage_start = perf_counter()
                pressure_add, flow_add, pressure_leak_all, flow_leak_all = (
                    _ensure_signatures_for_centers(
                        wn=wn,
                        wn_inp_path=wn_inp_path,
                        center_list=add_center,
                        pressure_leak_all=pressure_leak_all,
                        flow_leak_all=flow_leak_all,
                        timestep_list=timestep_list,
                        pressure_monitor=pressure_monitor,
                        flow_monitor=flow_monitor,
                        leak_mag=leak_mag,  # 与上面一致
                        n_workers=n_workers,
                    )
                )
                _accumulate_stage(
                    stage_timing, "signature_for_extra_centers", stage_start
                )
                pressure_leak = pd.concat([pressure_leak, pressure_add], axis=0)
                if (flow_leak is not None) and (flow_add is not None):
                    flow_leak = pd.concat([flow_leak, flow_add], axis=0)
            # --------------------------------------------------------
            #
            if len(candidate_pipe_input) < 1.2 * top_pipe_num_max / top_group_ratio:
                if_compalsive = 1
            cos_h, dis_h, dis_f_h = adjust_ratio(similarity_mode, cos_h, dis_h, dis_f_h)
            candidate_center_list_sup = _dedupe_preserve_order(
                candidate_center_list + add_center
            )
            stage_start = perf_counter()
            similarity, cos_h, dis_h, dis_f_h, break_flag, similarity_details = (
                cal_similarity_all_multi_new_sq_improve_double_lzr(
                    candidate_center_list_sup,
                    similarity_mode,
                    pressure_leak,
                    pressure_monitor,
                    pressure_predict,
                    pressure_normal,
                    if_flow,
                    if_only_cos,
                    if_only_flow,
                    flow_leak,
                    flow_monitor,
                    flow_predict,
                    flow_normal,
                    timestep_list,
                    Top_sensor_num,
                    if_gy,
                    effective_sensor,
                    cos_h,
                    dis_h,
                    dis_f_h,
                    if_compalsive,
                    max_flow,
                )
            )
            last_round_candidate_details = similarity_details
            _accumulate_stage(stage_timing, "similarity_ranking", stage_start)
            if break_flag == 1:
                exit_condition = "similarity_break_flag"
                break

            new_similarity = update_similarity(
                candidate_center_list, similarity, leak_center_dict
            )
            if len(candidate_pipe_input) > top_pipe_num_max / top_group_ratio:
                cut_ratio, new_similarity = extra_judge(new_similarity)
            else:
                cut_ratio = 1
            stage_start = perf_counter()
            final_area_t, final_center_t, all_node_new_1, if_end = (
                area_output_num_ki_improve(
                    candidate_center_list,
                    candidate_group_list,
                    new_similarity,
                    new_all_node,
                    top_group_ratio,
                    top_pipe_num_max,
                    top_pipe_num_min,
                    cut_ratio,
                )
            )
            _accumulate_stage(stage_timing, "candidate_area_selection", stage_start)
            final_area = final_area + final_area_t
            final_center = final_center + final_center_t

            final_area = sorted(set(final_area))
            final_center = sorted(set(final_center))
            if if_end == 1:
                exit_condition = "candidate_area_if_end"
                break
            elif len(candidate_pipe_input) == len(final_area):
                exit_condition = "candidate_size_no_change"
                break
            else:
                candidate_pipe_input = final_area
                if not partition_on_full_graph:
                    all_node_iter = all_node_new_1
                iter_count += 1
                sys.stdout.write(
                    "\r"
                    + "已经完成"
                    + str(iter_count)
                    + "次迭代计算"
                    + "候选节点"
                    + str(len(final_area))
                    + "个"
                )
        # if break_flag == 0:
        #     final_area_pipe = copy.deepcopy(final_area)
        #     simulation_times = simulation_times + len(final_area)
        #     pressure_leak_sp = pressure_leak_all.loc[final_area_pipe].loc[:, :]
        #     flow_leak_sp = flow_leak_all.loc[final_area_pipe].loc[:, :]
        #     similarity_sp, cos_h, dis_h, dis_f_h, break_flag = cal_similarity_all_multi_new_sq_improve_double_lzr(
        #         final_area_pipe, similarity_mode, pressure_leak_sp,
        #         pressure_monitor, pressure_predict, pressure_normal, if_flow,
        #         if_only_cos, if_only_flow,
        #         flow_leak_sp, flow_monitor, flow_predict, flow_normal,
        #         timestep_list, Top_sensor_num, if_gy, effective_sensor, cos_h, dis_h, dis_f_h, if_compalsive, max_flow)
        if break_flag == 0:
            final_area_pipe = list(final_area)  # 确保是 list

            # 只为还没算过的管段补齐 SLF（按需计算）
            stage_start = perf_counter()
            pressure_leak_sp, flow_leak_sp, pressure_leak_all, flow_leak_all = (
                _ensure_signatures_for_centers(
                    wn=wn,
                    wn_inp_path=wn_inp_path,
                    center_list=final_area_pipe,  # 这次要用的“最终区域里的所有管段”
                    pressure_leak_all=pressure_leak_all,  # 累积缓存（会被更新）
                    flow_leak_all=flow_leak_all,
                    timestep_list=timestep_list,
                    pressure_monitor=pressure_monitor,
                    flow_monitor=flow_monitor,
                    leak_mag=leak_mag,
                    n_workers=n_workers,
                )
            )
            _accumulate_stage(stage_timing, "signature_for_final_area", stage_start)

            # 如果你要精确统计模拟次数，这里可以加上“本次新补的数量”，
            # 做法：让 _ensure_signatures_for_centers 额外返回 need_cnt，再 simulation_times += need_cnt

            stage_start = perf_counter()
            (
                similarity_sp,
                cos_h,
                dis_h,
                dis_f_h,
                break_flag,
                similarity_details,
            ) = (
                cal_similarity_all_multi_new_sq_improve_double_lzr(
                    final_area_pipe,
                    similarity_mode,
                    pressure_leak_sp,
                    pressure_monitor,
                    pressure_predict,
                    pressure_normal,
                    if_flow,
                    if_only_cos,
                    if_only_flow,
                    flow_leak_sp,
                    flow_monitor,
                    flow_predict,
                    flow_normal,
                    timestep_list,
                    Top_sensor_num,
                    if_gy,
                    effective_sensor,
                    cos_h,
                    dis_h,
                    dis_f_h,
                    if_compalsive,
                    max_flow,
                )
            )
            last_round_candidate_details = similarity_details
            _accumulate_stage(stage_timing, "similarity_final", stage_start)

        else:
            dpressure = (pressure_predict - pressure_monitor).mean()
            dpressure = dpressure.abs()
            simulation_times = simulation_times + len(dpressure.index)
            similarity_sp = pd.Series(dtype=float)
            for each_node in dpressure.index:
                pipe = node_pipe_dic[each_node][0]
                similarity_sp.loc[pipe] = dpressure.loc[each_node]
            similarity_sp = similarity_sp.sort_values(ascending=False, kind="mergesort")
            t2 = datetime.now()
            final_area_pipe = []

        sys.stdout.write(
            "\r"
            + "已经完成"
            + str(iter_count + 1)
            + "次迭代计算"
            + "候选节点"
            + str(len(final_area_pipe))
            + "个"
        )
        t2 = datetime.now()
        dt = (t2 - t1).seconds
        final_candidates_csv = _write_last_round_candidates_csv(
            csv_path=final_candidates_csv_path,
            exit_condition=exit_condition,
            iteration_count=iter_count + 1,
            similarity_mode=similarity_mode,
            candidate_details=last_round_candidate_details,
            fallback_similarity=similarity_sp,
        )
    else:
        exit_condition = "no_effective_sensor_after_threshold"
        dpressure = (pressure_predict - pressure_monitor).mean()
        dpressure = dpressure.abs()

        similarity_sp = pd.Series(dtype=float)
        for each_node in dpressure.index:
            pipe = node_pipe_dic[each_node][0]
            similarity_sp.loc[pipe] = dpressure.loc[each_node]
        similarity_sp = similarity_sp.sort_values(ascending=False, kind="mergesort")
        t2 = datetime.now()
        dt = (t2 - t1).seconds
        final_candidates_csv = _write_last_round_candidates_csv(
            csv_path=final_candidates_csv_path,
            exit_condition=exit_condition,
            iteration_count=0,
            similarity_mode=similarity_mode,
            candidate_details=None,
            fallback_similarity=similarity_sp,
        )
    stage_timing["iterations"] = iter_count + 1 if len(dpressure) > 0 else 0
    stage_timing["total_elapsed_seconds"] = float(dt)
    stage_timing["exit_condition"] = exit_condition
    stage_timing["final_candidates_csv"] = final_candidates_csv
    return (
        similarity_sp.index[0],
        dt,
        simulation_times,
        wn,
        similarity_sp,
        exit_condition,
        final_candidates_csv,
    )


