"""
简历解析模块

该模块提供简历文件的解析功能，支持PDF格式的简历文档解析。
"""

import logging
from datetime import datetime
import json
import os
import uuid
import base64
from typing import Dict, Any, Optional, List
from app.core.data_parse.time_utils import get_east_asia_time_str, get_east_asia_timestamp, get_east_asia_isoformat, get_east_asia_date_str
import PyPDF2
from openai import OpenAI
import boto3
from botocore.config import Config
from app.config.config import DevelopmentConfig, ProductionConfig

# 使用配置变量
config = ProductionConfig()

# MinIO 配置
minio_url = f"{'https' if config.MINIO_SECURE else 'http'}://{config.MINIO_HOST}"
minio_access_key = config.MINIO_USER
minio_secret_key = config.MINIO_PASSWORD
minio_bucket = config.MINIO_BUCKET


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 format_date_to_yyyy_mm_dd(raw_date):
    """
    将各种日期格式转换为YYYY-MM-DD格式
    
    Args:
        raw_date (str): 原始日期字符串
        
    Returns:
        str: 格式化后的日期字符串，格式为YYYY-MM-DD
    """
    if not raw_date:
        return ''
    
    import re
    from datetime import datetime
    
    # 移除多余的空格
    raw_date = raw_date.strip()
    
    # 匹配常见的日期格式
    patterns = [
        # 匹配 YYYY.MM 格式，如 2023.11
        r'(\d{4})\.(\d{1,2})',
        # 匹配 YYYY-MM 格式，如 2023-11
        r'(\d{4})-(\d{1,2})',
        # 匹配 YYYY年MM月 格式，如 2023年11月
        r'(\d{4})年(\d{1,2})月',
        # 匹配 YYYY/MM 格式，如 2023/11
        r'(\d{4})/(\d{1,2})',
        # 匹配 YYYYMM 格式，如 202311
        r'(\d{4})(\d{2})',
        # 匹配 YYYY.MM.DD 格式，如 2023.11.01
        r'(\d{4})\.(\d{1,2})\.(\d{1,2})',
        # 匹配 YYYY-MM-DD 格式，如 2023-11-01
        r'(\d{4})-(\d{1,2})-(\d{1,2})',
        # 匹配 YYYY年MM月DD日 格式，如 2023年11月1日
        r'(\d{4})年(\d{1,2})月(\d{1,2})日',
        # 匹配 YYYY/MM/DD 格式，如 2023/11/01
        r'(\d{4})/(\d{1,2})/(\d{1,2})'
    ]
    
    for pattern in patterns:
        match = re.match(pattern, raw_date)
        if match:
            groups = match.groups()
            year = groups[0]
            month = groups[1].zfill(2)  # 补齐两位数
            
            if len(groups) == 2:
                # 只有年月，添加01作为日期
                day = '01'
            else:
                # 有年月日
                day = groups[2].zfill(2)  # 补齐两位数
            
            # 验证日期有效性
            try:
                datetime(int(year), int(month), int(day))
                return f"{year}-{month}-{day}"
            except ValueError:
                continue
    
    # 如果没有匹配到任何格式，返回原始值
    logging.warning(f"无法解析日期格式: {raw_date}")
    return raw_date


def standardize_career_entry(entry):
    """
    标准化career_path条目格式
    
    Args:
        entry: 原始条目数据
        
    Returns:
        dict: 标准化后的条目
    """
    if not isinstance(entry, dict):
        entry = {}
    
    return {
        "date": entry.get('date', ''),
        "hotel_en": entry.get('hotel_en', ''),
        "hotel_zh": entry.get('hotel_zh', ''),
        "image_path": entry.get('image_path', ''),
        "source": entry.get('source', 'resume_extraction'),
        "title_en": entry.get('title_en', ''),
        "title_zh": entry.get('title_zh', '')
    }


