from app import db
from datetime import datetime
import logging
from app.core.data_parse.time_utils import get_east_asia_time_naive, get_east_asia_time_str, get_east_asia_timestamp, get_east_asia_isoformat, get_east_asia_date_str
import uuid
import os
import boto3
from botocore.config import Config
from io import BytesIO
import json
from app.models.parse_models import ParseTaskRepository
from app.config.config import DevelopmentConfig, ProductionConfig

# 配置变量
config = ProductionConfig()
minio_url = f"{'https' if getattr(config, 'MINIO_SECURE', False) else 'http'}://{getattr(config, 'MINIO_HOST', 'localhost')}"
minio_access_key = getattr(config, 'MINIO_USER', 'minioadmin')
minio_secret_key = getattr(config, 'MINIO_PASSWORD', 'minioadmin')
minio_bucket = getattr(config, 'MINIO_BUCKET', 'dataops')


def get_minio_client():
    """获取MinIO客户端连接"""
    try:
        logging.info(f"尝试连接MinIO服务器: {minio_url}")
        
        minio_client = boto3.client(
            's3',
            endpoint_url=minio_url,
            aws_access_key_id=minio_access_key,
            aws_secret_access_key=minio_secret_key,
            config=Config(
                signature_version='s3v4',
                retries={'max_attempts': 3, 'mode': 'standard'},
                connect_timeout=10,
                read_timeout=30
            )
        )
        
        # 确保存储桶存在
        buckets = minio_client.list_buckets()
        bucket_names = [bucket['Name'] for bucket in buckets.get('Buckets', [])]
        logging.info(f"成功连接到MinIO服务器，现有存储桶: {bucket_names}")
        
        if minio_bucket not in bucket_names:
            logging.info(f"创建存储桶: {minio_bucket}")
            minio_client.create_bucket(Bucket=minio_bucket)
            
        return minio_client
    except Exception as e:
        logging.error(f"MinIO连接错误: {str(e)}")
        return None


