import time

import polars as pl
from pathlib import Path
from yangke.performance.iapws97 import H_PT, T_P
from yangke.sis.BeiJingShenHua.data_pre import 过滤数据, 分段保存Excel文件, filter_data

# folder = Path(r"D:\国华燃机")


# folder = Path(r"C:\Users\YangKe\Desktop\冷端优化历史数据")
# folder = Path(r"C:\Users\54067\Desktop\冷端优化历史数据")
folder = Path(r"C:\wps同步\高安屯冷端历史")


def load_single_csv(file_path):
    """
    加载csv文件，忽略文件第一样，将第二行作为列标题，第一列的标题重命名为DateTime，格式为1/2/024 0:00
    """
    # 尝试使用不同的编码格式
    encodings = ['utf-8', 'gbk', 'gb2312', 'latin1']

    for encoding in encodings:
        try:
            # 先读取前两行来获取列标题，但不推断数据类型
            temp_df = pl.read_csv(
                file_path,
                skip_rows=1,  # 跳过第一行
                has_header=True,  # 第二行作为标题
                infer_schema_length=0,  # 不推断数据类型
                encoding=encoding
            )

            # 获取原始列名
            original_columns = temp_df.columns

            # 创建 schema_overrides 字典，指定所有非时间列为 Float64
            schema_dict = {col: pl.Float64 for col in original_columns[1:]}  # 从第二列开始设为Float64

            # 将第一列也设为字符串类型（时间列），后面再处理
            if original_columns:
                schema_dict[original_columns[0]] = pl.Utf8

                # 重新读取整个文件，应用类型约束
                df = pl.read_csv(
                    file_path,
                    skip_rows=1,  # 跳过第一行
                    has_header=True,  # 第二行作为标题
                    infer_schema_length=0,  # 不推断数据类型
                    schema_overrides=schema_dict,
                    encoding=encoding
                )

                # 将第一列重命名为DateTime
                rename_dict = {original_columns[0]: "DateTime"}
                df = df.rename(rename_dict)

                # 尝试将DateTime列转换为datetime类型
                # 定义多种可能的时间格式
                datetime_formats = [
                    "%m/%d/%Y %H:%M",  # 1/2/2024 0:00 格式
                    "%m/%d/%y %H:%M",  # 1/2/024 0:00 格式
                    "%Y/%m/%d %H:%M",
                    "%m/%d/%Y",
                    "%Y-%m-%d %H:%M:%S",
                    "%Y-%m-%d %H:%M",
                    "%Y-%m-%d",
                    "%d/%m/%Y %H:%M",
                    "%d/%m/%Y",
                    "%m/%d/%y %H:%M:%S",
                    "%m-%d-%Y %H:%M",
                    "%Y/%m/%d %H:%M:%S.%f"
                ]

                # 逐个尝试时间格式转换
                datetime_converted = False
                for fmt in datetime_formats:
                    try:
                        df = df.with_columns([
                            pl.col("DateTime").str.strptime(pl.Datetime, fmt, strict=False).alias("DateTime")
                        ])
                        # 检查是否成功转换（没有空值）
                        if not df.select(pl.col("DateTime").is_null().any()).item():
                            datetime_converted = True
                            break
                    except:
                        continue

                # 如果所有格式都失败，保持字符串格式并警告
                if not datetime_converted:
                    print(f"警告: 无法将时间列转换为datetime格式，保持为字符串格式。文件: {file_path}")

            else:
                raise ValueError("文件中没有列")

            print(f"成功使用 {encoding} 编码读取文件: {file_path}")
            return df
        except Exception as e:
            print(f"使用 {encoding} 编码失败: {e}")
            continue

    raise ValueError(f"无法使用常见编码格式读取文件: {file_path}")