def parse_resume_with_qwen(file_path: str) -> Dict[str, Any]:
    """
    使用阿里云千问大模型解析简历PDF文档
    
    Args:
        file_path (str): PDF文件路径或MinIO URL
        
    Returns:
        Dict[str, Any]: 解析结果
    """
    # 阿里云 Qwen API 配置
    QWEN_API_KEY = os.environ.get('QWEN_API_KEY', 'sk-8f2320dafc9e4076968accdd8eebd8e9')
    
    try:
        # 初始化 OpenAI 客户端，配置为阿里云 API
        client = OpenAI(
            api_key=QWEN_API_KEY,
            base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
        )
        
        # 构建针对简历解析的专业提示语
        prompt = """你是企业简历的信息提取专家。请仔细分析提供的简历文本，精确提取名片相关信息。
请从上传的简历文本中提取以下结构化信息，严格按JSON格式输出：
{
  "basic_info": {
    "中文姓名": "",
    "英文姓名": "",
    "中文头衔": "",
    "英文头衔": "",
    "中文酒店": "",
    "英文酒店": "",
    "手机号": "",
    "邮箱": "",
    "中文工作地址": "",
    "英文工作地址": "",
    "生日": "",   
    "年龄": "",
    "籍贯": "",
    "性别": "",
    "居住地": "",
    "品牌": "",
    "隶属关系": "",
    "品牌组合": ""
  },
  "work_experience": [
    {
      "任职时间": "",
      "中文酒店": "",
      "英文酒店": "",
      "中文职位": "",
      "英文职位": ""
    }
  ]
}
提取要求：
1. 中文优先，双语内容保留中文和英文,纯英文内容不需要翻译成中文，直接使用英文内容保存到英文字段
2. 工作经历按倒序排列，只需要提取开始时间作为任职时间
3. 如果提供了性别，则按照男/女进行填写，如果是男/女以外的内容，则按照空字符串进行填写
4. basic_info中的酒店和头衔按照工作经历提取的最近那一个工作酒店和头衔进行填写
5. 其他信息忽略，不需要写入JSON。
6. 如果简历中没有工作经历，则不提取工作经历。
"""
        
        # 准备文件内容并提取文本
        file_name = None
        resume_text = ""
        
        # 检查是否是MinIO URL
        if file_path.startswith('http://') or file_path.startswith('https://'):
            # 处理MinIO URL
            from urllib.parse import urlparse
            
            # 获取MinIO客户端
            minio_client = get_minio_client()
            if not minio_client:
                raise Exception('无法连接到MinIO服务器')
            
            # 提取对象键
            parsed_url = urlparse(file_path)
            path_parts = parsed_url.path.strip('/').split('/', 1)
            if len(path_parts) < 2:
                raise Exception(f'无效的MinIO URL格式: {file_path}')
            
            object_key = path_parts[1]  # 跳过bucket名称
            file_name = os.path.basename(parsed_url.path)
            
            # 从MinIO获取PDF数据并提取文本
            try:
                response = minio_client.get_object(Bucket=minio_bucket, Key=object_key)
                pdf_data = response['Body'].read()
                
                # 使用PyPDF2提取PDF文本
                from io import BytesIO
                pdf_reader = PyPDF2.PdfReader(BytesIO(pdf_data))
                for page_num, page in enumerate(pdf_reader.pages):
                    try:
                        page_text = page.extract_text()
                        if page_text:
                            resume_text += f"\n=== 第{page_num + 1}页 ===\n{page_text}\n"
                    except Exception as e:
                        logging.warning(f"提取第{page_num + 1}页文本失败: {str(e)}")
                        continue
                        
            except Exception as minio_error:
                raise Exception(f'从MinIO获取PDF失败: {str(minio_error)}')
        else:
            # 处理本地文件路径
            if not os.path.exists(file_path):
                raise Exception(f'文件不存在: {file_path}')
            
            file_name = os.path.basename(file_path)
            
            # 使用PyPDF2提取PDF文本
            with open(file_path, 'rb') as file:
                pdf_reader = PyPDF2.PdfReader(file)
                for page_num, page in enumerate(pdf_reader.pages):
                    try:
                        page_text = page.extract_text()
                        if page_text:
                            resume_text += f"\n=== 第{page_num + 1}页 ===\n{page_text}\n"
                    except Exception as e:
                        logging.warning(f"提取第{page_num + 1}页文本失败: {str(e)}")
                        continue
        
        # 检查是否成功提取到文本
        if not resume_text or len(resume_text.strip()) < 50:
            raise Exception('PDF文本提取失败，可能是扫描版PDF或文本质量较差')
        
        # 构建完整的提示语
        full_prompt = prompt + "\n\n以下是需要分析的简历文本内容：\n\n" + resume_text
        
        # 调用 Qwen API，发送文本内容
        logging.info(f"发送PDF文本内容到 Qwen 模型进行解析: {file_name}")
        completion = client.chat.completions.create(
            model="qwen-long-latest",  # 使用qwen-long-latest模型
            messages=[
                {
                    "role": "user",
                    "content": full_prompt
                }
            ],
            temperature=0.1,  # 降低温度增加精确性
            response_format={"type": "json_object"}  # 要求输出JSON格式
        )
        
        # 解析响应
        response_content = completion.choices[0].message.content
        logging.info(f"成功从 Qwen 模型获取简历解析响应")
        
        # 直接解析 Qwen 返回的 JSON 响应
        try:
            if not response_content:
                raise Exception("API返回内容为空")
            qwen_response = json.loads(response_content)
            logging.info(f"成功解析 Qwen 简历响应中的 JSON: {qwen_response}")
        except json.JSONDecodeError as e:
            error_msg = f"JSON 解析失败: {str(e)}"
            logging.error(error_msg)
            raise Exception(error_msg)
        
        # 从新的响应格式中提取并映射字段
        parsed_resume = {}
        
        # 提取 basic_info 中的字段
        basic_info = qwen_response.get('basic_info', {})
        
        # 映射中文字段到英文字段
        field_mapping = {
            '中文姓名': 'name_zh',
            '英文姓名': 'name_en',
            '中文头衔': 'title_zh',
            '英文头衔': 'title_en',
            '中文酒店': 'hotel_zh',
            '英文酒店': 'hotel_en',
            '手机号': 'mobile',
            '邮箱': 'email',
            '中文工作地址': 'address_zh',
            '英文工作地址': 'address_en',
            '生日': 'birthday',
            '年龄': 'age',
            '籍贯': 'native_place',
            '性别': 'gender',
            '居住地': 'residence',
            '品牌': 'brand',
            '隶属关系': 'affiliation',
            '品牌组合': 'brand_group'
        }
        
        # 执行字段映射
        for chinese_field, english_field in field_mapping.items():
            value = basic_info.get(chinese_field, '')
            if value:
                # 特殊处理年龄字段，提取数字
                if english_field == 'age':
                    import re
                    age_match = re.search(r'(\d+)', str(value))
                    if age_match:
                        parsed_resume[english_field] = age_match.group(1)
                    else:
                        parsed_resume[english_field] = ''
                else:
                    parsed_resume[english_field] = value
            else:
                # 设置默认值
                if english_field in ['career_path', 'affiliation']:
                    parsed_resume[english_field] = []
                elif english_field == 'age':
                    parsed_resume[english_field] = ''
                else:
                    parsed_resume[english_field] = ""
        
        # 处理 work_experience 映射到 career_path
        work_experience = qwen_response.get('work_experience', [])
        if work_experience and isinstance(work_experience, list):
            career_path = []
            for work_item in work_experience:
                if isinstance(work_item, dict):
                    # 格式化日期为YYYY-MM-DD格式
                    raw_date = work_item.get('任职时间', '')
                    formatted_date = format_date_to_yyyy_mm_dd(raw_date)
                    
                    career_entry = {
                        "date": formatted_date,
                        "hotel_en": work_item.get('英文酒店', ''),
                        "hotel_zh": work_item.get('中文酒店', ''),
                        "image_path": '',
                        "source": 'resume_extraction',
                        "title_en": work_item.get('英文职位', ''),
                        "title_zh": work_item.get('中文职位', '')
                    }
                    career_entry = standardize_career_entry(career_entry)
                    career_path.append(career_entry)
            
            parsed_resume['career_path'] = career_path
            logging.info(f"成功映射 {len(career_path)} 条工作经历到 career_path")
        else:
            parsed_resume['career_path'] = []
            logging.info("未找到工作经历信息，career_path设为空数组")
        
        # 确保所有必要字段存在
        required_fields = [
            'name_zh', 'name_en', 'title_zh', 'title_en', 
            'hotel_zh', 'hotel_en', 'mobile', 'phone', 
            'email', 'address_zh', 'address_en',
            'postal_code_zh', 'postal_code_en', 'birthday', 'age', 'native_place', 'residence', 'gender',
            'brand_group', 'career_path', 'affiliation'
        ]
        
        for field in required_fields:
            if field not in parsed_resume:
                if field in ['career_path', 'affiliation']:
                    parsed_resume[field] = []
                elif field == 'age':
                    parsed_resume[field] = ''
                else:
                    parsed_resume[field] = ""
        
        # 处理 affiliation 字段（如果从basic_info中提取到）
        if parsed_resume.get('affiliation') and not isinstance(parsed_resume['affiliation'], list):
            # 如果affiliation是字符串，转换为数组格式
            affiliation_str = parsed_resume['affiliation']
            if affiliation_str:
                parsed_resume['affiliation'] = [{"company": "", "group": affiliation_str}]
            else:
                parsed_resume['affiliation'] = []
        
        # 为affiliation增加记录（如果提取到公司信息）
        if parsed_resume.get('hotel_zh') or parsed_resume.get('hotel_en'):
            company_name = parsed_resume.get('hotel_zh') or parsed_resume.get('hotel_en')
            affiliation_entry = {
                "company": company_name,
                "group": ""
            }
            
            # 如果原有affiliation为空或不是数组，则重新设置
            if not isinstance(parsed_resume.get('affiliation'), list) or not parsed_resume['affiliation']:
                parsed_resume['affiliation'] = [affiliation_entry]
                logging.info(f"为简历解析结果设置了affiliation记录: {affiliation_entry}")
            else:
                # 检查是否已存在相同公司的记录
                company_exists = any(
                    aff.get('company') == company_name 
                    for aff in parsed_resume['affiliation'] 
                    if isinstance(aff, dict)
                )
                if not company_exists:
                    parsed_resume['affiliation'].append(affiliation_entry)
                    logging.info(f"为简历解析结果添加了affiliation记录: {affiliation_entry}")
        
        return parsed_resume
        
    except Exception as e:
        error_msg = f"Qwen 模型简历解析失败: {str(e)}"
        logging.error(error_msg, exc_info=True)
        raise Exception(error_msg)