def process_career_path(career_path, talent_node_id, talent_name_zh):
    """
    处理career_path，创建Hotel节点和相关的Neo4j关系
    
    Args:
        career_path (list): 职业轨迹列表，每个元素包含酒店、职位等信息
        talent_node_id (int): Talent节点的Neo4j ID
        talent_name_zh (str): 人才中文姓名，用于日志记录
        
    Returns:
        dict: 处理结果信息，包含成功和失败的统计
    """
    result = {
        'total_items': 0,
        'hotels_created': 0,
        'hotels_skipped': 0,
        'brand_relationships_created': 0,
        'brand_relationships_failed': 0,
        'work_for_relationships_created': 0,
        'work_for_relationships_failed': 0,
        'work_as_relationships_created': 0,
        'work_as_relationships_failed': 0,
        'errors': []
    }
    
    try:
        if not career_path or not isinstance(career_path, list):
            logging.info("career_path为空或不是列表格式，跳过Hotel节点创建")
            return result
        
        result['total_items'] = len(career_path)
        logging.info(f"开始处理career_path，共 {len(career_path)} 条职业轨迹记录")
        
        # 在执行当前代码逻辑之前，清除传入节点的WORK_FOR和WORK_AS关系
        try:
            from app.services.neo4j_driver import neo4j_driver
            
            with neo4j_driver.get_session() as session:
                # 清除WORK_FOR关系
                clear_work_for_query = """
                MATCH (t:Talent)-[r:WORK_FOR]->(h:Hotel)
                WHERE id(t) = $talent_node_id
                DELETE r
                """
                work_for_result = session.run(clear_work_for_query, talent_node_id=talent_node_id)
                logging.info(f"已清除Talent节点(ID: {talent_node_id})的所有WORK_FOR关系")
                
                # 清除WORK_AS关系
                clear_work_as_query = """
                MATCH (t:Talent)-[r:WORK_AS]->(d:DataLabel)
                WHERE id(t) = $talent_node_id
                DELETE r
                """
                work_as_result = session.run(clear_work_as_query, talent_node_id=talent_node_id)
                logging.info(f"已清除Talent节点(ID: {talent_node_id})的所有WORK_AS关系")
                
        except Exception as clear_error:
            logging.error(f"清除Talent节点关系失败: {str(clear_error)}")
            result['errors'].append(f"清除Talent节点关系失败: {str(clear_error)}")
            # 即使清除关系失败，也继续执行后续逻辑
        
        # 查询Neo4j图数据库中DataLabel标签中node_type为"brand"的所有节点
        brand_labels = []
        try:
            from app.services.neo4j_driver import neo4j_driver
            
            with neo4j_driver.get_session() as session:
                # 查询node_type为"brand"的DataLabel节点
                brand_query = """
                MATCH (n:DataLabel)
                WHERE n.node_type = "brand"
                RETURN n.name_zh as name_zh
                ORDER BY n.name_zh
                """
                brand_result = session.run(brand_query)
                
                # 将查询结果的name_zh保存到brand_labels数组
                for record in brand_result:
                    if record['name_zh']:
                        brand_labels.append(record['name_zh'])
                
                logging.info(f"成功查询到 {len(brand_labels)} 个品牌标签")
                logging.info(f"品牌标签列表: {brand_labels}")
                
                # 更新结果统计
                result['brand_labels_count'] = len(brand_labels)
                result['brand_labels'] = brand_labels
                
        except Exception as brand_query_error:
            logging.error(f"查询品牌标签失败: {str(brand_query_error)}")
            result['errors'].append(f"查询品牌标签失败: {str(brand_query_error)}")
            # 即使查询品牌标签失败，也继续执行后续逻辑
        
        # 将brand_labels构建成JSON字符串
        brand_labels_json = json.dumps(brand_labels, ensure_ascii=False)
        logging.info(f"品牌标签JSON字符串: {brand_labels_json}")
        
        for i, career_item in enumerate(career_path):
            try:
                if not isinstance(career_item, dict):
                    logging.warning(f"跳过无效的career_path元素 {i}: 不是字典格式")
                    result['hotels_skipped'] += 1
                    continue
                
                hotel_zh = career_item.get('hotel_zh', '')
                hotel_en = career_item.get('hotel_en', '')
                
                if not hotel_zh:
                    logging.warning(f"跳过career_path元素 {i}: 缺少hotel_zh字段")
                    result['hotels_skipped'] += 1
                    continue
                
                # 创建Hotel节点
                try:
                    from app.services.neo4j_driver import neo4j_driver
                    
                    # 直接使用Cypher语句查找或创建Hotel节点
                    with neo4j_driver.get_session() as session:
                        # 首先查找是否已存在相同hotel_zh的Hotel节点
                        find_query = """
                        MATCH (h:Hotel {hotel_zh: $hotel_zh})
                        RETURN id(h) as node_id, h.hotel_zh as hotel_zh
                        LIMIT 1
                        """
                        find_result = session.run(find_query, hotel_zh=hotel_zh).single()
                        
                        if find_result:
                            # 找到现有节点，使用其ID
                            hotel_node_id = find_result['node_id']
                            logging.info(f"找到现有Hotel节点，Neo4j ID: {hotel_node_id}, 酒店: {hotel_zh}")
                            result['hotels_created'] += 0  # 不增加计数，因为不是新创建的
                        else:
                            # 没有找到，创建新节点
                            current_time = get_east_asia_time_str()
                            create_query = """
                            CREATE (h:Hotel {
                                hotel_zh: $hotel_zh,
                                hotel_en: $hotel_en,
                                create_time: $create_time
                            })
                            RETURN id(h) as node_id
                            """
                            create_result = session.run(create_query, 
                                                      hotel_zh=hotel_zh,
                                                      hotel_en=hotel_en,
                                                      create_time=current_time).single()
                            
                            hotel_node_id = create_result['node_id'] if create_result else None
                            logging.info(f"成功创建新Hotel节点，Neo4j ID: {hotel_node_id}, 酒店: {hotel_zh}")
                            result['hotels_created'] += 1
                    
                except Exception as hotel_error:
                    logging.error(f"创建Hotel节点失败: {str(hotel_error)}")
                    result['errors'].append(f"创建Hotel节点失败: {hotel_zh}, 错误: {str(hotel_error)}")
                    continue
                
                # 使用千问大模型判断酒店所属品牌
                try:
                    from openai import OpenAI
                    
                    # 配置千问模型参数
                    QWEN_API_KEY = os.environ.get('QWEN_API_KEY', 'sk-8f2320dafc9e4076968accdd8eebd8e9')
                    base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1"
                    model = "qwen-long-latest"
                    
                    # 创建OpenAI客户端
                    client = OpenAI(
                        api_key=QWEN_API_KEY,
                        base_url=base_url,
                    )
                    
                    # 构建提示词
                    prompt = f"""请根据酒店名称'{hotel_zh}'判断该酒店所属的品牌。通常酒店名称前半部分是地名，后半部分是品牌名称。
                    例如：扬州瘦西湖夜泊君亭酒店，品牌名称为'夜泊君亭'。
                    
                    请从以下品牌列表中选择最匹配的品牌名称：
                    {brand_labels_json}
                    
                    要求：
                    1. 必须返回标准的JSON格式
                    2. 格式必须为：{{"brand": "品牌名称"}}
                    3. 不要包含任何其他文本、说明或markdown格式
                    4. 如果无法确定品牌或品牌不在列表中，返回：{{"brand": ""}}
                    5. 品牌名称必须完全匹配列表中的某个品牌名称
                    
                    请直接返回JSON，不要有其他内容。"""
                    
                    # 调用千问大模型
                    logging.info(f"######################发送给千问大模型的prompt: '{prompt}'")
                    
                    response = client.chat.completions.create(
                        model=model,
                        messages=[
                            {"role": "user", "content": prompt}
                        ],
                        temperature=0.1,
                        max_tokens=200
                    )
                    
                    brand_response = response.choices[0].message.content
                    
                    if brand_response and isinstance(brand_response, str):
                        # 记录原始响应内容
                        logging.info(f"###################千问大模型返回的原始响应: '{brand_response}'")
                        
                        # 尝试解析JSON响应
                        try:
                            # 清理响应内容，移除可能的markdown格式和多余文本
                            cleaned_response = brand_response.strip()
                            if cleaned_response.startswith('```json'):
                                cleaned_response = cleaned_response[7:]
                            if cleaned_response.endswith('```'):
                                cleaned_response = cleaned_response[:-3]
                            cleaned_response = cleaned_response.strip()
                            
                            logging.info(f"清理后的响应内容: '{cleaned_response}'")
                            
                            brand_data = json.loads(cleaned_response)
                            brand_name = brand_data.get('brand', '')
                            
                            if brand_name:
                                # 查找对应的DataLabel节点
                                try:
                                    from app.services.neo4j_driver import neo4j_driver
                                    
                                    # 直接查询Neo4j查找name_zh等于品牌名称的DataLabel节点
                                    with neo4j_driver.get_session() as session:
                                        query = "MATCH (n:DataLabel {name_zh: $brand_name}) RETURN id(n) as node_id, n.name_zh as name_zh LIMIT 1"
                                        result_query = session.run(query, brand_name=brand_name).single()
                                        
                                        if result_query:
                                            label_node_id = result_query['node_id']
                                            
                                            # 创建Hotel节点与品牌标签的BELONGS_TO关系
                                            try:
                                                from app.services.neo4j_driver import neo4j_driver
                                                
                                                with neo4j_driver.get_session() as session:
                                                    # 检查Hotel节点与DataLabel节点之间是否已经存在BELONGS_TO关系
                                                    check_relationship_query = """
                                                    MATCH (h:Hotel)-[r:BELONGS_TO]->(d:DataLabel)
                                                    WHERE id(h) = $hotel_node_id AND id(d) = $label_node_id
                                                    RETURN r
                                                    LIMIT 1
                                                    """
                                                    existing_relationship = session.run(check_relationship_query, 
                                                                                      hotel_node_id=hotel_node_id,
                                                                                      label_node_id=label_node_id).single()
                                                    
                                                    if existing_relationship:
                                                        logging.info(f"Hotel节点与品牌标签的关系已存在，跳过创建: {hotel_zh} BELONGS_TO {brand_name}")
                                                        result['brand_relationships_created'] += 0  # 不增加计数，因为关系已存在
                                                    else:
                                                        # 关系不存在，创建新的BELONGS_TO关系
                                                        # 直接使用Cypher创建关系，避免使用create_relationship函数
                                                        with neo4j_driver.get_session() as session:
                                                            create_rel_query = """
                                                            MATCH (h:Hotel), (d:DataLabel)
                                                            WHERE id(h) = $hotel_node_id AND id(d) = $label_node_id
                                                            MERGE (h)-[r:BELONGS_TO]->(d)
                                                            RETURN r
                                                            """
                                                            rel_result = session.run(create_rel_query, 
                                                                                    hotel_node_id=hotel_node_id,
                                                                                    label_node_id=label_node_id)
                                                            if rel_result.single():
                                                                logging.info(f"成功创建Hotel节点与品牌标签的关系: {hotel_zh} BELONGS_TO {brand_name}")
                                                                result['brand_relationships_created'] += 1
                                                            else:
                                                                logging.warning(f"创建Hotel节点与品牌标签关系失败: {hotel_zh} -> {brand_name}")
                                                                result['brand_relationships_failed'] += 1
                                                            
                                            except Exception as check_error:
                                                logging.error(f"检查Hotel节点与品牌标签关系失败: {str(check_error)}")
                                                result['errors'].append(f"检查关系失败: {hotel_zh} -> {brand_name}, 错误: {str(check_error)}")
                                                # 即使检查失败，也尝试创建关系
                                                with neo4j_driver.get_session() as session:
                                                    create_rel_query = """
                                                    MATCH (h:Hotel), (d:DataLabel)
                                                    WHERE id(h) = $hotel_node_id AND id(d) = $label_node_id
                                                    MERGE (h)-[r:BELONGS_TO]->(d)
                                                    RETURN r
                                                    """
                                                    rel_result = session.run(create_rel_query, 
                                                                            hotel_node_id=hotel_node_id,
                                                                            label_node_id=label_node_id)
                                                    if rel_result.single():
                                                        logging.info(f"成功创建Hotel节点与品牌标签的关系: {hotel_zh} BELONGS_TO {brand_name}")
                                                        result['brand_relationships_created'] += 1
                                                    else:
                                                        logging.warning(f"创建Hotel节点与品牌标签关系失败: {hotel_zh} -> {brand_name}")
                                                        result['brand_relationships_failed'] += 1
                                        else:
                                            logging.warning(f"未找到品牌标签节点: {brand_name}")
                                            result['brand_relationships_failed'] += 1
                                            
                                except Exception as query_error:
                                    logging.error(f"查询品牌标签节点失败: {str(query_error)}")
                                    result['brand_relationships_failed'] += 1
                                    result['errors'].append(f"查询品牌标签节点失败: {brand_name}, 错误: {str(query_error)}")
                            else:
                                logging.warning(f"千问大模型返回的品牌名称为空，酒店: {hotel_zh}")
                                result['brand_relationships_failed'] += 1
                                
                        except json.JSONDecodeError as json_error:
                            logging.warning(f"解析千问大模型返回的JSON失败，酒店: {hotel_zh}, 响应: '{brand_response}'")
                            result['brand_relationships_failed'] += 1
                    else:
                        logging.warning(f"千问大模型返回结果无效，酒店: {hotel_zh}")
                        result['brand_relationships_failed'] += 1
                        
                except Exception as brand_error:
                    logging.error(f"调用千问大模型判断品牌失败: {str(brand_error)}")
                    result['brand_relationships_failed'] += 1
                    result['errors'].append(f"调用千问大模型判断品牌失败: {hotel_zh}, 错误: {str(brand_error)}")
                
                # 创建Talent节点到Hotel节点的WORK_FOR关系
                try:
                    # 获取职业轨迹信息
                    title_zh = career_item.get('title_zh', '')
                    date = career_item.get('date', '')
                    
                    # 创建WORK_FOR关系，包含title_zh和date属性
                    work_for_properties = {}
                    if title_zh:
                        work_for_properties['title_zh'] = title_zh
                    if date:
                        work_for_properties['date'] = date
                    
                    # 直接使用Cypher创建WORK_FOR关系，避免使用create_relationship函数
                    from app.services.neo4j_driver import neo4j_driver
                    
                    with neo4j_driver.get_session() as session:
                        work_for_query = """
                        MATCH (t:Talent), (h:Hotel)
                        WHERE id(t) = $talent_node_id AND id(h) = $hotel_node_id
                        MERGE (t)-[r:WORK_FOR]->(h)
                        SET r += $properties
                        RETURN r
                        """
                        work_for_result = session.run(work_for_query, 
                                                    talent_node_id=talent_node_id,
                                                    hotel_node_id=hotel_node_id,
                                                    properties=work_for_properties)
                        
                        if work_for_result.single():
                            logging.info(f"成功创建Talent到Hotel的WORK_FOR关系: Talent({talent_name_zh}) WORK_FOR Hotel({hotel_zh})")
                            result['work_for_relationships_created'] += 1
                        else:
                            logging.warning(f"创建Talent到Hotel的WORK_FOR关系失败: Talent({talent_name_zh}) -> Hotel({hotel_zh})")
                            result['work_for_relationships_failed'] += 1
                    
                    # 创建Talent节点到DataLabel节点的WORK_AS关系
                    try:
                        # 查找对应的DataLabel节点（职位标签）
                        try:
                            from app.services.neo4j_driver import neo4j_driver
                            
                            # 直接查询Neo4j查找name_zh等于title_zh的DataLabel节点
                            with neo4j_driver.get_session() as session:
                                query = "MATCH (n:DataLabel {name_zh: $title_zh}) RETURN id(n) as node_id, n.name_zh as name_zh LIMIT 1"
                                result_query = session.run(query, title_zh=title_zh).single()
                                
                                if result_query:
                                    # 找到现有的DataLabel节点
                                    label_node_id = result_query['node_id']
                                    logging.info(f"找到现有职位标签节点: {title_zh}, ID: {label_node_id}")
                                else:
                                    # 没有找到，创建新的DataLabel节点
                                    from app.core.graph.graph_operations import create_or_get_node
                                    
                                    current_time = get_east_asia_time_str()
                                    label_properties = {
                                        'name_zh': title_zh,
                                        'en_name': career_item.get('title_en', ''),
                                        'describe': '',
                                        'time': current_time,
                                        'category': '人才地图',
                                        'status': 'active',
                                        'node_type': 'position'
                                    }
                                    
                                    label_node_id = create_or_get_node('DataLabel', **label_properties)
                                    logging.info(f"创建新职位标签节点: {title_zh}, ID: {label_node_id}")
                                
                                # 创建WORK_AS关系，包含hotel_zh和date属性
                                work_as_properties = {}
                                if hotel_zh:
                                    work_as_properties['hotel_zh'] = hotel_zh
                                if date:
                                    work_as_properties['date'] = date
                                
                                # 创建Talent节点到DataLabel节点的WORK_AS关系
                                # 直接使用Cypher创建WORK_AS关系，避免使用create_relationship函数
                                work_as_query = """
                                MATCH (t:Talent), (d:DataLabel)
                                WHERE id(t) = $talent_node_id AND id(d) = $label_node_id
                                MERGE (t)-[r:WORK_AS]->(d)
                                SET r += $properties
                                RETURN r
                                """
                                work_as_result = session.run(work_as_query, 
                                                            talent_node_id=talent_node_id,
                                                            label_node_id=label_node_id,
                                                            properties=work_as_properties)
                                
                                if work_as_result.single():
                                    logging.info(f"成功创建Talent到职位标签的WORK_AS关系: Talent({talent_name_zh}) WORK_AS DataLabel({title_zh})")
                                    result['work_as_relationships_created'] += 1
                                else:
                                    logging.warning(f"创建Talent到职位标签的WORK_AS关系失败: Talent({talent_name_zh}) -> DataLabel({title_zh})")
                                    result['work_as_relationships_failed'] += 1
                                    
                        except Exception as label_query_error:
                            logging.error(f"查询或创建职位标签节点失败: {str(label_query_error)}")
                            result['errors'].append(f"查询或创建职位标签节点失败: {title_zh}, 错误: {str(label_query_error)}")
                            
                    except Exception as work_as_error:
                        logging.error(f"创建WORK_AS关系失败: {str(work_as_error)}")
                        result['errors'].append(f"创建WORK_AS关系失败: {title_zh}, 错误: {str(work_as_error)}")
                        
                except Exception as work_for_error:
                    logging.error(f"创建WORK_FOR关系失败: {str(work_for_error)}")
                    result['errors'].append(f"创建WORK_FOR关系失败: {hotel_zh}, 错误: {str(work_for_error)}")
                
            except Exception as career_error:
                logging.error(f"处理career_path元素 {i} 失败: {str(career_error)}")
                result['errors'].append(f"处理career_path元素 {i} 失败: {str(career_error)}")
                continue
        
        logging.info(f"career_path处理完成，统计信息: {result}")
        return result
        
    except Exception as career_path_error:
        error_msg = f"处理career_path失败: {str(career_path_error)}"
        logging.error(error_msg)
        result['errors'].append(error_msg)
        return result