def load_csv(file_path, datetime_formats=None):
    """
    加载csv数据，第一列为日期列，后续列均为float列，列标题为测点的KKS码
    """
    # 尝试使用不同的编码格式
    encodings = ['utf-8', 'gbk', 'gb2312', 'latin1']

    for encoding in encodings:
        try:
            # 先读取获取列信息
            temp_df = pl.read_csv(
                file_path,
                infer_schema_length=0,
                encoding=encoding  # 指定编码
            )

            # 创建 schema_overrides 字典，指定所有数值列为 Float64
            schema_dict = {col: pl.Float64 for col in temp_df.columns[1:]}

            # 如果指定了 datetime_formats，则为时间列设置特殊处理
            if datetime_formats:
                # 显式指定时间列类型为字符串，后面再转换
                time_col = temp_df.columns[0]
                schema_dict[time_col] = pl.Utf8
                df_csv1 = pl.read_csv(
                    file_path,
                    infer_schema_length=0,
                    schema_overrides=schema_dict,
                    encoding=encoding,
                    try_parse_dates=False  # 不自动解析日期
                )

                # 尝试多种时间格式解析
                if isinstance(datetime_formats, list):
                    formats_to_try = datetime_formats
                else:
                    formats_to_try = [datetime_formats]

                # 添加常见的备用格式
                formats_to_try.extend(["%Y/%m/%d", "%Y-%m-%d", "%m/%d/%Y %H:%M:%S"])

                # 逐个尝试格式直到成功
                parsed_successfully = False
                for fmt in formats_to_try:
                    try:
                        df_csv1 = df_csv1.with_columns(
                            pl.col(time_col).str.strptime(pl.Datetime, format=fmt, strict=False)
                        )
                        parsed_successfully = True
                        break
                    except:
                        continue

                if not parsed_successfully:
                    # 如果所有格式都失败，使用最宽松的解析方式
                    df_csv1 = df_csv1.with_columns(
                        pl.col(time_col).str.to_datetime(strict=False)
                    )
            else:
                df_csv1 = pl.read_csv(
                    file_path,
                    infer_schema_length=0,
                    schema_overrides=schema_dict,
                    encoding=encoding,
                    try_parse_dates=True
                )
                # 将时间列统一转换为字符串类型以保证一致性
                time_col = df_csv1.columns[0]
                df_csv1 = df_csv1.with_columns(
                    pl.col(time_col).cast(pl.Utf8)
                )

            print(f"成功使用 {encoding} 编码读取文件: {file_path}")
            return df_csv1
        except Exception as e:
            print(f"使用 {encoding} 编码失败: {e}")
            continue

    raise ValueError(f"无法使用常见编码格式读取文件: {file_path}")


def merge_dataframes(df1, df2):
    """
    合并两个DataFrame，基于时间列进行inner join，并处理重复列名
    """
    # 获取时间列名（假设为第一列）
    time_column = df1.columns[0]

    # 检查除了时间列外是否有重复的列名
    cols1 = set(df1.columns[1:])
    cols2 = set(df2.columns[1:])
    common_columns = cols1.intersection(cols2)

    if common_columns:
        print(f"发现重复的列名: {common_columns}，将在df2中移除这些列")
        # 从df2中删除与df1重复的列（除了时间列）
        cols_to_keep_df2 = [col for col in df2.columns if col == time_column or col not in common_columns]
        df2_filtered = df2.select(cols_to_keep_df2)
    else:
        df2_filtered = df2

    # 使用inner join合并，只保留两个df都有的时刻
    merged_df = df1.join(df2_filtered, on=time_column, how='inner')

    return merged_df


def 加载点表(file_path):
    """
    加载点名文件，点表文件中包含两列数据，列名为测点描述的是点描述，列名为KKS码的就是测点对应的KKS码的值
    """
    point_table = pl.read_excel(file_path)

    # 过滤掉空列和其他无关列，只保留需要的两列
    required_columns = ["KKS码", "测点描述"]
    available_columns = [col for col in required_columns if col in point_table.columns]

    if len(available_columns) != 2:
        raise ValueError(f"点表文件缺少必要列，期望列: {required_columns}, 实际可用列: {available_columns}")

    # 只选择需要的列并去除空行
    point_table = point_table.select(available_columns).drop_nulls()

    return point_table