def parse_resume_file(file_path: str, task_id: Optional[str] = None) -> Dict[str, Any]:
    """
    解析简历文件，支持本地路径和MinIO URL
    
    Args:
        file_path (str): 简历文件路径或MinIO URL
        task_id (str, optional): 关联的任务ID
        
    Returns:
        Dict[str, Any]: 解析结果
    """
    try:
        logging.info(f"开始解析简历文件: {file_path}")
        
        # 验证文件格式和存在性
        if not validate_resume_format(file_path):
            # 检查是否是MinIO URL
            if file_path.startswith('http://') or file_path.startswith('https://'):
                from urllib.parse import urlparse
                parsed_url = urlparse(file_path)
                file_ext = os.path.splitext(parsed_url.path)[1].lower()
                if file_ext != '.pdf':
                    return {
                        'success': False,
                        'error': f'不支持的文件格式: {file_ext}，仅支持PDF格式',
                        'data': None
                    }
                else:
                    return {
                        'success': False,
                        'error': f'文件不存在: {file_path}',
                        'data': None
                    }
            else:
                # 本地文件路径
                if not os.path.exists(file_path):
                    return {
                        'success': False,
                        'error': f'文件不存在: {file_path}',
                        'data': None
                    }
                
                file_ext = os.path.splitext(file_path)[1].lower()
                if file_ext != '.pdf':
                    return {
                        'success': False,
                        'error': f'不支持的文件格式: {file_ext}，仅支持PDF格式',
                        'data': None
                    }
        
        # 步骤1: 获取文件基本信息
        logging.info("开始获取文件基本信息")
        page_count = 0
        file_size = 0
        
        try:
            if file_path.startswith('http://') or file_path.startswith('https://'):
                # 对于MinIO URL，获取文件信息
                from urllib.parse import urlparse
                minio_client = get_minio_client()
                if minio_client:
                    parsed_url = urlparse(file_path)
                    path_parts = parsed_url.path.strip('/').split('/', 1)
                    if len(path_parts) >= 2:
                        object_key = path_parts[1]
                        try:
                            response = minio_client.head_object(Bucket=minio_bucket, Key=object_key)
                            file_size = response.get('ContentLength', 0)
                        except Exception:
                            file_size = 0
            else:
                # 本地文件
                file_size = os.path.getsize(file_path)
        except Exception:
            file_size = 0
        
        # 步骤2: 使用千问大模型直接解析PDF文档
        logging.info("开始使用千问大模型解析PDF文档")
        try:
            parsed_data = parse_resume_with_qwen(file_path)
            logging.info("千问大模型解析完成")
        except Exception as e:
            return {
                'success': False,
                'error': f"大模型解析失败: {str(e)}",
                'data': None
            }
        
        # 步骤3: 构建完整的解析结果
        parse_result = {
            **parsed_data,  # 包含所有千问解析的结果
            'parse_time': get_east_asia_isoformat(),
            'file_info': {
                'original_path': file_path,
                'file_size': file_size,
                'file_type': 'pdf',
                'page_count': page_count,
                'text_length': 0  # 直接使用大模型解析，不提取文本
            },
            'extraction_info': {
                'extraction_method': 'Qwen-Long-Latest PDF解析',
                'text_extract_success': True,
                'ai_parse_success': True,
                'task_id': task_id
            }
        }
        
        logging.info(f"简历文件解析完成: {file_path}")
        
        return {
            'success': True,
            'error': None,
            'data': parse_result
        }
        
    except Exception as e:
        error_msg = f"解析简历文件失败: {str(e)}"
        logging.error(error_msg, exc_info=True)
        
        return {
            'success': False,
            'error': error_msg,
            'data': None
        }