def create_or_get_talent_node(**properties):
    """
    创建具有给定属性的新Talent节点或获取现有节点
    如果具有相同pg_id的节点存在，则更新属性
    
    Args:
        **properties: 作为关键字参数的节点属性，必须包含pg_id
        
    Returns:
        节点id
    """
    try:
        from app.services.neo4j_driver import neo4j_driver
        
        # 检查是否提供了pg_id
        if 'pg_id' not in properties:
            raise ValueError("pg_id is required for Talent node creation")
        
        pg_id = properties['pg_id']
        
        with neo4j_driver.get_session() as session:
            # 检查节点是否存在（根据pg_id查找）
            query = """
            MATCH (n:Talent {pg_id: $pg_id})
            RETURN n
            """
            result = session.run(query, pg_id=pg_id).single()
            
            if result:
                # 节点存在，更新属性
                props_string = ", ".join([f"n.{key} = ${key}" for key in properties if key != 'pg_id'])
                if props_string:
                    # 使用字符串格式化来避免动态字符串构造
                    update_query = f"""
                    MATCH (n:Talent {{pg_id: $pg_id}})
                    SET {props_string}
                    RETURN id(n) as node_id
                    """
                    result = session.run(update_query, **properties).single()  # type: ignore
                    if result:
                        logging.info(f"已更新现有Talent节点，pg_id: {pg_id}, Neo4j ID: {result['node_id']}")
                        return result["node_id"]
                    else:
                        logging.error(f"更新Talent节点失败，pg_id: {pg_id}")
                        return None
                else:
                    # 没有需要更新的属性，返回现有节点ID
                    existing_node_id = result['n'].id
                    logging.info(f"找到现有Talent节点，pg_id: {pg_id}, Neo4j ID: {existing_node_id}")
                    return existing_node_id
            
            # 如果到这里，则创建新节点
            props_keys = ", ".join([f"{key}: ${key}" for key in properties])
            create_query = f"""
            CREATE (n:Talent {{{props_keys}}})
            RETURN id(n) as node_id
            """
            result = session.run(create_query, **properties).single()  # type: ignore
            if result:
                logging.info(f"已创建新Talent节点，pg_id: {pg_id}, Neo4j ID: {result['node_id']}")
                return result["node_id"]
            else:
                logging.error(f"创建Talent节点失败，pg_id: {pg_id}")
                return None
            
    except Exception as e:
        logging.error(f"Error in create_or_get_talent_node: {str(e)}")
        raise e


def get_parse_tasks(page=1, per_page=10, task_type=None, task_status=None):
    """
    获取解析任务列表
    
    Args:
        page (int): 页码
        per_page (int): 每页记录数
        task_type (str): 任务类型过滤
        task_status (str): 任务状态过滤
        
    Returns:
        dict: 包含查询结果和分页信息
    """
    try:
        if page < 1 or per_page < 1 or per_page > 100:
            return {
                'code': 400,
                'success': False,
                'message': '分页参数错误',
                'data': None
            }
        
        query = ParseTaskRepository.query
        
        if task_type:
            query = query.filter_by(task_type=task_type)
        if task_status:
            query = query.filter_by(task_status=task_status)
        
        query = query.order_by(ParseTaskRepository.created_at.desc())
        
        pagination = query.paginate(page=page, per_page=per_page, error_out=False)
        
        tasks = [task.to_dict() for task in pagination.items]
        
        return {
            'code': 200,
            'success': True,
            'message': '获取解析任务列表成功',
            'data': {
                'tasks': tasks,
                'pagination': {
                    'page': page,
                    'per_page': per_page,
                    'total': pagination.total,
                    'pages': pagination.pages,
                    'has_next': pagination.has_next,
                    'has_prev': pagination.has_prev
                }
            }
        }
    
    except Exception as e:
        error_msg = f"获取解析任务列表失败: {str(e)}"
        logging.error(error_msg, exc_info=True)
        
        return {
            'code': 500,
            'success': False,
            'message': error_msg,
            'data': None
        }


def get_parse_task_detail(task_name):
    """
    获取解析任务详情
    
    Args:
        task_name (str): 任务名称
        
    Returns:
        dict: 包含查询结果
    """
    try:
        if not task_name:
            return {
                'code': 400,
                'success': False,
                'message': '任务名称不能为空',
                'data': None
            }
        
        task = ParseTaskRepository.query.filter_by(task_name=task_name).first()
        
        if not task:
            return {
                'code': 404,
                'success': False,
                'message': f'未找到任务名称为 {task_name} 的记录',
                'data': None
            }
        
        return {
            'code': 200,
            'success': True,
            'message': f'成功获取任务 {task_name} 的详细信息',
            'data': task.to_dict()
        }
    
    except Exception as e:
        error_msg = f"获取解析任务详情失败: {str(e)}"
        logging.error(error_msg, exc_info=True)
        
        return {
            'code': 500,
            'success': False,
            'message': error_msg,
            'data': None
        }


def _validate_files_by_task_type(files, task_type):
    """
    根据任务类型验证文件格式
    
    Args:
        files (list): 文件数组
        task_type (str): 任务类型
        
    Returns:
        dict: 验证结果
    """
    # 定义不同任务类型允许的文件扩展名
    allowed_extensions = {
        '名片': {'.jpg', '.jpeg', '.png'},
        '简历': {'.pdf'},
        '新任命': {'.md'},
        '杂项': None  # 杂项不限制文件格式
    }
    
    task_extensions = allowed_extensions.get(task_type)
    
    for i, file_obj in enumerate(files):
        if not hasattr(file_obj, 'filename') or not file_obj.filename:
            return {
                'code': 400,
                'success': False,
                'message': f'第{i+1}个文件缺少文件名'
            }
        
        # 杂项类型不验证文件格式
        if task_type == '杂项':
            continue
            
        file_ext = os.path.splitext(file_obj.filename)[1].lower()
        if file_ext not in task_extensions:
            format_desc = {
                '名片': 'JPG和PNG格式',
                '简历': 'PDF格式',
                '新任命': 'MD格式'
            }
            return {
                'code': 400,
                'success': False,
                'message': f'第{i+1}个文件格式不支持，{task_type}任务只支持{format_desc[task_type]}'
            }
    
    return {'success': True}


def _handle_recruitment_task(created_by, data=None):
    """
    处理招聘类型任务（数据库记录，不需要文件上传）
    
    Args:
        created_by (str): 创建者
        data (str or list): 招聘数据内容，可以是JSON字符串或已解析的列表
        
    Returns:
        dict: 处理结果
    """
    try:
        # 生成任务名称
        current_date = get_east_asia_date_str()
        task_uuid = str(uuid.uuid4())[:8]
        task_name = f"recruitment_task_{current_date}_{task_uuid}"
        
        # 构建任务来源信息，直接使用传入的数据
        task_source = []
        
        # 将传入的data参数写入task_source字段
        if data:
            # 如果data是字符串，尝试解析为JSON
            if isinstance(data, str):
                try:
                    data_list = json.loads(data)
                except json.JSONDecodeError:
                    # 如果不是有效的JSON，将其作为单个元素处理
                    data_list = [data]
            elif isinstance(data, list):
                data_list = data
            else:
                # 其他类型转换为列表
                data_list = [data]
            
            # 对每个元素添加parse_flag和status字段
            for item in data_list:
                if isinstance(item, dict):
                    item['parse_flag'] = 1
                    item['status'] = '待解析'
                else:
                    # 如果不是字典，转换为字典并添加parse_flag和status
                    item = {'data': item, 'parse_flag': 1, 'status': '待解析'}
                task_source.append(item)
        
        # 创建解析任务记录
        parse_task = ParseTaskRepository()
        parse_task.task_name = task_name
        parse_task.task_status = '待解析'  # 招聘任务不需要实际解析操作，直接设置为成功
        parse_task.task_type = '招聘'
        parse_task.task_source = task_source
        parse_task.collection_count = len(task_source)  # 招聘任务的数据项数量
        parse_task.parse_count = 0
        parse_task.parse_result = None
        parse_task.created_by = created_by
        parse_task.updated_by = created_by
        
        db.session.add(parse_task)
        db.session.commit()
        
        logging.info(f"成功创建招聘任务记录: {task_name}, 处理了 {len(task_source)} 个数据项")
        
        return {
            'code': 200,
            'success': True,
            'message': '招聘任务创建成功'
        }
        
    except Exception as e:
        db.session.rollback()
        error_msg = f"创建招聘任务失败: {str(e)}"
        logging.error(error_msg, exc_info=True)
        
        return {
            'code': 500,
            'success': False,
            'message': error_msg
        }


def _get_minio_directory_by_task_type(task_type):
    """
    根据任务类型获取MinIO存储目录
    
    Args:
        task_type (str): 任务类型
        
    Returns:
        str: MinIO目录路径
    """
    directory_mapping = {
        '名片': 'talent_photos',
        '简历': 'resume_files',
        '新任命': 'appointment_files',
        '杂项': 'misc_files'
    }
    
    return directory_mapping.get(task_type, 'misc_files')


def _generate_filename_by_task_type(task_type, original_filename):
    """
    根据任务类型生成文件名
    
    Args:
        task_type (str): 任务类型
        original_filename (str): 原始文件名
        
    Returns:
        str: 生成的文件名
    """
    timestamp = get_east_asia_timestamp()
    unique_id = uuid.uuid4().hex[:8]
    file_ext = os.path.splitext(original_filename)[1].lower()
    
    filename_prefix = {
        '名片': 'talent_photo',
        '简历': 'resume',
        '新任命': 'appointment',
        '杂项': 'misc_file'
    }
    
    prefix = filename_prefix.get(task_type, 'misc_file')
    return f"{prefix}_{timestamp}_{unique_id}{file_ext}"


def _get_content_type_by_extension(filename):
    """
    根据文件扩展名获取ContentType
    
    Args:
        filename (str): 文件名
        
    Returns:
        str: ContentType
    """
    file_ext = os.path.splitext(filename)[1].lower()
    
    content_type_mapping = {
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.png': 'image/png',
        '.pdf': 'application/pdf',
        '.md': 'text/markdown'
    }
    
    return content_type_mapping.get(file_ext, 'application/octet-stream')