def load_all_data():
    """
    加载所有数据，这个不是通用方法，每个项目的数据文件数量，目录都不相同，需要根据需要修改
    """
    # save_file = folder / "合并后的历史数据.csv"
    # if Path(save_file).exists():
    #     print(f"已存在文件{save_file}，直接加载数据")
    #     return pl.read_csv(save_file)
    #     # 为所有文件提供多个可能的时间格式
    # common_formats = ["%Y/%m/%d %H:%M", "%Y/%m/%d"]
    #
    # df1 = load_csv(folder / "导数1.csv", datetime_formats=common_formats)
    # df2 = load_csv(folder / "导数2.csv", datetime_formats=common_formats)
    # df3 = load_csv(folder / "导数3.csv", datetime_formats=common_formats)
    # # 对于导数4.csv，传入特定的时间格式
    # df4 = load_csv(folder / "导数4.csv", datetime_formats=["%m/%d/%Y %H:%M:%S"])

    # # 合并数据
    # merged_df = merge_dataframes(df1, df2)
    # merged_df = merge_dataframes(merged_df, df3)
    # merged_df = merge_dataframes(merged_df, df4)
    #
    # # 加载点表信息
    # point_table = 加载点表(folder / "冷端历史点表.xlsx")
    #
    # # 创建KKS码到点描述的映射字典
    # kks_to_description = dict(zip(point_table["KKS码"], point_table["测点描述"]))
    #
    # # 更新列名，将KKS码替换为点描述
    # new_column_names = []
    # for col in merged_df.columns:
    #     # 第一列是时间列，重命名为DateTime
    #     if col == merged_df.columns[0]:
    #         new_column_names.append("DateTime")
    #     else:
    #         # 查找对应的点描述，如果找不到则保持原KKS码
    #         description = kks_to_description.get(col, col)
    #         new_column_names.append(description)
    #
    # names = dict(zip(merged_df.columns, new_column_names))
    # # 重命名列
    # merged_df = merged_df.rename(names)
    # merged_df.write_csv(save_file)
    # return merged_df

    merged_df = load_single_csv(folder / "高安屯冷端历史.csv")
    return merged_df


def cal_enthalpy1(df_inner: pl.DataFrame, new_col_name, p_title, t_title):
    """
    计算蒸汽或水的焓值，该方法执行速度慢，Python调用开销大
    """
    # 创建缓存文件路径
    cache_file = folder / "cache" / f"{new_col_name}.parquet"
    cache_file.parent.mkdir(parents=True, exist_ok=True)  # 确保缓存目录存在

    # 如果缓存文件存在，直接加载
    if cache_file.exists():
        print(f"从缓存加载 {new_col_name}")
        cached_df = pl.read_parquet(cache_file)
        # 将缓存列添加到原数据框
        try:
            df_inner = df_inner.with_columns(cached_df[new_col_name])
            return df_inner
        except Exception as e:
            print(f"从缓存加载 {new_col_name} 失败: {e}")
            # 如果缓存文件存在但无法加载，则删除缓存文件并重新计算
            cache_file.unlink()

    # 如果缓存文件不存在，执行计算
    start_time = time.time()  # 记录开始时间

    df_inner = df_inner.with_columns(
        pl.struct([p_title, t_title])
        .map_elements(lambda x: H_PT(x[p_title], x[t_title]),
                      return_dtype=pl.Float64)
        .alias(new_col_name)
    )

    end_time = time.time()  # 记录结束时间
    execution_time = end_time - start_time  # 计算执行时间
    print(f"cal_enthalpy1函数执行时间: {execution_time:.4f} 秒，列名: {new_col_name}")

    # 保存计算结果到缓存文件
    cache_df = df_inner.select(["DateTime", new_col_name])  # 假设第一列是DateTime
    cache_df.write_parquet(cache_file)
    print(f"计算结果已缓存到 {cache_file}")

    return df_inner