def extract_resume_text(file_path: str) -> Dict[str, Any]:
    """
    提取简历文本内容，支持本地路径和MinIO URL
    
    Args:
        file_path (str): 简历文件路径或MinIO URL
        
    Returns:
        Dict[str, Any]: 提取结果
    """
    try:
        logging.info(f"开始提取PDF文本: {file_path}")
        
        text_content = ""
        page_count = 0
        
        # 检查是否是MinIO URL
        if file_path.startswith('http://') or file_path.startswith('https://'):
            # 处理MinIO URL
            from urllib.parse import urlparse
            from io import BytesIO
            
            # 获取MinIO客户端
            minio_client = get_minio_client()
            if not minio_client:
                return {
                    'success': False,
                    'error': '无法连接到MinIO服务器',
                    'text_content': None,
                    'page_count': 0
                }
            
            # 提取对象键
            parsed_url = urlparse(file_path)
            path_parts = parsed_url.path.strip('/').split('/', 1)
            if len(path_parts) < 2:
                return {
                    'success': False,
                    'error': f'无效的MinIO URL格式: {file_path}',
                    'text_content': None,
                    'page_count': 0
                }
            
            object_key = path_parts[1]  # 跳过bucket名称
            
            # 从MinIO获取PDF数据
            try:
                response = minio_client.get_object(Bucket=minio_bucket, Key=object_key)
                pdf_data = response['Body'].read()
                
                # 使用PyPDF2提取PDF文本
                pdf_reader = PyPDF2.PdfReader(BytesIO(pdf_data))
                page_count = len(pdf_reader.pages)
                
                for page_num, page in enumerate(pdf_reader.pages):
                    try:
                        page_text = page.extract_text()
                        if page_text:
                            text_content += f"\n=== 第{page_num + 1}页 ===\n{page_text}\n"
                        else:
                            logging.warning(f"第{page_num + 1}页无法提取文本")
                    except Exception as e:
                        logging.warning(f"提取第{page_num + 1}页文本失败: {str(e)}")
                        continue
                        
            except Exception as minio_error:
                return {
                    'success': False,
                    'error': f'从MinIO获取PDF失败: {str(minio_error)}',
                    'text_content': None,
                    'page_count': 0
                }
        else:
            # 处理本地文件路径
            # 使用PyPDF2提取PDF文本
            with open(file_path, 'rb') as file:
                pdf_reader = PyPDF2.PdfReader(file)
                page_count = len(pdf_reader.pages)
                
                for page_num, page in enumerate(pdf_reader.pages):
                    try:
                        page_text = page.extract_text()
                        if page_text:
                            text_content += f"\n=== 第{page_num + 1}页 ===\n{page_text}\n"
                        else:
                            logging.warning(f"第{page_num + 1}页无法提取文本")
                    except Exception as e:
                        logging.warning(f"提取第{page_num + 1}页文本失败: {str(e)}")
                        continue
        
        # 清理文本内容
        text_content = text_content.strip()
        
        if not text_content:
            # 如果PyPDF2无法提取文本，尝试将PDF转换为图片并进行OCR
            logging.warning("PyPDF2无法提取文本，PDF可能是扫描版或图像格式")
            return {
                'success': False,
                'error': 'PDF文本提取失败，可能是扫描版PDF，需要OCR处理',
                'text_content': None,
                'page_count': page_count
            }
        
        logging.info(f"成功提取PDF文本，共{page_count}页，文本长度: {len(text_content)}字符")
        
        return {
            'success': True,
            'text_content': text_content,
            'page_count': page_count
        }
        
    except Exception as e:
        logging.error(f"提取简历文本失败: {str(e)}", exc_info=True)
        return {
            'success': False,
            'error': str(e),
            'text_content': None,
            'page_count': 0
        }