def add_parse_task(files, task_type, created_by='system', data=None, publish_time=None):
    """
    新增解析任务，根据任务类型处理不同类型的文件
    
    Args:
        files (list): 前端上传的文件数组，每个元素是FileStorage对象
        task_type (str): 任务类型，可选值：'名片', '简历', '新任命', '招聘', '杂项'
        created_by (str): 创建者，默认为'system'
        data (str): 数据内容，招聘类型必需
        publish_time (str): 发布时间，新任命类型必需
        
    Returns:
        dict: 包含操作结果的字典
    """
    try:
        # 参数验证
        if not task_type or task_type not in ['名片', '简历', '新任命', '招聘', '杂项']:
            return {
                'code': 400,
                'success': False,
                'message': 'task_type参数必须是以下值之一：名片、简历、新任命、招聘、杂项'
            }
        
        # 对于招聘类型，不需要文件，直接处理数据库记录
        if task_type == '招聘':
            if files:
                return {
                    'code': 400,
                    'success': False,
                    'message': '招聘类型任务不需要上传文件'
                }
            # 招聘任务处理逻辑
            return _handle_recruitment_task(created_by, data)
        
        # 其他类型需要验证文件
        if not files or not isinstance(files, list):
            return {
                'code': 400,
                'success': False,
                'message': 'files参数必须是非空数组'
            }
        
        if len(files) == 0:
            return {
                'code': 400,
                'success': False,
                'message': '文件数组不能为空'
            }
        
        # 根据任务类型验证文件格式
        validation_result = _validate_files_by_task_type(files, task_type)
        if not validation_result['success']:
            return validation_result
        
        # 获取MinIO客户端
        minio_client = get_minio_client()
        if not minio_client:
            return {
                'code': 500,
                'success': False,
                'message': '无法连接到MinIO服务器'
            }
        
        # 存储上传结果
        uploaded_files = []
        failed_uploads = []
        
        # 获取MinIO存储目录
        minio_directory = _get_minio_directory_by_task_type(task_type)
        
        # 上传每个文件到MinIO
        for i, file_obj in enumerate(files):
            try:
                # 生成唯一的文件名
                filename = _generate_filename_by_task_type(task_type, file_obj.filename)
                minio_path = f"{minio_directory}/{filename}"
                
                # 上传文件到MinIO
                logging.info(f"开始上传第{i+1}个文件到MinIO: {minio_path}")
                
                # 重置文件指针
                file_obj.seek(0)
                
                # 根据文件类型设置ContentType
                content_type = file_obj.content_type or _get_content_type_by_extension(file_obj.filename)
                
                # 对包含非ASCII字符的文件名和任务类型进行URL编码处理
                import urllib.parse
                safe_filename = urllib.parse.quote(file_obj.filename, safe='')
                safe_task_type = urllib.parse.quote(task_type, safe='')
                safe_content_type = urllib.parse.quote(f'{task_type}_parse_task', safe='')
                
                minio_client.put_object(
                    Bucket=minio_bucket,
                    Key=minio_path,
                    Body=file_obj,
                    ContentType=content_type,
                    Metadata={
                        'original_filename': safe_filename,
                        'upload_time': get_east_asia_isoformat(),
                        'task_type': safe_task_type,
                        'content_type': safe_content_type
                    }
                )
                
                # 构建完整的MinIO路径，包含minio_client部分
                complete_minio_path = f"{minio_url}/{minio_bucket}/{minio_path}"
                
                uploaded_files.append({
                    'original_filename': file_obj.filename,
                    'minio_path': complete_minio_path,
                    'relative_path': minio_path,  # 保留相对路径作为参考
                    'file_size': len(file_obj.read()) if hasattr(file_obj, 'read') else 0
                })
                
                # 重置文件指针
                file_obj.seek(0)
                
                logging.info(f"成功上传第{i+1}个文件到MinIO: {minio_path}")
                
            except Exception as upload_error:
                error_msg = f"上传第{i+1}个文件失败: {str(upload_error)}"
                logging.error(error_msg, exc_info=True)
                failed_uploads.append({
                    'filename': file_obj.filename,
                    'error': str(upload_error)
                })
        
        # 检查是否有文件上传成功
        if len(uploaded_files) == 0:
            return {
                'code': 500,
                'success': False,
                'message': '所有文件上传失败'
            }
        
        # 生成任务名称
        current_date = get_east_asia_date_str()
        task_uuid = str(uuid.uuid4())[:8]
        task_name = f"parse_task_{current_date}_{task_uuid}"
        
        # 构建任务来源信息，简化为数组格式
        task_source = []
        
        # 添加成功上传的文件信息
        for file_info in uploaded_files:
            file_obj = {
                'original_filename': file_info['original_filename'],
                'minio_path': file_info['minio_path'],
                'status': '待解析',
                'parse_flag': 1
            }
            # 对于新任命类型，添加publish_time字段
            if task_type == '新任命' and publish_time:
                file_obj['publish_time'] = publish_time
            task_source.append(file_obj)
        
        # 添加失败的文件信息
        for failed_file in failed_uploads:
            file_obj = {
                'original_filename': failed_file['filename'],
                'minio_path': '',
                'status': '上传失败',
                'parse_flag': 0
            }
            # 对于新任命类型，添加publish_time字段
            if task_type == '新任命' and publish_time:
                file_obj['publish_time'] = publish_time
            task_source.append(file_obj)
        
        # 创建解析任务记录
        try:
            parse_task = ParseTaskRepository()
            parse_task.task_name = task_name
            parse_task.task_status = '待解析'
            parse_task.task_type = task_type
            parse_task.task_source = task_source
            parse_task.collection_count = len(uploaded_files)
            parse_task.parse_count = 0  # 解析数量初始为0
            parse_task.parse_result = None  # 解析结果初始为空
            parse_task.created_by = created_by
            parse_task.updated_by = created_by
            
            db.session.add(parse_task)
            db.session.commit()
            
            logging.info(f"成功创建解析任务记录: {task_name}")
            
            # 返回成功结果，简化结构
            result_data = parse_task.to_dict()
            
            # 如果是新任命类型，执行MD文件切分
            if task_type == '新任命':
                try:
                    logging.info(f"开始对新任命任务进行MD文件切分，task_id: {parse_task.id}")
                    split_result = split_markdown_files(parse_task.id)
                    if split_result.get('success'):
                        logging.info(f"MD文件切分成功: {split_result.get('message', '')}")
                    else:
                        logging.warning(f"MD文件切分失败: {split_result.get('message', '')}")
                except Exception as split_error:
                    logging.error(f"执行MD文件切分时发生错误: {str(split_error)}")
            
            if len(failed_uploads) > 0:
                return {
                    'code': 206,  # Partial Content
                    'success': True,
                    'message': f'解析任务创建成功，但有{len(failed_uploads)}个文件上传失败'
                }
            else:
                return {
                    'code': 200,
                    'success': True,
                    'message': '解析任务创建成功，所有文件上传完成'
                }
                
        except Exception as db_error:
            db.session.rollback()
            error_msg = f"创建解析任务记录失败: {str(db_error)}"
            logging.error(error_msg, exc_info=True)
            
            return {
                'code': 500,
                'success': False,
                'message': error_msg
            }
            
    except Exception as e:
        error_msg = f"新增解析任务失败: {str(e)}"
        logging.error(error_msg, exc_info=True)
        
        return {
            'code': 500,
            'success': False,
            'message': error_msg
        }


def _update_origin_source_with_minio_path(existing_origin_source, talent_data=None):
    """
    更新origin_source字段，将talent_data提供的origin_source与现有的origin_source进行合并
    
    Args:
        existing_origin_source: 现有的origin_source内容
        talent_data: 人才数据，包含origin_source字段
        
    Returns:
        list: 更新后的origin_source JSON数组，格式为：
        [
            {"task_type": "名片", "minio_path": "path1", "source_date": "2025-01-01"},
            {"task_type": "简历", "minio_path": "path2", "source_date": "2025-01-02"}
        ]
    """
    import json
    
    try:
        # 解析现有的origin_source
        origin_list = []
        if existing_origin_source:
            if isinstance(existing_origin_source, str):
                try:
                    parsed = json.loads(existing_origin_source)
                    if isinstance(parsed, list):
                        origin_list = parsed
                    elif isinstance(parsed, dict):
                        origin_list = [parsed]
                    else:
                        origin_list = []
                except (json.JSONDecodeError, TypeError):
                    origin_list = []
            elif isinstance(existing_origin_source, list):
                origin_list = existing_origin_source
            elif isinstance(existing_origin_source, dict):
                origin_list = [existing_origin_source]
            else:
                origin_list = []
        
        # 确保origin_list是列表
        if not isinstance(origin_list, list):
            origin_list = []
        
        # 处理talent_data提供的origin_source
        if talent_data and talent_data.get('origin_source'):
            talent_origin_source = talent_data.get('origin_source')
            
            if isinstance(talent_origin_source, list):
                # 如果是列表，验证每个元素是否符合格式要求
                for entry in talent_origin_source:
                    if isinstance(entry, dict) and 'task_type' in entry and 'minio_path' in entry:
                        # 检查是否已存在相同的minio_path记录
                        existing_paths = [item.get('minio_path') for item in origin_list if isinstance(item, dict)]
                        if entry.get('minio_path') not in existing_paths:
                            origin_list.append(entry)
            elif isinstance(talent_origin_source, str):
                # 如果是字符串，尝试解析为JSON
                try:
                    parsed_talent_origin = json.loads(talent_origin_source)
                    if isinstance(parsed_talent_origin, list):
                        for entry in parsed_talent_origin:
                            if isinstance(entry, dict) and 'task_type' in entry and 'minio_path' in entry:
                                existing_paths = [item.get('minio_path') for item in origin_list if isinstance(item, dict)]
                                if entry.get('minio_path') not in existing_paths:
                                    origin_list.append(entry)
                    elif isinstance(parsed_talent_origin, dict) and 'task_type' in parsed_talent_origin and 'minio_path' in parsed_talent_origin:
                        existing_paths = [item.get('minio_path') for item in origin_list if isinstance(item, dict)]
                        if parsed_talent_origin.get('minio_path') not in existing_paths:
                            origin_list.append(parsed_talent_origin)
                except (json.JSONDecodeError, TypeError):
                    # 如果解析失败，忽略talent_data的origin_source
                    pass
            elif isinstance(talent_origin_source, dict) and 'task_type' in talent_origin_source and 'minio_path' in talent_origin_source:
                # 如果是字典，检查是否符合格式要求
                existing_paths = [item.get('minio_path') for item in origin_list if isinstance(item, dict)]
                if talent_origin_source.get('minio_path') not in existing_paths:
                    origin_list.append(talent_origin_source)
        
        # 验证最终结果，确保每个元素都符合格式要求
        validated_list = []
        for item in origin_list:
            if isinstance(item, dict) and 'task_type' in item and 'minio_path' in item:
                # 确保有source_date字段，如果没有则添加当前日期
                if 'source_date' not in item:
                    item['source_date'] = get_east_asia_date_str()
                validated_list.append(item)
        
        return validated_list
        
    except Exception as e:
        logging.error(f"更新origin_source失败: {str(e)}")
        # 如果处理失败，返回空数组
        return []


