import logging
import os
import shutil
import time
from datetime import datetime
from multiprocessing import Pool  # 新增
from pathlib import Path

import pandas as pd

from labeler import apply_labels

FILE_MODIFY_MIN_SECONDS = 60
# ROLLING_DAYS_30 = 30
# ROLLING_DAYS_90 = 90

# ====================== 配置项 ======================
# base_dir = "/data/wind-turbine"
base_dir = "/data/wind_files/headquarter/scada"
BASE_TMP_DIR = Path(f"{base_dir}/tmp")
BASE_LABEL_DIR = Path(f"{base_dir}/label")

# BASE_ROLLING_DAYS_DIR = Path(f"{base_dir}/{ROLLING_DAYS}days")

ERROR_DATA_DIR = Path(f"{base_dir}/error_data")

LOG_DIR = Path("/data/logs/scada_data_py")
LOG_FILE = LOG_DIR / f"process_{datetime.now().strftime('%Y%m%d')}.log"
LOG_LEVEL = logging.INFO

# 多进程配置（根据服务器CPU核心数调整）
MAX_WORKERS = 8


# ====================== 算法 ======================
def wind_data_labeling(df: pd.DataFrame, model_name: str = '') -> pd.DataFrame:
    df = apply_labels(df, model_name)
    return df


# ====================== 日志 ======================
def init_logger():
    LOG_DIR.mkdir(parents=True, exist_ok=True)
    logging.basicConfig(
        level=LOG_LEVEL,
        format="%(asctime)s - %(levelname)s - %(message)s",
        handlers=[
            logging.FileHandler(LOG_FILE, encoding="utf-8"),
            logging.StreamHandler()
        ]
    )
    return logging.getLogger(__name__)


logger = init_logger()


# ====================== 工具方法 ======================
def is_file_valid(file_path: Path) -> bool:
    try:
        pd.read_parquet(file_path)
        return True
    except Exception as e:
        logger.error(f"文件损坏，跳过：{file_path}，异常：{str(e)}")
        return False


def get_target_files(base_dir: Path) -> list:
    target_files = []
    now = time.time()
    for root, _, files in os.walk(base_dir):
        for file in files:
            if not file.endswith(".parquet"):
                continue
            file_path = Path(root) / file
            if now - file_path.stat().st_mtime <= FILE_MODIFY_MIN_SECONDS:
                continue
            target_files.append(file_path)
    return target_files


from functools import wraps


def retry_on_exception(max_retries=3, delay=1, backoff=2):
    """重试装饰器"""

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            retries = 0
            current_delay = delay
            while retries < max_retries:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    retries += 1
                    if retries == max_retries:
                        raise
                    logger.warning(f"操作失败，{retries}/{max_retries} 次重试，错误: {e}")
                    time.sleep(current_delay)
                    current_delay *= backoff
            return None

        return wrapper

    return decorator


@retry_on_exception(max_retries=3, delay=0.5)
def safe_read_parquet(file_path):
    """带重试机制的 Parquet 读取"""
    return pd.read_parquet(file_path)


def merge_rolling_days_data(model: str, wind_farm_code: str, wind_turbine_code: str,
                            df_oneday_label: pd.DataFrame, ROLLING_DAYS: int) -> None:
    BASE_ROLLING_DAYS_DIR = Path(f"{base_dir}/{ROLLING_DAYS}days")
    output_path = BASE_ROLLING_DAYS_DIR / model / wind_farm_code / f"{wind_turbine_code}.parquet"
    output_path.parent.mkdir(parents=True, exist_ok=True)

    df_list = []
    if output_path.exists():
        df_list.append(safe_read_parquet(output_path))
    df_list.append(df_oneday_label)

    merged_df = pd.concat(df_list, ignore_index=True)
    merged_df['localtime'] = pd.to_datetime(merged_df['localtime'], errors='coerce')
    merged_df.sort_values(by=['localtime'], inplace=True)

    # 按自然日 00:00 保留最近30天
    latest_time = merged_df['localtime'].max()
    latest_day = latest_time.floor('D')
    thirty_days_ago = latest_day - pd.Timedelta(days=ROLLING_DAYS - 1)
    merged_df = merged_df[merged_df['localtime'] > thirty_days_ago]

    merged_df.to_parquet(output_path, engine="pyarrow", index=False)
    logger.info(f"{ROLLING_DAYS}天滚动文件生成成功：{output_path}")


# ====================== 单个文件处理逻辑（抽成独立函数）======================
def process_single_file(tmp_file):
    # 20251020/CCWE1500-82.DF/EmlMGcty/zKCujSuK.parquet
    parts = tmp_file.parts
    data_date = parts[-4]
    model = parts[-3]
    wind_farm_code = parts[-2]
    wind_turbine_code = tmp_file.stem

    try:
        if not is_file_valid(tmp_file):
            target_path = ERROR_DATA_DIR / data_date / model / wind_farm_code / f"{wind_turbine_code}.parquet"
            target_path.parent.mkdir(parents=True, exist_ok=True)
            shutil.move(tmp_file, target_path)
            return "fail"

        # 读取 + 打标签
        df = safe_read_parquet(tmp_file)
        for col in df.columns:
            if col != "localtime":
                df[col] = pd.to_numeric(df[col], errors="coerce")

        # 补全数据,查询前一天最后一条数据,向后补全

        df_oneday_label = wind_data_labeling(df, model)

        # 保存label
        label_file = BASE_LABEL_DIR / data_date / model / wind_farm_code / f"{wind_turbine_code}.parquet"
        label_file.parent.mkdir(parents=True, exist_ok=True)
        df_oneday_label.to_parquet(label_file, engine="pyarrow", index=False)
        logger.info(f"{wind_turbine_code} 打标签完成：{label_file}")

        # 30天数据合并
        merge_rolling_days_data(model, wind_farm_code, wind_turbine_code, df_oneday_label, 30)
        # 90天数据合并
        merge_rolling_days_data(model, wind_farm_code, wind_turbine_code, df_oneday_label, 90)
        # 删除临时文件
        tmp_file.unlink()
        logger.info(f"临时文件已删除：{tmp_file}")
        return "success"

    except Exception as e:
        logger.error(f"处理失败：{tmp_file}，异常：{str(e)}", exc_info=True)
        target_path = ERROR_DATA_DIR / data_date / model / wind_farm_code / f"{wind_turbine_code}.parquet"
        target_path.parent.mkdir(parents=True, exist_ok=True)
        shutil.move(tmp_file, target_path)
        return "fail"


# ====================== 主流程（多进程）======================
def process_wind_data():
    logger.info(f"临时目录：{BASE_TMP_DIR}")
    tmp_files = get_target_files(BASE_TMP_DIR)

    if not tmp_files:
        logger.info("无待处理文件，任务结束")
        return

    logger.info(f"待处理文件总数：{len(tmp_files)}")
    logger.info(f"启动多进程，进程数：{MAX_WORKERS}")

    # ========== 多进程核心 ==========
    with Pool(processes=MAX_WORKERS) as pool:
        results = pool.map(process_single_file, tmp_files)

    success_count = sum(1 for res in results if res == "success")
    fail_count = sum(1 for res in results if res == "fail")

    logger.info(f"任务完成 | 成功：{success_count} | 失败：{fail_count}")
    logger.info("=" * 50)


if __name__ == "__main__":
    process_wind_data()