def _get_filename_from_path(file_path: str) -> str:
    """
    从文件路径或MinIO URL中提取文件名
    
    Args:
        file_path (str): 文件路径或MinIO URL
        
    Returns:
        str: 文件名
    """
    try:
        if file_path.startswith('http://') or file_path.startswith('https://'):
            # 从MinIO URL中提取文件名
            from urllib.parse import urlparse
            parsed_url = urlparse(file_path)
            return os.path.basename(parsed_url.path)
        else:
            # 从本地路径中提取文件名
            return os.path.basename(file_path)
    except Exception:
        return 'unknown_file.pdf'


def validate_resume_format(file_path: str) -> bool:
    """
    验证简历文件格式，支持本地路径和MinIO URL
    
    Args:
        file_path (str): 文件路径或MinIO URL
        
    Returns:
        bool: 是否为有效的简历格式
    """
    try:
        # 检查是否是MinIO URL
        if file_path.startswith('http://') or file_path.startswith('https://'):
            # 处理MinIO URL
            from urllib.parse import urlparse
            
            # 从URL提取文件扩展名
            parsed_url = urlparse(file_path)
            file_ext = os.path.splitext(parsed_url.path)[1].lower()
            if file_ext != '.pdf':
                return False
            
            # 验证文件是否存在于MinIO中
            try:
                minio_client = get_minio_client()
                if not minio_client:
                    return False
                
                # 提取对象键
                path_parts = parsed_url.path.strip('/').split('/', 1)
                if len(path_parts) < 2:
                    return False
                
                object_key = path_parts[1]  # 跳过bucket名称
                
                # 检查文件是否存在
                response = minio_client.head_object(Bucket=minio_bucket, Key=object_key)
                return True
            except Exception:
                return False
        else:
            # 处理本地文件路径
            if not os.path.exists(file_path):
                return False
                
            file_ext = os.path.splitext(file_path)[1].lower()
            return file_ext == '.pdf'
        
    except Exception as e:
        logging.error(f"验证简历格式失败: {str(e)}")
        return False