def 计算凝汽器热负荷(df_inner: pl.DataFrame = None):
    """
    计算
    """
    # 使用H_PT函数计算焓值并添加新列
    # 注意: H_PT应该接受压力和温度作为参数，返回焓值
    df_inner = df_inner.with_columns(
        (pl.col("高压缸进汽压力") + pl.col("平均大气压力"))
        .alias("高压缸进汽压力绝压")
    )
    df_inner = df_inner.with_columns(
        (pl.col("1号锅炉高压主汽流量") + pl.col('2号锅炉高压主汽流量')).alias("高压缸进汽总流量"))
    df_inner = cal_enthalpy1(df_inner, "高压缸进汽焓", "高压缸进汽压力绝压", "高压缸进汽温度")
    df_inner = df_inner.with_columns((pl.col("高压缸进汽总流量") * pl.col("高压缸进汽焓")).alias("高压缸进汽能量"))

    # 计算平均凝结水焓
    df_inner = df_inner.with_columns(
        (pl.col("凝泵出口凝结水压力") + pl.col('平均大气压力')).alias("凝结水绝对压力")
    )
    df_inner = cal_enthalpy1(df_inner, "平均凝结水焓", "凝结水绝对压力",
                             "凝泵出口凝结水温度")

    # 因为高压部分吸热量是使用高压蒸汽流量计算的，高压蒸汽由两部分组成，一部分由凝结水焓经TCA+过热器到高压主汽，一部分完全由余热锅炉
    # 加热变成高压主汽，此处计算是总流量*总焓降，因此无需再单独计算TCA吸热量

    # 计算F_crh
    df_inner = df_inner.with_columns(
        (pl.col("再热蒸汽母管压力") + pl.col("平均大气压力"))
        .alias("再热蒸汽母管绝对压力")
    )
    df_inner = df_inner.with_columns(
        (pl.col("高排压力") + pl.col("平均大气压力")).alias("高排绝对压力")
    )
    df_inner = cal_enthalpy1(df_inner, "热再热蒸汽焓", "再热蒸汽母管绝对压力", "再热蒸汽母管温度")
    df_inner = cal_enthalpy1(df_inner, "高排焓", "高排绝对压力", "高排温度")
    df_inner = df_inner.with_columns(
        (pl.col("高排至1号炉流量") + pl.col("高排至2号炉流量"))
        .alias("F_crh")
    )
    df_inner = df_inner.with_columns(
        (pl.col("F_crh") * (pl.col("热再热蒸汽焓") - pl.col("高排焓")))
        .alias("冷再热蒸汽吸热量")
    )

    # 计算中压给水部分吸热量
    # 这里如果使用两台炉中压过热器出口流量之和计算，则还需要考虑中压减温水流量，如果使用热再热蒸汽流量-冷再热蒸汽流量，则不需要考虑中压减温水流量
    df_inner = df_inner.with_columns(
        (pl.col("1号炉再热蒸汽流量") + pl.col("1号炉再热蒸汽流量") - pl.col("F_crh"))
        .alias("F_is")
    )
    df_inner = df_inner.with_columns(
        (pl.col("F_is") * pl.col("热再热蒸汽焓"))
        .alias("F_is*H_hrh")
    )

    # 计算低压部分吸热量
    df_inner = df_inner.with_columns(
        (pl.col("低压主蒸汽压力(中排混合前)") + pl.col("平均大气压力"))
        .alias("低压主蒸汽绝对压力混合前")
    )
    df_inner = cal_enthalpy1(df_inner, "H_ls", "低压主蒸汽绝对压力混合前", "低压主蒸汽温度(中排混合前)")
    # df_inner = df_inner.with_columns(
    #     (pl.col("1号炉低过出口蒸汽流量") + pl.col("2号炉低过出口蒸汽流量"))
    #     .alias("F_ls")
    # )
    # 根据纯凝模式判断是否需要扣除热网加热器疏水
    df_inner = df_inner.with_columns(
        pl.when(pl.col("纯凝模式") > 0.5)  # 1表示纯凝模式
        .then(pl.col("1号炉低过出口蒸汽流量") + pl.col("2号炉低过出口蒸汽流量") - pl.col("12号热网加热器疏水") - pl.col(
            "34号热网加热器疏水"))  # 纯凝模式下，低压主汽=两台余热锅炉低压主汽之和-热网疏水
        .otherwise(  # 抽凝模式低压主汽流量=两台余热锅炉低压主汽之和
            pl.col("1号炉低过出口蒸汽流量") + pl.col("2号炉低过出口蒸汽流量")
        )
        .alias("F_ls")
    )
    df_inner = df_inner.with_columns(
        (pl.col("F_ls") * pl.col("H_ls"))
        .alias("低压蒸汽能量")
    )

    df_inner = df_inner.with_columns(  # 发电机效率为99%，机械损耗0.607MW
        (3600 * (pl.col("汽轮发电机功率") / 0.99 + 0.607))
        .alias("汽机轴功率当量热量")  # 单位为MJ/h
    )

    # 烟气加热器供热量，烟气加热器位于低省出口再循环管道上，利用再循环水加热热网水。
    # 修改计算方法：根据纯凝模式判断是否需要减去汽机抽汽供热量
    df_inner = df_inner.with_columns(
        pl.when(pl.col("纯凝模式") > 0.5)  # 纯凝模式，>0.5就是1
        .then(  # 纯凝模式没有抽汽，厂里有的抽汽是从余热锅炉出口直接抽走的，不计入汽轮机系统内
            pl.col("高压缸进汽能量") + pl.col("冷再热蒸汽吸热量") + pl.col("F_is*H_hrh") + pl.col("低压蒸汽能量")
            - pl.col("汽机轴功率当量热量")
        )
        .otherwise(  # 抽凝模式，汽机抽汽供热量由汽机系统内抽走，需要扣除
            pl.col("高压缸进汽能量") + pl.col("冷再热蒸汽吸热量") + pl.col("F_is*H_hrh") + pl.col("低压蒸汽能量")
            - pl.col("汽机抽汽供热量") * 1000 - pl.col("汽机轴功率当量热量")
        )
        .alias("低压缸排汽能量")
    )

    df_inner = cal_enthalpy1(df_inner, "补水焓", "凝结水补水压力", "凝结水补水温度")  # 考虑到水焓，就不修压力了
    df_inner = df_inner.with_columns(
        (pl.col("高压缸进汽总流量") + pl.col("F_is") + pl.col("F_ls") + pl.col("凝结水补水流量"))
        .alias("F_con")  # 这里不能再加冷再热部分流量，因为冷再热的流量已经在冷再热蒸汽吸热量里扣过了
    )

    df_inner = df_inner.with_columns(
        (pl.col("低压缸排汽能量") + pl.col("补水焓") * pl.col("凝结水补水流量") - pl.col("F_con") * pl.col(
            "平均凝结水焓")).alias("凝汽器热负荷")
    )

    df_inner = df_inner.with_columns(
        (pl.col("汽机循环水供水管道流量1") + pl.col("汽机循环水供水管道流量2")).alias("循环水流量")
    )
    # 计算循环水吸热量，等于比热乘以流量乘以温升，流量单位为t/h，温度单位为℃，吸热量单位为MJ/h
    df_inner = df_inner.with_columns(
        (4.2 * pl.col("汽机循环水供水管道流量1") * (pl.col("凝汽器回水温度1") - pl.col("凝汽器进水温度1"))).alias(
            "循环水吸热量1")
    )
    df_inner = df_inner.with_columns(
        (4.2 * pl.col("汽机循环水供水管道流量2") * (pl.col("凝汽器回水温度2") - pl.col("凝汽器进水温度2"))).alias(
            "循环水吸热量2")
    )
    df_inner = df_inner.with_columns(
        (pl.col("循环水吸热量1") + pl.col("循环水吸热量2")).alias("凝汽器热负荷(循环水计算)")
    )

    df_inner = df_inner.with_columns(
        ((pl.col("燃机1天然气流量") + pl.col('燃机2天然气流量')) / (
                pl.col('燃机功率1') + pl.col('燃机功率2') + pl.col('汽轮发电机功率')))
        .alias("气耗率")
    )

    # 只选择指定的列
    required_columns = ["DateTime", "背压模式", "纯凝模式", "汽轮发电机功率", "燃机功率1", "燃机功率2",
                        "汽机抽汽供热量", "瞬时热网供水热量", "总供热量", "凝汽器热负荷",
                        "凝结水补水流量",
                        "风机运行台数",
                        "循泵运行方式数字编码", "冷却塔进水蝶阀开启数量",
                        "环境温度", "大气湿度", "风速", "循环水流量", "背压", "气耗率"]

    # 检查列是否存在，只选择实际存在的列
    available_columns = [col for col in required_columns if col in df_inner.columns]
    df_inner = df_inner.select(available_columns)
    return df_inner



if __name__ == "__main__":
    df = load_all_data()
    df = filter_data(df, folder)
    df = 计算凝汽器热负荷(df)
    # 根据纯凝模式是否为0保存为两个文件
    纯凝数据 = df.filter(pl.col("纯凝模式") > 0.5)
    抽凝数据 = df.filter(pl.col("纯凝模式") <= 0.5)

    纯凝数据.write_csv(folder / "纯凝.csv")
    抽凝数据.write_csv(folder / "抽凝.csv")

    df.write_csv(folder / "data.csv")  # 这个是最终的预处理数据，提供给神经网络进行训练使用
    # 分段保存过滤后的数据
    分段保存Excel文件(df, "预处理后数据", 2000, folder / "数据清洗")