def add_single_talent(talent_data, minio_path=None, task_type=None):
    """
    添加单个人才记录（基于add_business_card逻辑，去除MinIO图片上传）
    
    Args:
        talent_data (dict): 人才信息数据
        minio_path (str, optional): MinIO路径，用于更新origin_source字段
        task_type (str, optional): 任务类型，用于更新origin_source字段
        
    Returns:
        dict: 处理结果，包含保存的信息和状态
    """
    try:
        # 检查必要的数据
        if not talent_data:
            return {
                'code': 400,
                'success': False,
                'message': '人才数据不能为空'
            }
        
        # 检查重复记录
        try:
            from app.core.data_parse.parse_system import check_duplicate_business_card
            duplicate_check = check_duplicate_business_card(talent_data)
            logging.info(f"重复记录检查结果: {duplicate_check['reason']}")
        except Exception as e:
            logging.error(f"重复记录检查失败: {str(e)}", exc_info=True)
            # 如果检查失败，默认创建新记录
            duplicate_check = {
                'is_duplicate': False,
                'action': 'create_new',
                'existing_card': None,
                'reason': f'重复检查失败，创建新记录: {str(e)}'
            }
        
        try:
            # 根据重复检查结果执行不同操作
            if duplicate_check['action'] == 'update':
                # 更新现有记录
                existing_card = duplicate_check['existing_card']
                
                # 导入手机号码处理函数
                from app.core.data_parse.parse_system import normalize_mobile_numbers, merge_mobile_numbers
                
                # 更新基本信息
                existing_card.name_en = talent_data.get('name_en', existing_card.name_en)
                existing_card.title_zh = talent_data.get('title_zh', existing_card.title_zh)
                existing_card.title_en = talent_data.get('title_en', existing_card.title_en)
                
                # 处理手机号码字段，支持多个手机号码
                if 'mobile' in talent_data:
                    new_mobile = normalize_mobile_numbers(talent_data.get('mobile', ''))
                    if new_mobile:
                        # 如果有新的手机号码，合并到现有手机号码中
                        existing_card.mobile = merge_mobile_numbers(existing_card.mobile, new_mobile)
                
                existing_card.phone = talent_data.get('phone', existing_card.phone)
                existing_card.email = talent_data.get('email', existing_card.email)
                existing_card.hotel_zh = talent_data.get('hotel_zh', existing_card.hotel_zh)
                existing_card.hotel_en = talent_data.get('hotel_en', existing_card.hotel_en)
                existing_card.address_zh = talent_data.get('address_zh', existing_card.address_zh)
                existing_card.address_en = talent_data.get('address_en', existing_card.address_en)
                existing_card.postal_code_zh = talent_data.get('postal_code_zh', existing_card.postal_code_zh)
                existing_card.postal_code_en = talent_data.get('postal_code_en', existing_card.postal_code_en)
                existing_card.brand_zh = talent_data.get('brand_zh', existing_card.brand_zh)
                existing_card.brand_en = talent_data.get('brand_en', existing_card.brand_en)
                existing_card.affiliation_zh = talent_data.get('affiliation_zh', existing_card.affiliation_zh)
                existing_card.affiliation_en = talent_data.get('affiliation_en', existing_card.affiliation_en)
                
                # 处理生日字段
                if talent_data.get('birthday'):
                    try:
                        existing_card.birthday = datetime.strptime(talent_data.get('birthday'), '%Y-%m-%d').date()
                    except ValueError:
                        # 如果日期格式不正确，保持原值
                        pass
                
                # 处理年龄字段
                if 'age' in talent_data:
                    try:
                        if talent_data['age'] is not None and str(talent_data['age']).strip():
                            age_value = int(talent_data['age'])
                            if 0 < age_value <= 150:  # 合理的年龄范围检查
                                existing_card.age = age_value
                        else:
                            existing_card.age = None
                    except (ValueError, TypeError):
                        # 如果年龄格式不正确，保持原值
                        pass
                
                existing_card.native_place = talent_data.get('native_place', existing_card.native_place)
                existing_card.gender = talent_data.get('gender', existing_card.gender)  # 新增性别字段
                existing_card.residence = talent_data.get('residence', existing_card.residence)
                existing_card.brand_group = talent_data.get('brand_group', existing_card.brand_group)
                # 更新image_path字段，从talent_data中获取
                existing_card.image_path = talent_data.get('image_path', existing_card.image_path)
                # 更新origin_source字段，将talent_data提供的origin_source与现有的origin_source进行合并
                existing_card.origin_source = _update_origin_source_with_minio_path(existing_card.origin_source, talent_data)
                existing_card.talent_profile = talent_data.get('talent_profile', existing_card.talent_profile)
                existing_card.updated_by = 'talent_system'
                
                # 更新职业轨迹，传递从talent_data获取的图片路径
                from app.core.data_parse.parse_system import update_career_path
                image_path = talent_data.get('image_path', '')
                existing_card.career_path = update_career_path(existing_card, talent_data)
                
                db.session.commit()
                
                logging.info(f"已更新现有人才记录，ID: {existing_card.id}")
                
                # 在Neo4j图数据库中更新Talent节点
                try:
                    # 创建Talent节点属性
                    talent_properties = {
                        'name_zh': existing_card.name_zh,
                        'name_en': existing_card.name_en,
                        'mobile': existing_card.mobile,
                        'phone': existing_card.phone,
                        'email': existing_card.email,
                        'status': existing_card.status,
                        'birthday': existing_card.birthday.strftime('%Y-%m-%d') if existing_card.birthday else None,
                        'age': existing_card.age,
                        'residence': existing_card.residence,
                        'native_place': existing_card.native_place,
                        'gender': existing_card.gender,  # 新增性别字段
                        'pg_id': existing_card.id,  # PostgreSQL主记录的ID
                        'updated_at': get_east_asia_time_str()
                    }
                    
                    # 在Neo4j中更新或创建Talent节点
                    neo4j_node_id = create_or_get_talent_node(**talent_properties)
                    logging.info(f"成功在Neo4j中更新Talent节点，Neo4j ID: {neo4j_node_id}, PostgreSQL ID: {existing_card.id}")
                    
                    # 处理career_path，创建相关的Neo4j节点和关系
                    if existing_card.career_path and isinstance(existing_card.career_path, list):
                        try:
                            # 调用process_career_path函数处理职业轨迹
                            career_result = process_career_path(
                                career_path=existing_card.career_path,
                                talent_node_id=neo4j_node_id,
                                talent_name_zh=existing_card.name_zh
                            )
                            
                            # 记录处理结果
                            logging.info(f"处理career_path完成，结果: {career_result}")
                                    
                        except Exception as career_error:
                            logging.error(f"处理career_path失败: {str(career_error)}")
                            # career_path处理失败不影响主流程，继续执行
                    else:
                        logging.info(f"人才记录 {existing_card.id} 没有career_path数据，跳过Neo4j关系处理")
                    
                    # 更新parsed_talents表中的对应记录状态
                    if talent_data.get('id'):
                        try:
                            from app.core.data_parse.parse_system import ParsedTalent
                            parsed_talent = ParsedTalent.query.filter_by(id=talent_data['id']).first()
                            if parsed_talent:
                                parsed_talent.status = '已入库'
                                db.session.commit()
                                logging.info(f"已更新parsed_talents表记录状态为已入库，ID: {talent_data['id']}")
                        except Exception as update_error:
                            logging.error(f"更新parsed_talents表记录状态失败: {str(update_error)}")
                            # 状态更新失败不影响主流程
                    
                except Exception as neo4j_error:
                    logging.error(f"在Neo4j中更新Talent节点失败: {str(neo4j_error)}")
                    # Neo4j操作失败不影响主流程，继续返回成功结果
                
                return {
                    'code': 200,
                    'success': True,
                    'message': f'人才信息已更新。{duplicate_check["reason"]}'
                }
                
            elif duplicate_check['action'] == 'create_with_duplicates':
                # 创建新记录作为主记录，并保存疑似重复记录信息
                from app.core.data_parse.parse_system import create_main_card_with_duplicates
                main_card, duplicate_record = create_main_card_with_duplicates(
                    talent_data, 
                    talent_data.get('image_path', ''),  # 从talent_data获取图片路径
                    duplicate_check['suspected_duplicates'],
                    duplicate_check['reason'],
                    task_type=task_type  # 传递task_type参数
                )
                
                # 更新origin_source字段，将talent_data提供的origin_source与现有的origin_source进行合并
                main_card.origin_source = _update_origin_source_with_minio_path(main_card.origin_source, talent_data)
                db.session.commit()  # 提交origin_source的更新
                
                # 注意：当创建新记录作为主记录并保存疑似重复记录信息时，不在Neo4j图数据库中创建Talent节点
                # 这是因为疑似重复记录需要进一步人工确认和处理
                logging.info(f"跳过Neo4j Talent节点创建，等待疑似重复记录处理完成，PostgreSQL ID: {main_card.id}")
                
                # 更新parsed_talents表中的对应记录状态
                if talent_data.get('id'):
                    try:
                        from app.core.data_parse.parse_system import ParsedTalent
                        parsed_talent = ParsedTalent.query.filter_by(id=talent_data['id']).first()
                        if parsed_talent:
                            parsed_talent.status = '已入库'
                            db.session.commit()
                            logging.info(f"已更新parsed_talents表记录状态为已入库，ID: {talent_data['id']}")
                    except Exception as update_error:
                        logging.error(f"更新parsed_talents表记录状态失败: {str(update_error)}")
                        # 状态更新失败不影响主流程
                
                return {
                    'code': 202,  # Accepted，表示已接受但需要进一步处理
                    'success': True,
                    'message': f'创建新记录成功，发现疑似重复记录待处理。{duplicate_check["reason"]}'
                }
                
            else:
                # 创建新记录
                # 直接使用上传的请求参数talent_data中的career_path记录
                career_path = talent_data.get('career_path', [])
                
                # 获取图片路径
                image_path = talent_data.get('image_path', '')
                
                # 导入手机号码处理函数和BusinessCard模型
                from app.core.data_parse.parse_system import normalize_mobile_numbers, BusinessCard
                
                # 处理年龄字段，确保是有效的整数或None
                age_value = None
                if talent_data.get('age'):
                    try:
                        age_value = int(talent_data.get('age'))
                        if age_value <= 0 or age_value > 150:  # 合理的年龄范围检查
                            age_value = None
                    except (ValueError, TypeError):
                        age_value = None
                
                business_card = BusinessCard()
                business_card.name_zh = talent_data.get('name_zh', '')
                business_card.name_en = talent_data.get('name_en', '')
                business_card.title_zh = talent_data.get('title_zh', '')
                business_card.title_en = talent_data.get('title_en', '')
                business_card.mobile = normalize_mobile_numbers(talent_data.get('mobile', ''))
                business_card.phone = talent_data.get('phone', '')
                business_card.email = talent_data.get('email', '')
                business_card.hotel_zh = talent_data.get('hotel_zh', '')
                business_card.hotel_en = talent_data.get('hotel_en', '')
                business_card.address_zh = talent_data.get('address_zh', '')
                business_card.address_en = talent_data.get('address_en', '')
                business_card.postal_code_zh = talent_data.get('postal_code_zh', '')
                business_card.postal_code_en = talent_data.get('postal_code_en', '')
                business_card.brand_zh = talent_data.get('brand_zh', '')
                business_card.brand_en = talent_data.get('brand_en', '')
                business_card.affiliation_zh = talent_data.get('affiliation_zh', '')
                business_card.affiliation_en = talent_data.get('affiliation_en', '')
                business_card.birthday = datetime.strptime(talent_data.get('birthday'), '%Y-%m-%d').date() if talent_data.get('birthday') else None
                business_card.age = age_value
                business_card.native_place = talent_data.get('native_place', '')
                business_card.gender = talent_data.get('gender', '')  # 新增性别字段
                business_card.residence = talent_data.get('residence', '')
                business_card.image_path = image_path  # 从talent_data获取图片路径
                business_card.career_path = career_path  # 直接使用talent_data中的career_path
                business_card.brand_group = talent_data.get('brand_group', '')
                business_card.origin_source = _update_origin_source_with_minio_path(None, talent_data)
                business_card.talent_profile = talent_data.get('talent_profile', '')
                business_card.status = 'active'
                business_card.updated_by = 'talent_system'
                
                db.session.add(business_card)
                db.session.commit()
                
                logging.info(f"人才信息已保存到数据库，ID: {business_card.id}")
                
                # 在Neo4j图数据库中创建Talent节点
                try:
                    # 创建Talent节点属性
                    talent_properties = {
                        'name_zh': business_card.name_zh,
                        'name_en': business_card.name_en,
                        'mobile': business_card.mobile,
                        'phone': business_card.phone,
                        'email': business_card.email,
                        'status': business_card.status,
                        'birthday': business_card.birthday.strftime('%Y-%m-%d') if business_card.birthday else None,
                        'age': business_card.age,
                        'residence': business_card.residence,
                        'native_place': business_card.native_place,
                        'gender': business_card.gender,  # 新增性别字段
                        'pg_id': business_card.id,  # PostgreSQL主记录的ID
                        'updated_at': get_east_asia_time_str()
                    }
                    
                    # 在Neo4j中创建Talent节点
                    neo4j_node_id = create_or_get_talent_node(**talent_properties)
                    logging.info(f"成功在Neo4j中创建Talent节点，Neo4j ID: {neo4j_node_id}, PostgreSQL ID: {business_card.id}")
                    
                    # 处理career_path，创建Hotel节点及关系
                    career_result = process_career_path(career_path, neo4j_node_id, business_card.name_zh)
                    logging.info(f"career_path处理完成，结果: {career_result}")
                    
                    # 更新parsed_talents表中的对应记录状态
                    if talent_data.get('id'):
                        try:
                            from app.core.data_parse.parse_system import ParsedTalent
                            parsed_talent = ParsedTalent.query.filter_by(id=talent_data['id']).first()
                            if parsed_talent:
                                parsed_talent.status = '已入库'
                                db.session.commit()
                                logging.info(f"已更新parsed_talents表记录状态为已入库，ID: {talent_data['id']}")
                        except Exception as update_error:
                            logging.error(f"更新parsed_talents表记录状态失败: {str(update_error)}")
                            # 状态更新失败不影响主流程
                    
                except Exception as neo4j_error:
                    logging.error(f"在Neo4j中创建Talent节点失败: {str(neo4j_error)}")
                    # Neo4j操作失败不影响主流程，继续返回成功结果
                
                return {
                    'code': 200,
                    'success': True,
                    'message': f'人才信息保存成功。{duplicate_check["reason"]}'
                }
                
        except Exception as e:
            db.session.rollback()
            error_msg = f"保存人才信息到数据库失败: {str(e)}"
            logging.error(error_msg, exc_info=True)
            
            return {
                'code': 500,
                'success': False,
                'message': error_msg
            }
            
    except Exception as e:
        db.session.rollback()
        error_msg = f"人才处理失败: {str(e)}"
        logging.error(error_msg, exc_info=True)
        
        return {
            'code': 500,
            'success': False,
            'message': error_msg
        }