def batch_parse_resumes(file_paths: List[str], task_id=None, task_type=None) -> Dict[str, Any]:
    """
    批量解析简历文件，从parse_task_repository表读取任务记录进行处理
    
    Args:
        file_paths (List[str]): 简历文件路径列表（已废弃，现在从数据库读取）
        task_id (str, optional): 任务ID，用于从数据库读取task_source
        task_type (str, optional): 任务类型
        
    Returns:
        Dict[str, Any]: 批量解析结果，格式与parse_result保持一致
    """
    try:
        # 根据task_id从parse_task_repository表读取记录
        if not task_id:
            return {
                'code': 400,
                'success': False,
                'message': '缺少task_id参数',
                'data': None
            }
        
        # 导入数据库模型
        from app.models.parse_models import ParseTaskRepository
        from app import db
        
        # 查询对应的任务记录
        task_record = ParseTaskRepository.query.get(task_id)
        if not task_record:
            return {
                'code': 404,
                'success': False,
                'message': f'未找到task_id为{task_id}的任务记录',
                'data': None
            }
        
        # 获取task_source作为需要处理的数据列表
        task_source = task_record.task_source
        if not task_source or not isinstance(task_source, list):
            return {
                'code': 400,
                'success': False,
                'message': 'task_source为空或格式不正确',
                'data': None
            }
        
        results = []
        success_count = 0
        failed_count = 0
        parsed_record_ids = []  # 收集成功解析的记录ID
        
        logging.info(f"开始批量解析简历文件，共 {len(task_source)} 个文件")
        
        # 逐一处理每个task_source元素
        for i, item in enumerate(task_source):
            try:
                # 检查parse_flag，只有值为1的才需要处理
                if not isinstance(item, dict) or item.get('parse_flag') != 1:
                    continue
                
                # 从文件项中获取minio_path和original_filename
                minio_path = item.get('minio_path', '')
                original_filename = item.get('original_filename', f'resume_{i}.pdf')
                
                if not minio_path:
                    failed_count += 1
                    # 更新task_source中对应记录的状态
                    item['parse_flag'] = 1
                    item['status'] = '解析失败'
                    results.append({
                        'index': i,
                        'minio_path': str(item),
                        'success': False,
                        'error': f'字典中缺少minio_path字段: {item}',
                        'data': None
                    })
                    continue
                
                logging.info(f"处理第 {i+1}/{len(file_paths)} 个文件: {minio_path}")
                
                result = parse_resume_file(minio_path)
                
                if result.get('success', False):
                    # 提取并转换为标准名片格式
                    resume_data = result.get('data', {})
                    
                    # 构建符合规范的名片格式数据
                    standardized_data = {
                        "address_en": resume_data.get('address_en', ''),
                        "address_zh": resume_data.get('address_zh', ''),
                        "affiliation": resume_data.get('affiliation', []),
                        "age": resume_data.get('age', ''),
                        "birthday": resume_data.get('birthday', ''),
                        "gender": resume_data.get('gender', ''),
                        "brand_group": resume_data.get('brand_group', ''),
                        "career_path": resume_data.get('career_path', []),
                        "email": resume_data.get('email', ''),
                        "hotel_en": resume_data.get('hotel_en', ''),
                        "hotel_zh": resume_data.get('hotel_zh', ''),
                        "mobile": resume_data.get('mobile', ''),
                        "name_en": resume_data.get('name_en', ''),
                        "name_zh": resume_data.get('name_zh', ''),
                        "native_place": resume_data.get('native_place', ''),
                        "phone": resume_data.get('phone', ''),
                        "postal_code_en": resume_data.get('postal_code_en', ''),
                        "postal_code_zh": resume_data.get('postal_code_zh', ''),
                        "residence": resume_data.get('residence', ''),
                        "title_en": resume_data.get('title_en', ''),
                        "title_zh": resume_data.get('title_zh', '')
                    }
                    
                    # 调用get_brand_group_by_hotel获取品牌和集团信息
                    if standardized_data.get('hotel_zh'):
                        try:
                            from app.core.data_parse.parse_system import get_brand_group_by_hotel
                            brand_result = get_brand_group_by_hotel(standardized_data['hotel_zh'])
                            if brand_result.get('success') and brand_result.get('data'):
                                brand_data = brand_result['data']
                                # 赋值品牌和集团信息
                                standardized_data['brand_zh'] = brand_data.get('brand_name_zh', '')
                                standardized_data['brand_en'] = brand_data.get('brand_name_en', '')
                                standardized_data['affiliation_zh'] = brand_data.get('group_name_zh', '')
                                standardized_data['affiliation_en'] = brand_data.get('group_name_en', '')
                                logging.info(f"成功获取品牌和集团信息: {brand_data}")
                            else:
                                logging.warning(f"获取品牌信息失败: {brand_result.get('message', '')}")
                                # 设置默认值
                                standardized_data['brand_zh'] = ''
                                standardized_data['brand_en'] = ''
                                standardized_data['affiliation_zh'] = ''
                                standardized_data['affiliation_en'] = ''
                        except Exception as brand_error:
                            logging.error(f"调用get_brand_group_by_hotel失败: {str(brand_error)}")
                            # 设置默认值
                            standardized_data['brand_zh'] = ''
                            standardized_data['brand_en'] = ''
                            standardized_data['affiliation_zh'] = ''
                            standardized_data['affiliation_en'] = ''
                    else:
                        # 没有酒店信息，设置默认值
                        standardized_data['brand_zh'] = ''
                        standardized_data['brand_en'] = ''
                        standardized_data['affiliation_zh'] = ''
                        standardized_data['affiliation_en'] = ''
                    
                    # 记录成功解析的人才信息到parsed_talents表
                    try:
                        from app.core.data_parse.parse_task import record_parsed_talent
                        # 在记录到parsed_talents表之前，设置image_path和origin_source
                        standardized_data['image_path'] = minio_path
                        
                        # 设置origin_source为JSON数组格式
                        current_date = get_east_asia_date_str()
                        origin_source_entry = {
                            "task_type": "简历",
                            "minio_path": minio_path,
                            "source_date": current_date
                        }
                        standardized_data['origin_source'] = [origin_source_entry]
                        
                        # 更新career_path中记录的image_path字段
                        if standardized_data.get('career_path') and isinstance(standardized_data['career_path'], list):
                            for career_entry in standardized_data['career_path']:
                                if isinstance(career_entry, dict):
                                    career_entry['image_path'] = minio_path
                        
                        record_result = record_parsed_talent(standardized_data, task_id, task_type)
                        if record_result.get('success'):
                            # 收集成功解析的记录ID
                            parsed_record = record_result.get('data', {})
                            if parsed_record and 'id' in parsed_record:
                                parsed_record_ids.append(str(parsed_record['id']))
                            logging.info(f"成功记录人才信息到parsed_talents表: {standardized_data.get('name_zh', '')}")
                            
                            # 更新task_source中对应记录的状态
                            item['parse_flag'] = 0
                            item['status'] = '解析成功'
                        else:
                            logging.warning(f"记录人才信息失败: {record_result.get('message', '')}")
                            # 更新task_source中对应记录的状态
                            item['parse_flag'] = 1
                            item['status'] = '解析失败'
                    except Exception as record_error:
                        logging.error(f"调用record_parsed_talent函数失败: {str(record_error)}")
                        # 更新task_source中对应记录的状态
                        item['parse_flag'] = 1
                        item['status'] = '解析失败'
                    
                    success_count += 1
                    # 构建完整的MinIO URL路径
                    relative_path = f"resume_files/{original_filename}"
                    complete_minio_path = minio_path if minio_path.startswith('http') else f"{minio_url}/{minio_bucket}/{relative_path}"
                    
                    results.append({
                        "data": standardized_data,
                        "error": None,
                        "filename": original_filename,
                        "index": i,
                        "message": "简历文件解析成功",
                        "minio_path": complete_minio_path,
                        "object_key": relative_path,
                        "success": True
                    })
                    logging.info(f"成功处理第 {i+1} 个文件: {original_filename}")
                else:
                    failed_count += 1
                    # 更新task_source中对应记录的状态
                    item['parse_flag'] = 1
                    item['status'] = '解析失败'
                    # 构建完整的MinIO URL路径
                    relative_path = f"resume_files/{original_filename}"
                    complete_minio_path = minio_path if minio_path.startswith('http') else f"{minio_url}/{minio_bucket}/{relative_path}"
                    
                    results.append({
                        "data": None,
                        "error": result.get('error', '处理失败'),
                        "filename": original_filename,
                        "index": i,
                        "message": "简历文件解析失败",
                        "minio_path": complete_minio_path,
                        "object_key": relative_path,
                        "success": False
                    })
                    logging.error(f"处理第 {i+1} 个文件失败: {result.get('error', '未知错误')}")
                    
            except Exception as item_error:
                failed_count += 1
                error_msg = f"处理简历文件失败: {str(item_error)}"
                logging.error(error_msg, exc_info=True)
                # 更新task_source中对应记录的状态
                item['parse_flag'] = 1
                item['status'] = '解析失败'
                # 构建完整的MinIO URL路径
                relative_path = f"resume_files/{original_filename}"
                complete_minio_path = minio_path if minio_path.startswith('http') else f"{minio_url}/{minio_bucket}/{relative_path}"
                
                results.append({
                    "data": None,
                    "error": error_msg,
                    "filename": original_filename,
                    "index": i,
                    "message": "简历文件解析失败",
                    "minio_path": complete_minio_path,
                    "object_key": relative_path,
                    "success": False
                })
        
        # 根据处理结果更新task_status
        if failed_count == 0:
            task_status = '解析成功'
        elif success_count == 0:
            task_status = '解析失败'
        else:
            task_status = '部分解析成功'
        
        # 所有task_source记录处理完成后，将更新后的task_source和task_status保存到数据库
        try:
            task_record.task_source = task_source
            task_record.task_status = task_status
            task_record.parse_count = success_count
            task_record.parse_result = {
                'success_count': success_count,
                'failed_count': failed_count,
                'parsed_record_ids': parsed_record_ids,
                'processed_time': get_east_asia_isoformat()
            }
            db.session.commit()
            logging.info(f"成功更新task_id为{task_id}的任务记录，task_status={task_status}，处理成功{success_count}条，失败{failed_count}条")
        except Exception as update_error:
            logging.error(f"更新任务记录失败: {str(update_error)}")
            db.session.rollback()
        
        # 组装最终结果
        if failed_count == 0:
            return {
                'code': 200,
                'success': True,
                'message': f'批量处理完成，全部 {success_count} 个文件处理成功',
                'data': {
                    'success_count': success_count,
                    'failed_count': failed_count,
                    'parsed_record_ids': parsed_record_ids
                }
            }
        elif success_count == 0:
            return {
                'code': 500,
                'success': False,
                'message': f'批量处理失败，全部 {failed_count} 个文件处理失败',
                'data': {
                    'success_count': success_count,
                    'failed_count': failed_count,
                    'parsed_record_ids': parsed_record_ids
                }
            }
        else:
            return {
                'code': 206,  # Partial Content
                'success': True,
                'message': f'批量处理部分成功，成功 {success_count} 个，失败 {failed_count} 个',
                'data': {
                    'success_count': success_count,
                    'failed_count': failed_count,
                    'parsed_record_ids': parsed_record_ids
                }
            }
            
    except Exception as e:
        error_msg = f"批量解析简历失败: {str(e)}"
        logging.error(error_msg, exc_info=True)
        
        batch_result = {
            'summary': {
                'total_files': len(file_paths) if file_paths else 1,
                'success_count': 0,
                'failed_count': len(file_paths) if file_paths else 1,
                'success_rate': 0
            },
            'results': [],
            'processed_time': get_east_asia_isoformat()
        }
        
        return {
            'code': 500,
            'success': False,
            'message': error_msg,
            'data': batch_result
        } 