import json
import shutil
import csv
import os
import asyncio
import logging
from pathlib import Path
from typing import List, Dict, Tuple, Optional
from concurrent.futures import ThreadPoolExecutor, as_completed
from pydicom.uid import generate_uid


from ..cookie.getcookie import logger
from ..models import DICOMDirectory, Series, DICOMInstance
from ..core.series_processor import get_series_info


def copy_dicom(src_path: str, dest_dir: str) -> Path:
    src = Path(src_path)
    dest_folder = Path(dest_dir)
    if not src.exists():
        raise FileNotFoundError(f"源文件不存在: {src}")
    dest_folder.mkdir(parents=True, exist_ok=True)

    dest = dest_folder / src.name
    if dest.exists():
        stem = src.stem
        suffix = src.suffix
        i = 1
        while True:
            candidate = dest_folder / f"{stem}_copy{i}{suffix}"
            if not candidate.exists():
                dest = candidate
                break
            i += 1

    shutil.copy2(src, dest)
    return dest


def copy_dicom_parallel(file_dest_pairs: list[tuple[str, str]], max_workers: int = None) -> int:
    """
    并行复制多个 DICOM 文件到各自的目标目录，使用多线程加速 IO 操作。
    如果目标文件已存在，则重命名。
    返回成功复制的文件数量。
    """
    if max_workers is None:
        max_workers = min(120, os.cpu_count() * 20)

    def copy_single(src_path: str, dest_dir: str) -> Path:
        src = Path(src_path)
        dest_folder = Path(dest_dir)
        if not src.exists():
            raise FileNotFoundError(f"源文件不存在: {src}")
        dest_folder.mkdir(parents=True, exist_ok=True)

        dest = dest_folder / src.name
        if dest.exists():
            stem = src.stem
            suffix = src.suffix
            i = 1
            while True:
                candidate = dest_folder / f"{stem}_copy{i}{suffix}"
                if not candidate.exists():
                    dest = candidate
                    break
                i += 1

        shutil.copy2(src, dest)
        return dest

    success_count = 0
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = [executor.submit(copy_single, src, dest) for src, dest in file_dest_pairs]
        for future in as_completed(futures):
            try:
                future.result()
                success_count += 1
            except Exception as e:
                logger.exception(f"复制失败: {e}")

    return success_count





def copy_files(dicom_files: List[DICOMInstance], output_folder: str, series_uid: str) -> None:
    """复制文件到输出文件夹"""
    output_folder_path = os.path.join(output_folder, f"{series_uid}")
    os.makedirs(output_folder_path, exist_ok=True)
    for dicom_file in dicom_files:
        filepath = dicom_file.filepath
        filename = os.path.basename(filepath)
        output_file = os.path.join(output_folder_path, filename)
        shutil.copyfile(filepath, output_file)