def add_parsed_talents(api_response_data):
    """
    处理解析任务响应数据，提取人才信息并写入business_cards表
    
    Args:
        api_response_data (dict): 请求数据，严格按照样例格式：
            {"task_id": "119", "task_type": "名片", "data": {"results": [{"name_zh": "...", ...}]}}
        
    Returns:
        dict: 批量处理结果，格式与其他batch函数保持一致
    """
    try:
        # 验证参数
        if not api_response_data or not isinstance(api_response_data, dict):
            return {
                'code': 400,
                'success': False,
                'message': 'api_response_data参数必须是非空字典'
            }
        
        # 获取data字段
        response_data = api_response_data.get('data')
        if not response_data or not isinstance(response_data, dict):
            return {
                'code': 400,
                'success': False,
                'message': '请求中缺少有效的data字段'
            }
        
        # 获取results数组
        results = response_data.get('results', [])
        if not isinstance(results, list):
            return {
                'code': 400,
                'success': False,
                'message': '请求中的results字段必须是数组'
            }
        
        if len(results) == 0:
            return {
                'code': 400,
                'success': False,
                'message': '请求中的results数组为空，没有人才数据需要处理'
            }
        
        # 从api_response_data中提取task_type
        task_type = api_response_data.get('task_type', '')
        
        logging.info(f"开始处理人才数据，共 {len(results)} 条记录，任务类型: {task_type}")
        
        processed_results = []
        success_count = 0
        failed_count = 0
        success_messages = []  # 收集成功处理的message信息
        failed_messages = []   # 收集失败处理的message信息
        
        # 逐一处理每个结果项
        for i, talent_data in enumerate(results):
            try:
                logging.debug(f"处理第 {i+1}/{len(results)} 条记录")
                
                # 验证人才数据格式
                if not talent_data or not isinstance(talent_data, dict):
                    failed_count += 1
                    error_msg = '人才数据格式无效，必须是字典格式'
                    processed_results.append({
                        'index': i,
                        'success': False,
                        'message': error_msg
                    })
                    failed_messages.append(f"第{i+1}条记录: {error_msg}")
                    logging.warning(f"第 {i+1} 条记录人才数据格式无效")
                    continue
                
                # 检查必要字段
                if not talent_data.get('name_zh'):
                    failed_count += 1
                    error_msg = '人才数据缺少必要字段name_zh'
                    processed_results.append({
                        'index': i,
                        'success': False,
                        'message': error_msg
                    })
                    failed_messages.append(f"第{i+1}条记录: {error_msg}")
                    logging.warning(f"第 {i+1} 条记录缺少name_zh字段")
                    continue
                
                # 获取minio_path（如果存在）
                minio_path = talent_data.get('minio_path', '')
                
                # 处理单个人才数据
                try:
                    talent_result = add_single_talent(talent_data, minio_path, task_type)
                    if talent_result.get('success', False):
                        # 成功处理后，更新parsed_talents表中对应记录的status为"已入库"
                        talent_id = talent_data.get('id')
                        if talent_id:
                            try:
                                from app.core.data_parse.parse_system import ParsedTalent, db
                                # 查询并更新parsed_talents表中的记录
                                parsed_record = ParsedTalent.query.get(talent_id)
                                if parsed_record:
                                    parsed_record.status = '已入库'
                                    parsed_record.updated_at = get_east_asia_time_naive()
                                    parsed_record.updated_by = 'system'
                                    db.session.commit()
                                    logging.info(f"已更新parsed_talents表记录状态: id={talent_id}, status=已入库")
                                else:
                                    logging.warning(f"未找到ID为{talent_id}的parsed_talents记录")
                            except Exception as update_error:
                                logging.error(f"更新parsed_talents记录状态失败: {str(update_error)}")
                        
                        success_count += 1
                        talent_message = talent_result.get('message', f'成功处理人员: {talent_data.get("name_zh", "未知")}')
                        success_messages.append(f"第{i+1}条记录: {talent_message}")
                        processed_results.append({
                            'index': i,
                            'success': True,
                            'error': None,
                            'message': talent_message
                        })
                        logging.debug(f"成功处理第 {i+1} 条记录")
                    else:
                        failed_count += 1
                        error_msg = talent_result.get('message', '处理失败')
                        processed_results.append({
                            'index': i,
                            'success': False,
                            'message': error_msg
                        })
                        failed_messages.append(f"第{i+1}条记录: {error_msg}")
                        logging.error(f"处理第 {i+1} 条记录失败: {error_msg}")
                except Exception as talent_error:
                    failed_count += 1
                    error_msg = f"处理人才数据异常: {str(talent_error)}"
                    processed_results.append({
                        'index': i,
                        'success': False,
                        'message': error_msg
                    })
                    failed_messages.append(f"第{i+1}条记录: {error_msg}")
                    logging.error(error_msg, exc_info=True)
                    
            except Exception as item_error:
                failed_count += 1
                error_msg = f"处理结果项异常: {str(item_error)}"
                processed_results.append({
                    'index': i,
                    'success': False,
                    'message': error_msg
                })
                failed_messages.append(f"第{i+1}条记录: {error_msg}")
                logging.error(error_msg, exc_info=True)
        
        # 组装最终结果
        batch_result = {
            'summary': {
                'total_files': len(results),
                'success_count': success_count,
                'failed_count': failed_count,
                'success_rate': round((success_count / len(results)) * 100, 2) if len(results) > 0 else 0
            },
            'results': processed_results,
            'processed_time': get_east_asia_isoformat()
        }
        
        # 构建详细的message信息
        detailed_message = f"批量处理完成，成功 {success_count} 条，失败 {failed_count} 条"
        
        # 添加成功处理的详细信息
        if success_messages:
            detailed_message += f"。成功处理详情: {'; '.join(success_messages)}"
        
        # 添加失败处理的详细信息
        if failed_messages:
            detailed_message += f"。失败处理详情: {'; '.join(failed_messages)}"
        
        # 根据处理结果返回相应的状态
        if failed_count == 0:
            return {
                'code': 200,
                'success': True,
                'message': detailed_message
            }
        elif success_count == 0:
            return {
                'code': 500,
                'success': False,
                'message': detailed_message
            }
        else:
            return {
                'code': 206,  # Partial Content
                'success': True,
                'message': detailed_message
            }
            
    except Exception as e:
        error_msg = f"批量处理人才数据失败: {str(e)}"
        logging.error(error_msg, exc_info=True)
        
        return {
            'code': 500,
            'success': False,
            'message': error_msg
        } 


def _clean_field_value(value, field_type='string'):
    """
    清理字段值，将空字符串转换为None（适用于数据库字段）
    
    Args:
        value: 原始值
        field_type: 字段类型 ('string', 'date', 'int')
        
    Returns:
        清理后的值
    """
    if value is None:
        return None
    
    if field_type == 'string':
        return value if value != '' else None
    elif field_type == 'date':
        if value == '' or value is None:
            return None
        # 如果已经是date对象，直接返回
        if hasattr(value, 'date'):
            return value
        # 如果是字符串，尝试转换为date对象
        if isinstance(value, str):
            try:
                from datetime import datetime
                return datetime.strptime(value, '%Y-%m-%d').date()
            except ValueError:
                # 如果日期格式不正确，返回None
                return None
        return value
    elif field_type == 'int':
        if value == '' or value is None:
            return None
        try:
            return int(value)
        except (ValueError, TypeError):
            return None
    
    return value


def record_parsed_talent(talent_data, task_id=None, task_type=None):
    """
    记录单条解析成功的人才信息到parsed_talents表
    
    Args:
        talent_data (dict): 人才数据字典，包含解析出的人才信息
        task_id (str, optional): 任务ID
        task_type (str, optional): 任务类型
        
    Returns:
        dict: 包含操作结果的字典
    """
    try:
        from app.core.data_parse.parse_system import ParsedTalent, db
        from datetime import datetime
        
        # 验证人才数据
        if not talent_data or not isinstance(talent_data, dict):
            return {
                'success': False,
                'message': '人才数据不能为空且必须是字典格式'
            }
        
        # 检查必要字段 - name_zh或者name_en其中一个有值就算满足要求
        if not talent_data.get('name_zh') and not talent_data.get('name_en'):
            return {
                'success': False,
                'message': '人才数据必须包含name_zh或name_en字段中的至少一个'
            }
        
        # 创建ParsedTalent记录
        parsed_talent = ParsedTalent()
        parsed_talent.name_zh = _clean_field_value(talent_data.get('name_zh', ''), 'string')
        parsed_talent.name_en = _clean_field_value(talent_data.get('name_en', ''), 'string')
        parsed_talent.title_zh = _clean_field_value(talent_data.get('title_zh', ''), 'string')
        parsed_talent.title_en = _clean_field_value(talent_data.get('title_en', ''), 'string')
        parsed_talent.mobile = _clean_field_value(talent_data.get('mobile', ''), 'string')
        parsed_talent.phone = _clean_field_value(talent_data.get('phone', ''), 'string')
        parsed_talent.email = _clean_field_value(talent_data.get('email', ''), 'string')
        parsed_talent.hotel_zh = _clean_field_value(talent_data.get('hotel_zh', ''), 'string')
        parsed_talent.hotel_en = _clean_field_value(talent_data.get('hotel_en', ''), 'string')
        parsed_talent.address_zh = _clean_field_value(talent_data.get('address_zh', ''), 'string')
        parsed_talent.address_en = _clean_field_value(talent_data.get('address_en', ''), 'string')
        parsed_talent.postal_code_zh = _clean_field_value(talent_data.get('postal_code_zh', ''), 'string')
        parsed_talent.postal_code_en = _clean_field_value(talent_data.get('postal_code_en', ''), 'string')
        parsed_talent.brand_zh = _clean_field_value(talent_data.get('brand_zh', ''), 'string')
        parsed_talent.brand_en = _clean_field_value(talent_data.get('brand_en', ''), 'string')
        parsed_talent.affiliation_zh = _clean_field_value(talent_data.get('affiliation_zh', ''), 'string')
        parsed_talent.affiliation_en = _clean_field_value(talent_data.get('affiliation_en', ''), 'string')
        parsed_talent.image_path = _clean_field_value(talent_data.get('image_path', ''), 'string')
        parsed_talent.career_path = talent_data.get('career_path', [])
        parsed_talent.brand_group = _clean_field_value(talent_data.get('brand_group', ''), 'string')
        parsed_talent.birthday = _clean_field_value(talent_data.get('birthday'), 'date')
        parsed_talent.residence = _clean_field_value(talent_data.get('residence', ''), 'string')
        parsed_talent.age = _clean_field_value(talent_data.get('age'), 'int')
        parsed_talent.native_place = _clean_field_value(talent_data.get('native_place', ''), 'string')
        parsed_talent.gender = _clean_field_value(talent_data.get('gender', ''), 'string')  # 新增性别字段
        parsed_talent.origin_source = talent_data.get('origin_source', [])
        parsed_talent.talent_profile = _clean_field_value(talent_data.get('talent_profile', ''), 'string')
        parsed_talent.task_id = str(task_id) if task_id else ''
        parsed_talent.task_type = task_type or ''
        parsed_talent.status = '待审核'  # 统一设置为待审核状态
        parsed_talent.created_at = get_east_asia_time_naive()
        parsed_talent.updated_by = 'system'
        
        # 添加到数据库会话并提交
        db.session.add(parsed_talent)
        db.session.commit()
        
        logging.info(f"成功记录人才信息到parsed_talents表: {talent_data.get('name_zh', '')}")
        
        return {
            'success': True,
            'message': '成功记录人才信息'
        }
        
    except Exception as e:
        db.session.rollback()
        error_msg = f"记录人才信息失败: {str(e)}"
        logging.error(error_msg, exc_info=True)
        
        return {
            'success': False,
            'message': error_msg
        } 


def split_markdown_files(task_id):
    """
    根据task_id在parse_task_repository数据表里查找对应任务记录，
    遍历task_source中的每个元素，对包含**数字**格式的MD文件进行切分
    
    Args:
        task_id (str): 任务ID
        
    Returns:
        dict: 包含操作结果的字典
    """
    try:
        # 根据task_id查找任务记录
        task_record = ParseTaskRepository.query.get(task_id)
        if not task_record:
            return {
                'success': False,
                'message': f'未找到task_id为{task_id}的任务记录',
                'data': None
            }
        
        task_source = task_record.task_source
        if not task_source or not isinstance(task_source, list):
            return {
                'success': False,
                'message': 'task_source为空或格式不正确',
                'data': None
            }
        
        # 获取MinIO客户端
        minio_client = get_minio_client()
        if not minio_client:
            return {
                'success': False,
                'message': '无法连接到MinIO服务器',
                'data': None
            }
        
        success_count = 0
        failed_count = 0
        
        # 遍历task_source中的每个元素
        for i, item in enumerate(task_source):
            try:
                if not isinstance(item, dict):
                    failed_count += 1
                    continue
                
                minio_path = item.get('minio_path')
                if not minio_path:
                    failed_count += 1
                    continue
                
                # 从MinIO下载MD文件
                try:
                    # 解析MinIO URL获取对象路径
                    object_key = _extract_object_key_from_url(minio_path)
                    if not object_key:
                        failed_count += 1
                        continue
                    
                    # 下载文件
                    response = minio_client.get_object(Bucket=minio_bucket, Key=object_key)
                    file_content = response['Body'].read().decode('utf-8')
                    
                    # 检查是否包含**数字**格式
                    import re
                    pattern = r'\*\*\d+\*\*'
                    matches = re.findall(pattern, file_content)
                    
                    if not matches:
                        # 没有找到**数字**格式，跳过处理
                        logging.debug(f"文件 {minio_path} 不包含**数字**格式，跳过处理")
                        continue
                    
                    logging.info(f"文件 {minio_path} 包含**数字**格式，开始进行拆分处理")
                    
                    # 获取原始文件名
                    original_filename = item.get('original_filename', '')
                    if not original_filename:
                        # 从minio_path提取文件名
                        original_filename = object_key.split('/')[-1]
                    
                    # 去掉.md扩展名
                    base_name = original_filename
                    if base_name.endswith('.md'):
                        base_name = base_name[:-3]
                    
                    # 调用_split_markdown_content函数进行切分
                    split_parts = _split_markdown_content(file_content)
                    
                    if len(split_parts) <= 1:
                        # 没有成功切分，跳过
                        logging.debug(f"文件 {minio_path} 拆分后只有1个或0个部分，跳过处理")
                        continue
                    
                    logging.info(f"文件 {minio_path} 成功拆分为 {len(split_parts)} 个部分")
                    
                    # 为每个切分部分创建新的MD文件
                    new_items = []
                    for j, part_content in enumerate(split_parts, 1):
                        try:
                            # 生成新文件名
                            new_filename = f"{base_name}_{j}.md"
                            
                            # 上传到MinIO
                            new_minio_path = _upload_markdown_to_minio(
                                minio_client, part_content, new_filename
                            )
                            
                            if new_minio_path:
                                # 创建新的task_source元素，status为待解析，parse_flag值为1
                                new_item = {
                                    'status': '待解析',
                                    'publish_time': item.get('publish_time', ''),
                                    'parse_flag': 1,
                                    'minio_path': new_minio_path,
                                    'original_filename': new_filename
                                }
                                new_items.append(new_item)
                                success_count += 1
                                logging.info(f"成功创建拆分文件: {new_filename}")
                            else:
                                failed_count += 1
                                logging.error(f"上传拆分文件失败: {new_filename}")
                                
                        except Exception as split_error:
                            logging.error(f"处理切分文件失败: {str(split_error)}")
                            failed_count += 1
                    
                    # 将新创建的元素添加到task_source
                    task_source.extend(new_items)
                    
                    # 将原始记录的parse_flag设置为0
                    item['parse_flag'] = 0
                    logging.info(f"原始文件 {minio_path} 的parse_flag已设置为0")
                    
                except Exception as download_error:
                    logging.error(f"下载文件失败: {str(download_error)}")
                    failed_count += 1
                    
            except Exception as item_error:
                logging.error(f"处理task_source元素失败: {str(item_error)}")
                failed_count += 1
        
        # task_source原有元素都遍历完成后，把task_source修改后的值保存到parse_task_repository数据表里的对应记录中去
        try:
            # 添加调试信息
            logging.info(f"准备更新task_id为{task_id}的任务记录")
            logging.info(f"更新前的task_source长度: {len(task_record.task_source) if task_record.task_source else 0}")
            logging.info(f"更新后的task_source长度: {len(task_source)}")
            
            # 方法1: 使用SQLAlchemy ORM更新
            task_record.task_source = task_source
            db.session.add(task_record)  # 确保对象在会话中
            db.session.flush()  # 强制刷新到数据库
            
            # 提交事务
            db.session.commit()
            
            # 验证更新是否成功
            db.session.refresh(task_record)
            logging.info(f"更新后的task_source长度验证: {len(task_record.task_source) if task_record.task_source else 0}")
            
            # 如果ORM更新失败，尝试直接SQL更新
            if len(task_record.task_source) != len(task_source):
                logging.warning("ORM更新可能失败，尝试直接SQL更新")
                import json
                from sqlalchemy import text
                task_source_json = json.dumps(task_source, ensure_ascii=False)
                update_sql = text(f"UPDATE parse_task_repository SET task_source = '{task_source_json}' WHERE id = {task_id}")
                db.session.execute(update_sql)
                db.session.commit()
                logging.info("直接SQL更新完成")
            
            # 验证更新是否成功 - 直接查询数据库
            try:
                from sqlalchemy import text
                verify_sql = text(f"SELECT task_source FROM parse_task_repository WHERE id = {task_id}")
                result = db.session.execute(verify_sql).fetchone()
                if result:
                    actual_task_source = result[0]
                    logging.info(f"数据库中的实际task_source长度: {len(actual_task_source) if actual_task_source else 0}")
                    if len(actual_task_source) != len(task_source):
                        logging.error(f"数据库更新验证失败！期望长度: {len(task_source)}, 实际长度: {len(actual_task_source)}")
                    else:
                        logging.info("数据库更新验证成功")
                else:
                    logging.error("无法从数据库查询到更新后的记录")
            except Exception as verify_error:
                logging.error(f"验证数据库更新失败: {str(verify_error)}")
            
            logging.info(f"成功更新task_id为{task_id}的任务记录，切分成功{success_count}个文件，失败{failed_count}个")
        except Exception as update_error:
            logging.error(f"更新任务记录失败: {str(update_error)}")
            db.session.rollback()
            return {
                'success': False,
                'message': f'更新数据库失败: {str(update_error)}',
                'data': None
            }
        
        return {
            'success': True,
            'message': f'MD文件切分完成，成功{success_count}个，失败{failed_count}个',
            'data': {
                'success_count': success_count,
                'failed_count': failed_count
            }
        }
        
    except Exception as e:
        error_msg = f"MD文件切分失败: {str(e)}"
        logging.error(error_msg, exc_info=True)
        
        return {
            'success': False,
            'message': error_msg,
            'data': None
        }