def separate_series_by_patient(directory_path):
    base_path = Path(directory_path)
    csv_file = base_path / "文件与信息汇总.csv"

    # 检查是否已存在汇总文件，如果存在则不执行，防止反复执行
    if csv_file.exists():
        logger.info(f"文件 {csv_file} 已存在，跳过执行以防止反复执行")
        return {
            "content": [
                {
                    "type": "text",
                    "text": json.dumps({
                        "message": f"文件 {csv_file} 已存在，跳过执行以防止反复执行",
                        "skipped": True
                    }, ensure_ascii=False)
                }
            ]
        }
   
    dicom_directory = DICOMDirectory(directory_path)
    all_series = list(dicom_directory.get_dicom_series())

    # 变量初始化
    base_path = Path(directory_path)
    sucess_num = 0
    main_dir = []
    processed_pids = set()
    processed_series_uids = set()  # 跟踪已处理的 SeriesInstanceUID，避免重复记录
    series_records = []
    file_dest_pairs = []  # 收集所有文件和目标目录对
    directories_to_create = set()  # 收集需要创建的目录
    series_uid_file_count = {}  # 统计每个 series_uid 实际复制的文件数量
    series_uid_to_info = {}  # 存储每个series_uid的完整信息，用于JSON输出

    # 第一步：并行获取所有series的信息（IO密集型操作，并行可以大幅加速）
    def get_info_for_series(series):
        """获取series信息，返回(series对象, info字典)"""
        try:
            # 先尝试从series对象获取series_uid
            series_uid = None
            if hasattr(series, 'series_uid') and series.series_uid:
                series_uid = series.series_uid
            
            # 如果无法直接获取，尝试从第一个实例获取
            if not series_uid and series.instances:
                try:
                    series_uid = series.get_dicom_tag('SeriesInstanceUID')
                except:
                    pass
            
            # 获取完整信息
            info = get_series_info(series)
            series_uid = info.get("SeriesInstanceUID", f"unknown_{id(series)}")
            return (series, info, series_uid)
        except Exception as e:
            logger.warning(f"获取series信息失败: {e}")
            return None

    # 并行处理所有series信息获取
    series_info_list = []
    max_info_workers = min(32, (os.cpu_count() or 4) * 4)
    with ThreadPoolExecutor(max_workers=max_info_workers) as executor:
        futures = {executor.submit(get_info_for_series, series): series for series in all_series}
        for future in as_completed(futures):
            result = future.result()
            if result:
                series_info_list.append(result)

    # 第二步：第一遍遍历，统计所有series的文件数量
    series_info_map = {}  # series对象 -> (info, series_uid)
    for series, info, series_uid in series_info_list:
        series_info_map[series] = (info, series_uid)
    
    # 第一遍：统计每个series_uid的总文件数量
    for series in all_series:
        if series not in series_info_map:
            continue
            
        info, series_uid = series_info_map[series]
        
        # 统计当前series的文件数量
        file_count_for_this_series = 0
        for instance in getattr(series, "instances", []):
            src = (
                getattr(instance, "filepath", None)
                or getattr(instance, "file_path", None)
                or getattr(instance, "path", None)
            )
            if src:
                file_count_for_this_series += 1
        
        # 累加每个 series_uid 的文件数量（因为同一个 series_uid 可能分布在多个 series 对象中）
        if series_uid not in series_uid_file_count:
            series_uid_file_count[series_uid] = 0
        series_uid_file_count[series_uid] += file_count_for_this_series
    
    # 第二遍：只处理 imageCount >= 100 的series
    for series in all_series:
        if series not in series_info_map:
            continue
            
        info, series_uid = series_info_map[series]
        
        # 如果文件数量 < 100，跳过此series
        if series_uid not in series_uid_file_count or series_uid_file_count[series_uid] < 100:
            logger.info(f"Series {series_uid} 文件数量 {series_uid_file_count.get(series_uid, 0)} < 100，跳过处理")
            continue
        
        pid = info["PatientID"]

        # 处理患者目录
        p_dir = base_path / pid
        s_dir = p_dir / series_uid
        
        # 收集需要创建的目录
        directories_to_create.add(str(p_dir))
        directories_to_create.add(str(s_dir))
        
        if pid not in processed_pids:
            main_dir.append(p_dir)
            processed_pids.add(pid)

        if 1==2:
            pass
        else:
            # 不需要拆分，正常处理
            # 只记录第一次遇到的 SeriesInstanceUID，避免重复
            if series_uid not in processed_series_uids:
                # 记录序列信息
                record = {k: str(v) if v is not None else "" for k, v in info.items()}
                record["NewLocation"] = str(s_dir)
                # 注意：此时 imageCount 还是初始值，后面会在写入CSV前更新为实际文件数量
                series_records.append(record)
                processed_series_uids.add(series_uid)
                
                # 保存series信息用于JSON输出
                series_uid_to_info[series_uid] = {
                    "series_uid": series_uid,
                    "folder": str(s_dir),
                    "patient_id": pid
                }

            # 收集文件进行复制（只处理 >= 100 的series）
            for instance in getattr(series, "instances", []):
                # 支持常见的实例路径属性名
                src = (
                    getattr(instance, "filepath", None)
                    or getattr(instance, "file_path", None)
                    or getattr(instance, "path", None)
                )
                if not src:
                    logger.warning(f"实例缺少路径: patient={pid}, series={series_uid}")
                    continue

                # 收集文件和目标目录对
                file_dest_pairs.append((src, str(s_dir)))

    # 批量创建所有目录（并行）
    def create_dir(path_str: str):
        Path(path_str).mkdir(parents=True, exist_ok=True)
    
    if directories_to_create:
        with ThreadPoolExecutor(max_workers=min(32, len(directories_to_create))) as executor:
            list(executor.map(create_dir, directories_to_create))

    # 并行复制所有文件
    sucess_num = copy_dicom_parallel(file_dest_pairs)

    # 文件复制完成后，从目标文件夹统计实际文件数量，更新 imageCount
    # 同时过滤掉 imageCount < 100 的记录，并添加"是否需要拆分"字段
    filtered_series_records = []
    for record in series_records:
        new_location = record.get("NewLocation", "")
        series_uid = record.get("SeriesInstanceUID", "")
        actual_count = 0
        
        if new_location:
            try:
                s_dir = Path(new_location)
                if s_dir.exists() and s_dir.is_dir():
                    # 统计文件夹中的实际文件数量（排除子目录）
                    actual_count = sum(1 for item in s_dir.iterdir() if item.is_file())
                else:
                    # 如果文件夹不存在，使用之前统计的数量
                    if series_uid in series_uid_file_count:
                        actual_count = series_uid_file_count[series_uid]
            except Exception as e:
                logger.warning(f"统计文件夹文件数失败: {new_location}, {e}")
                # 出错时使用之前统计的数量作为备选
                if series_uid in series_uid_file_count:
                    actual_count = series_uid_file_count[series_uid]
        
        # 如果实际文件数量 < 100，跳过此记录（不写入CSV）
        if actual_count < 100:
            logger.info(f"Series {series_uid} 实际文件数量 {actual_count} < 100，不写入CSV")
            continue
        
        # 更新 imageCount
        record["imageCount"] = str(actual_count)
        record["SliceNum"] = str(actual_count)
        
        # 添加"是否需要拆分"字段：imageCount > 500 标记为"是"，否则"否"
        record["是否需要拆分"] = "是" if actual_count > 500 else "否"
        
        # 更新JSON输出信息
        if series_uid in series_uid_to_info:
            series_uid_to_info[series_uid]["imageCount"] = actual_count
            series_uid_to_info[series_uid]["是否需要拆分"] = "是" if actual_count > 500 else "否"
        
        filtered_series_records.append(record)

    # 创建CSV文件并保存信息（只保存 imageCount >= 100 的记录）
    if filtered_series_records:
        csv_file = base_path / "文件与信息汇总.csv"

        # 写入指定的字段，包括"是否需要拆分"
        priority = [
            "PatientID", "PatientName", "SeriesInstanceUID",
            "NewLocation", "PatientAge", "StudyDate", "StudyInstanceUID",
            "imageCount", "是否需要拆分"
        ]

        try:
            with open(csv_file, 'w', newline='', encoding='utf-8-sig') as f:
                writer = csv.DictWriter(f, fieldnames=priority, extrasaction='ignore')
                writer.writeheader()
                writer.writerows(filtered_series_records)
            logger.info(f"序列信息已保存至: {csv_file}，共 {len(filtered_series_records)} 条记录")
        except Exception as e:
            logger.error(f"保存序列信息失败: {e}")

    # 准备JSON输出，包含每个series的文件夹和是否需要拆分信息
    # 使用已更新的series_uid_to_info信息（已在过滤步骤中更新）
    series_details = []
    for series_uid, info in series_uid_to_info.items():
        # 只包含实际处理的series（imageCount >= 100）
        if "imageCount" in info:
            series_details.append({
                "series_uid": series_uid,
                "folder": info.get("folder", ""),
                "imageCount": info.get("imageCount", 0),
                "是否需要拆分": info.get("是否需要拆分", "否")
            })
    
    message=f"已为 {len(processed_pids)} 位患者分离 {len(filtered_series_records)} 个序列（已过滤 imageCount < 100），成功复制 {sucess_num} 个文件。"
    dic={
        "totalPatients": len(processed_pids),
        "totalSeries": len(filtered_series_records),
        "totalFilesCopied": sucess_num,
        "message": message,
        "newDirectory": [str(d) for d in main_dir],
        "seriesDetails": series_details
    }
    return {
            "content": [
                {
                    "type": "text",
                    "text": json.dumps(dic, ensure_ascii=False, indent=2)
                }
            ]
    }