def _split_markdown_content(content):
    """
    根据**数字**格式切分MD文件内容
    
    Args:
        content (str): MD文件内容
        
    Returns:
        list: 切分后的内容列表
    """
    import re
    
    # 查找所有**数字**的位置
    pattern = r'\*\*\d+\*\*'
    matches = re.finditer(pattern, content)
    
    positions = []
    for match in matches:
        positions.append(match.start())
    
    if len(positions) < 2:
        # 至少需要两个标记才能切分
        return [content]
    
    # 切分内容
    parts = []
    for i in range(len(positions)):
        if i == 0:
            # 第一个部分：从第一个标记到第二个标记
            start_pos = positions[i]
            if i + 1 < len(positions):
                end_pos = positions[i + 1]
            else:
                # 如果只有一个标记，从第一个标记到文件末尾
                end_pos = len(content)
        elif i == len(positions) - 1:
            # 最后一个部分：从最后一个标记到文件末尾
            start_pos = positions[i]
            end_pos = len(content)
        else:
            # 其他部分：从当前标记到下一个标记
            start_pos = positions[i]
            end_pos = positions[i + 1]
        
        part_content = content[start_pos:end_pos].strip()
        if part_content:
            parts.append(part_content)
    
    return parts


def _upload_markdown_to_minio(minio_client, content, filename):
    """
    将MD文件内容上传到MinIO
    
    Args:
        minio_client: MinIO客户端
        content (str): 文件内容
        filename (str): 文件名
        
    Returns:
        str: 上传后的minio_path，失败返回None
    """
    try:
        # 生成唯一的文件名
        unique_filename = f"{uuid.uuid4().hex}_{filename}"
        minio_path = f"appointment_files/{unique_filename}"
        
        # 上传文件
        minio_client.put_object(
            Bucket=minio_bucket,
            Key=minio_path,
            Body=content.encode('utf-8'),
            ContentType='text/markdown'
        )
        
        # 构建完整的minio_path
        full_minio_path = f"{minio_url}/{minio_bucket}/{minio_path}"
        
        logging.info(f"成功上传MD文件到MinIO: {full_minio_path}")
        return full_minio_path
        
    except Exception as e:
        logging.error(f"上传MD文件到MinIO失败: {str(e)}")
        return None


def _extract_object_key_from_url(minio_url):
    """
    从MinIO完整URL中提取对象键名
    
    Args:
        minio_url (str): 完整的MinIO URL
        
    Returns:
        str: 对象键名，失败时返回None
    """
    try:
        if not minio_url or not isinstance(minio_url, str):
            return None
            
        # 移除协议部分 (http:// 或 https://)
        if minio_url.startswith('https://'):
            url_without_protocol = minio_url[8:]
        elif minio_url.startswith('http://'):
            url_without_protocol = minio_url[7:]
        else:
            # 如果没有协议前缀，假设是相对路径
            url_without_protocol = minio_url
        
        # 分割路径部分
        parts = url_without_protocol.split('/')
        
        # 至少需要包含 host:port/bucket/object
        if len(parts) < 3:
            return None
        
        # 跳过host:port和bucket，获取对象路径
        object_key = '/'.join(parts[2:])
        
        return object_key if object_key else None
        
    except Exception as e:
        logging.error(f"解析MinIO URL失败: {str(e)}")
        return None 


def web_url_crawl(urls):
    """
    从指定URL数组读取网页内容，格式化后返回
    
    Args:
        urls (list): 字符串数组，每个元素为一个网页URL地址
        
    Returns:
        dict: 包含爬取结果的字典，格式如下：
            {
                'success': True/False,
                'message': '处理结果描述',
                'data': {
                    'total_urls': 总URL数量,
                    'success_count': 成功爬取的URL数量,
                    'failed_count': 失败的URL数量,
                    'contents': [
                        {
                            'url': 'URL地址',
                            'data': '网页内容',
                            'status': 'success'
                        }
                    ],
                    'failed_items': [
                        {
                            'url': 'URL地址',
                            'error': '错误信息',
                            'status': 'failed'
                        }
                    ]
                }
            }
    """
    import requests
    import time
    import random
    
    result = {
        'success': False,
        'message': '',
        'data': {
            'total_urls': 0,
            'success_count': 0,
            'failed_count': 0,
            'contents': [],
            'failed_items': []
        }
    }
    
    try:
        # 验证输入参数
        if not urls or not isinstance(urls, list):
            result['message'] = 'urls参数必须是一个非空数组'
            return result
        
        if len(urls) == 0:
            result['message'] = 'urls数组不能为空'
            return result
        
        result['data']['total_urls'] = len(urls)
        logging.info(f"开始爬取网页内容，共 {len(urls)} 个URL")
        
        # 设置请求头，模拟浏览器访问
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
            'Referer': 'https://mp.weixin.qq.com/',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
            'Accept-Encoding': 'gzip, deflate, br',
            'Connection': 'keep-alive',
            'Upgrade-Insecure-Requests': '1'
        }
        
        # 设置请求超时和重试参数
        timeout = 30
        max_retries = 3
        
        # 逐个处理URL
        for i, url in enumerate(urls):
            if not url or not isinstance(url, str):
                logging.warning(f"跳过无效URL (索引 {i}): {url}")
                result['data']['failed_count'] += 1
                result['data']['failed_items'].append({
                    'url': str(url) if url else 'None',
                    'error': '无效的URL格式',
                    'status': 'failed'
                })
                continue
            
            url = url.strip()
            if not url:
                logging.warning(f"跳过空URL (索引 {i})")
                result['data']['failed_count'] += 1
                result['data']['failed_items'].append({
                    'url': '',
                    'error': 'URL为空',
                    'status': 'failed'
                })
                continue
            
            logging.info(f"正在处理第 {i+1}/{len(urls)} 个URL: {url}")
            
            # 尝试爬取网页内容
            success = False
            for retry in range(max_retries):
                try:
                    # 发送HTTP请求
                    response = requests.get(
                        url, 
                        headers=headers, 
                        timeout=timeout,
                        allow_redirects=True
                    )
                    
                    # 检查响应状态
                    if response.status_code == 200:
                        # 获取网页内容
                        content = response.text
                        
                        # 直接使用原始内容，不进行HTML解析和清理
                        try:
                            # 添加到成功结果中，直接使用原始HTML内容
                            result['data']['contents'].append({
                                'url': url,
                                'data': content,
                                'status': 'success',
                                'content_length': len(content),
                                'original_length': len(content),
                                'status_code': response.status_code,
                                'encoding': response.encoding
                            })
                            
                            result['data']['success_count'] += 1
                            success = True
                            logging.info(f"成功爬取URL: {url}, 内容长度: {len(content)}")
                            break
                            
                        except Exception as parse_error:
                            logging.warning(f"解析HTML内容失败，使用原始内容: {str(parse_error)}")
                            result['data']['contents'].append({
                                'url': url,
                                'data': content,
                                'status': 'success',
                                'content_length': len(content),
                                'original_length': len(content),
                                'status_code': response.status_code,
                                'encoding': response.encoding,
                                'note': 'HTML解析失败，使用原始内容'
                            })
                            
                            result['data']['success_count'] += 1
                            success = True
                            logging.info(f"成功爬取URL (使用原始内容): {url}, 内容长度: {len(content)}")
                            break
                            
                    else:
                        error_msg = f"HTTP状态码错误: {response.status_code}"
                        if retry < max_retries - 1:
                            logging.warning(f"URL {url} 请求失败 (重试 {retry+1}/{max_retries}): {error_msg}")
                            time.sleep(random.uniform(1, 3))  # 随机延迟
                            continue
                        else:
                            raise Exception(error_msg)
                            
                except requests.exceptions.Timeout:
                    error_msg = f"请求超时 (timeout={timeout}s)"
                    if retry < max_retries - 1:
                        logging.warning(f"URL {url} 请求超时 (重试 {retry+1}/{max_retries})")
                        time.sleep(random.uniform(2, 5))  # 超时后延迟更长时间
                        continue
                    else:
                        raise Exception(error_msg)
                        
                except requests.exceptions.RequestException as req_error:
                    error_msg = f"请求异常: {str(req_error)}"
                    if retry < max_retries - 1:
                        logging.warning(f"URL {url} 请求异常 (重试 {retry+1}/{max_retries}): {error_msg}")
                        time.sleep(random.uniform(1, 3))
                        continue
                    else:
                        raise Exception(error_msg)
                        
                except Exception as e:
                    error_msg = f"未知错误: {str(e)}"
                    if retry < max_retries - 1:
                        logging.warning(f"URL {url} 发生未知错误 (重试 {retry+1}/{max_retries}): {error_msg}")
                        time.sleep(random.uniform(1, 3))
                        continue
                    else:
                        raise Exception(error_msg)
            
            # 如果所有重试都失败了
            if not success:
                result['data']['failed_count'] += 1
                result['data']['failed_items'].append({
                    'url': url,
                    'error': error_msg,
                    'status': 'failed'
                })
                logging.error(f"URL {url} 爬取失败: {error_msg}")
            
            # 添加随机延迟，避免被反爬虫机制检测
            if i < len(urls) - 1:  # 最后一个URL不需要延迟
                delay = random.uniform(0.5, 2.0)
                logging.debug(f"等待 {delay:.2f} 秒后继续下一个URL")
                time.sleep(delay)
        
        # 设置最终结果
        if result['data']['success_count'] > 0:
            result['success'] = True
            if result['data']['failed_count'] == 0:
                result['message'] = f'成功爬取所有 {result["data"]["success_count"]} 个URL'
            else:
                result['message'] = f'部分成功: 成功爬取 {result["data"]["success_count"]} 个URL，失败 {result["data"]["failed_count"]} 个URL'
        else:
            result['message'] = f'所有URL爬取失败，共 {result["data"]["failed_count"]} 个URL'
        
        logging.info(f"网页爬取完成: {result['message']}")
        return result
        
    except Exception as e:
        error_msg = f"网页爬取过程中发生错误: {str(e)}"
        logging.error(error_msg, exc_info=True)
        result['message'] = error_msg
        return result