from typing import Dict, Any
from app import db
from datetime import datetime
import os
import boto3
from botocore.config import Config
import logging
import uuid
import json
import re
from io import BytesIO
from werkzeug.datastructures import FileStorage
from app.config.config import DevelopmentConfig, ProductionConfig
import base64

# 导入原有的函数和模型
from app.core.data_parse.parse_system import (
    BusinessCard, DuplicateBusinessCard,
    parse_text_with_qwen25VLplus, check_duplicate_business_card,
    update_career_path, create_main_card_with_duplicates,
    create_origin_source_entry, update_origin_source
)
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

from openai import OpenAI  # 添加此行以导入 OpenAI 客户端

# 使用配置变量,缺省认为在生产环境运行
config = ProductionConfig()
# 使用配置变量
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
use_ssl = config.MINIO_SECURE


def get_minio_client():
    """获取MinIO客户端连接"""
    try:
        # 使用全局配置变量
        global minio_url, minio_access_key, minio_secret_key, minio_bucket, use_ssl
        
        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_business_card_image(image_file):
    """
    处理名片图片并提取信息（仅负责图片解析部分）
    
    Args:
        image_file (FileStorage): 上传的名片图片文件
        
    Returns:
        dict: 图片解析结果，包含提取的信息和状态
    """
    try:
        # 读取图片数据
        image_data = image_file.read()
        image_file.seek(0)  # 重置文件指针以便后续读取
        
        try:
            # 优先使用 Qwen 2.5 VL Plus 模型直接从图像提取信息
            try:
                logging.info("尝试使用 Qwen 2.5 VL Plus 模型解析名片")
                extracted_data = parse_text_with_qwen25VLplus(image_data)
                logging.info("成功使用 Qwen 2.5 VL Plus 模型解析名片")
                
                
                return {
                    'code': 200,
                    'success': True,
                    'message': '名片图片解析成功',
                    'data': extracted_data
                }
            except Exception as qwen_error:
                logging.warning(f"Qwen 模型解析失败，错误原因: {str(qwen_error)}")
                
                # 如果是连接错误，尝试使用备用方案
                if "Connection error" in str(qwen_error):
                    logging.info("Qwen连接失败，尝试使用备用OCR+规则解析方案")
                    try:
                        # 尝试使用备用的OCR+规则提取方案
                        fallback_data = fallback_ocr_extraction(image_data)
                        if fallback_data:
                            logging.info("备用OCR解析成功")
                            return {
                                'code': 200,
                                'success': True,
                                'message': '使用备用OCR方案解析成功（Qwen连接失败）',
                                'data': fallback_data
                            }
                    except Exception as fallback_error:
                        logging.error(f"备用OCR解析也失败: {str(fallback_error)}")
                
                return {
                    'code': 500,
                    'success': False,
                    'message': f"名片图片解析失败: {str(qwen_error)}",
                    'data': None
                }
        except Exception as e:
            return {
                'code': 500,
                'success': False,
                'message': f"名片解析失败: {str(e)}",
                'data': None
            }
            
    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 add_business_card(card_data, image_file=None):
    """
    添加名片记录（负责业务逻辑处理部分）
    
    Args:
        card_data (dict): 名片信息数据
        image_file (FileStorage, optional): 名片图片文件（用于上传到MinIO）
        
    Returns:
        dict: 处理结果，包含保存的信息和状态
    """
    minio_path = None
    
    try:
        # 检查必要的数据
        if not card_data:
            return {
                'code': 400,
                'success': False,
                'message': '名片数据不能为空',
                'data': None
            }
        
        # 检查重复记录
        try:
            duplicate_check = check_duplicate_business_card(card_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)}'
            }
        
        # 上传图片到MinIO（如果提供了图片文件）
        if image_file:
            try:
                # 生成唯一的文件名
                file_ext = os.path.splitext(image_file.filename)[1].lower()
                if not file_ext:
                    file_ext = '.jpg'  # 默认扩展名
                
                unique_filename = f"{uuid.uuid4().hex}{file_ext}"
                minio_path = f"{unique_filename}"
                
                # 尝试上传到MinIO
                minio_client = get_minio_client()
                if minio_client:
                    try:
                        # 上传文件
                        logging.info(f"上传文件到MinIO: {minio_path}")
                        minio_client.put_object(
                            Bucket=minio_bucket,
                            Key=minio_path,
                            Body=image_file,
                            ContentType=image_file.content_type
                        )
                        logging.info(f"图片已上传到MinIO: {minio_path}")
                    except Exception as upload_err:
                        logging.error(f"上传文件到MinIO时出错: {str(upload_err)}")
                        # 即使上传失败，仍继续处理，但路径为None
                        minio_path = None
                else:
                    minio_path = None
                    logging.warning("MinIO客户端未初始化，图片未上传")
            except Exception as e:
                logging.error(f"上传图片到MinIO失败: {str(e)}", exc_info=True)
                minio_path = None
        
        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 = card_data.get('name_en', existing_card.name_en)
                existing_card.title_zh = card_data.get('title_zh', existing_card.title_zh)
                existing_card.title_en = card_data.get('title_en', existing_card.title_en)
                
                # 处理手机号码字段，支持多个手机号码
                if 'mobile' in card_data:
                    new_mobile = normalize_mobile_numbers(card_data.get('mobile', ''))
                    if new_mobile:
                        # 如果有新的手机号码，合并到现有手机号码中
                        existing_card.mobile = merge_mobile_numbers(existing_card.mobile, new_mobile)
                    elif card_data.get('mobile') == '':
                        # 如果明确传入空字符串，则清空手机号码
                        existing_card.mobile = ''
                
                existing_card.phone = card_data.get('phone', existing_card.phone)
                existing_card.email = card_data.get('email', existing_card.email)
                existing_card.hotel_zh = card_data.get('hotel_zh', existing_card.hotel_zh)
                existing_card.hotel_en = card_data.get('hotel_en', existing_card.hotel_en)
                existing_card.address_zh = card_data.get('address_zh', existing_card.address_zh)
                existing_card.address_en = card_data.get('address_en', existing_card.address_en)
                existing_card.postal_code_zh = card_data.get('postal_code_zh', existing_card.postal_code_zh)
                existing_card.postal_code_en = card_data.get('postal_code_en', existing_card.postal_code_en)
                existing_card.brand_zh = card_data.get('brand_zh', existing_card.brand_zh)
                existing_card.brand_en = card_data.get('brand_en', existing_card.brand_en)
                existing_card.affiliation_zh = card_data.get('affiliation_zh', existing_card.affiliation_zh)
                existing_card.affiliation_en = card_data.get('affiliation_en', existing_card.affiliation_en)
                # 处理生日字段
                if card_data.get('birthday'):
                    try:
                        existing_card.birthday = datetime.strptime(card_data.get('birthday'), '%Y-%m-%d').date()
                    except ValueError:
                        # 如果日期格式不正确，保持原值
                        pass
                
                # 处理年龄字段
                if 'age' in card_data:
                    try:
                        if card_data['age'] is not None and str(card_data['age']).strip():
                            age_value = int(card_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 = card_data.get('native_place', existing_card.native_place)
                existing_card.gender = card_data.get('gender', existing_card.gender)  # 新增性别字段
                existing_card.residence = card_data.get('residence', existing_card.residence)
                existing_card.brand_group = card_data.get('brand_group', existing_card.brand_group)
                existing_card.image_path = minio_path  # 更新为最新的图片路径
                # 更新origin_source字段，将新的记录添加到JSON数组中
                from app.core.data_parse.parse_system import update_origin_source
                existing_card.origin_source = update_origin_source(existing_card.origin_source, 'business_card_update', minio_path)
                existing_card.talent_profile = card_data.get('talent_profile', existing_card.talent_profile)  # 更新人才档案
                existing_card.updated_by = 'system'
                
                # 更新职业轨迹，传递图片路径
                existing_card.career_path = update_career_path(existing_card, card_data)
                
                db.session.commit()
                
                logging.info(f"已更新现有名片记录，ID: {existing_card.id}")
                
                return {
                    'code': 200,
                    'success': True,
                    'message': f'名片信息已更新。{duplicate_check["reason"]}',
                    'data': existing_card.to_dict()
                }
                
            elif duplicate_check['action'] == 'create_with_duplicates':
                # 创建新记录作为主记录，并保存疑似重复记录信息
                main_card, duplicate_record = create_main_card_with_duplicates(
                    card_data, 
                    minio_path, 
                    duplicate_check['suspected_duplicates'],
                    duplicate_check['reason'],
                    task_type='名片'  # 传递task_type参数
                )
                
                return {
                    'code': 202,  # Accepted，表示已接受但需要进一步处理
                    'success': True,
                    'message': f'创建新记录成功，发现疑似重复记录待处理。{duplicate_check["reason"]}',
                    'data': {
                        'main_card': main_card.to_dict(),
                        'duplicate_record_id': duplicate_record.id,
                        'suspected_duplicates_count': len(duplicate_check['suspected_duplicates']),
                        'processing_status': 'pending',
                        'duplicate_reason': duplicate_record.duplicate_reason,
                        'created_at': duplicate_record.created_at.strftime('%Y-%m-%d %H:%M:%S')
                    }
                }
                
            else:
                # 创建新记录
                # 直接使用上传的请求参数card_data中的career_path记录
                career_path = card_data.get('career_path', [])
                
                # 导入手机号码处理函数
                from app.core.data_parse.parse_system import normalize_mobile_numbers
                
                # 处理年龄字段，确保是有效的整数或None
                age_value = None
                if card_data.get('age'):
                    try:
                        age_value = int(card_data.get('age'))
                        if age_value <= 0 or age_value > 150:  # 合理的年龄范围检查
                            age_value = None
                    except (ValueError, TypeError):
                        age_value = None
                
                business_card = BusinessCard(
                    name_zh=card_data.get('name_zh', ''),
                    name_en=card_data.get('name_en', ''),
                    title_zh=card_data.get('title_zh', ''),
                    title_en=card_data.get('title_en', ''),
                    mobile=normalize_mobile_numbers(card_data.get('mobile', '')),
                    phone=card_data.get('phone', ''),
                    email=card_data.get('email', ''),
                    hotel_zh=card_data.get('hotel_zh', ''),
                    hotel_en=card_data.get('hotel_en', ''),
                    address_zh=card_data.get('address_zh', ''),
                    address_en=card_data.get('address_en', ''),
                    postal_code_zh=card_data.get('postal_code_zh', ''),
                    postal_code_en=card_data.get('postal_code_en', ''),
                    brand_zh=card_data.get('brand_zh', ''),
                    brand_en=card_data.get('brand_en', ''),
                    affiliation_zh=card_data.get('affiliation_zh', ''),
                    affiliation_en=card_data.get('affiliation_en', ''),
                    birthday=datetime.strptime(card_data.get('birthday'), '%Y-%m-%d').date() if card_data.get('birthday') else None,
                    age=age_value,
                    native_place=card_data.get('native_place', ''),
                    gender=card_data.get('gender', ''),  # 新增性别字段
                    residence=card_data.get('residence', ''),
                    image_path=minio_path,  # 最新的图片路径
                    career_path=career_path,  # 直接使用card_data中的career_path
                    brand_group=card_data.get('brand_group', ''),
                    origin_source=[create_origin_source_entry('business_card_creation', minio_path)],  # 原始资料记录
                    talent_profile=card_data.get('talent_profile', ''),  # 人才档案
                    status='active',
                    updated_by='system'
                )
                
                db.session.add(business_card)
                db.session.commit()
                
                logging.info(f"名片信息已保存到数据库，ID: {business_card.id}")
                
                return {
                    'code': 200,
                    'success': True,
                    'message': f'名片信息保存成功。{duplicate_check["reason"]}',
                    'data': business_card.to_dict()
                }
        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,
                'data': None
            }
            
    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,
            'data': None
        }


def delete_business_card(card_id):
    """
    删除名片记录
    
    Args:
        card_id (int): 名片记录ID
        
    Returns:
        dict: 删除操作的结果状态
    """
    try:
        # 导入必要的模块
        from app.services.neo4j_driver import neo4j_driver
        
        # 第一步：查找要删除的名片记录
        business_card = BusinessCard.query.get(card_id)
        if not business_card:
            return {
                'code': 404,
                'success': False,
                'message': f'未找到ID为{card_id}的名片记录',
                'data': None
            }
        
        # 保存被删除记录的信息，用于返回
        deleted_card_info = business_card.to_dict()
        
        # 第二步：从MinIO删除图片文件（如果存在）
        if business_card.image_path:
            try:
                minio_client = get_minio_client()
                if minio_client:
                    try:
                        minio_client.delete_object(
                            Bucket=minio_bucket,
                            Key=business_card.image_path
                        )
                        logging.info(f"已从MinIO删除图片文件: {business_card.image_path}")
                    except Exception as minio_error:
                        logging.warning(f"从MinIO删除图片文件失败: {str(minio_error)}")
                        # 继续执行，不因为MinIO删除失败而中断整个流程
            except Exception as e:
                logging.warning(f"MinIO删除操作失败: {str(e)}")
        
        # 第三步：删除PostgreSQL数据库中的相关记录
        try:
            # 删除duplicate_business_cards表中以该ID作为main_card_id的记录
            duplicate_records_as_main = DuplicateBusinessCard.query.filter_by(main_card_id=card_id).all()
            for duplicate_record in duplicate_records_as_main:
                db.session.delete(duplicate_record)
                logging.info(f"删除重复记录表中主记录ID为{card_id}的记录，重复记录ID: {duplicate_record.id}")
            
            # 删除business_cards表中的主记录
            db.session.delete(business_card)
            db.session.commit()
            
            logging.info(f"成功删除PostgreSQL数据库中的名片记录，ID: {card_id}")
            
        except Exception as db_error:
            db.session.rollback()
            error_msg = f"删除PostgreSQL数据库记录失败: {str(db_error)}"
            logging.error(error_msg, exc_info=True)
            return {
                'code': 500,
                'success': False,
                'message': error_msg,
                'data': None
            }
        
        # 第四步：删除Neo4j图数据库中的talent节点及其关系
        try:
            # 构建删除talent节点及其所有关系的Cypher查询
            delete_talent_query = """
            MATCH (t:Talent)
            WHERE t.pg_id = $pg_id
            OPTIONAL MATCH (t)-[r]-()
            DELETE r, t
            RETURN count(t) as deleted_count
            """
            
            # 执行删除查询
            with neo4j_driver.get_session() as session:
                result = session.run(delete_talent_query, pg_id=int(card_id))
                record = result.single()
                deleted_count = record['deleted_count'] if record else 0
                
                if deleted_count > 0:
                    logging.info(f"成功删除Neo4j中的talent节点及其关系，pg_id: {card_id}")
                else:
                    logging.info(f"Neo4j中未找到pg_id为{card_id}的talent节点")
                    
        except Exception as neo4j_error:
            # Neo4j删除失败不影响整体操作的成功，因为PostgreSQL记录已经删除成功
            logging.error(f"删除Neo4j数据失败: {str(neo4j_error)}", exc_info=True)
            return {
                'code': 206,  # Partial Content - 部分成功
                'success': True,
                'message': f'名片记录删除成功，但Neo4j图数据库清理失败: {str(neo4j_error)}',
                'data': deleted_card_info
            }
        
        return {
            'code': 200,
            'success': True,
            'message': '名片记录删除成功',
            'data': deleted_card_info
        }
        
    except Exception as e:
        # 确保数据库事务回滚
        try:
            db.session.rollback()
        except:
            pass
            
        error_msg = f"删除名片记录失败: {str(e)}"
        logging.error(error_msg, exc_info=True)
        
        return {
            'code': 500,
            'success': False,
            'message': error_msg,
            'data': None
        }


def batch_process_business_card_images(minio_paths_json, task_id=None, task_type=None):
    """
    批量处理名片图片，从parse_task_repository表读取任务记录进行处理
    
    Args:
        minio_paths_json (list): 包含MinIO对象访问地址的JSON数组（已废弃，现在从数据库读取）
        task_id (str, optional): 任务ID，用于从数据库读取task_source
        task_type (str, optional): 任务类型
        
    Returns:
        dict: 批量处理结果，包含所有解析结果的数组
    """
    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
            }
        
        # 获取MinIO客户端
        minio_client = get_minio_client()
        if not minio_client:
            return {
                'code': 500,
                'success': False,
                'message': '无法连接到MinIO服务器',
                '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 = item.get('minio_path')
                original_filename = item.get('original_filename', '')
                
                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(minio_paths_json)} 个文件: {minio_path}")
                
                # 解析MinIO URL获取对象路径
                object_key = _extract_object_key_from_url(minio_path)
                if not object_key:
                    failed_count += 1
                    results.append({
                        'index': i,
                        'minio_path': minio_path,
                        'success': False,
                        'error': f'无效的MinIO URL格式: {minio_path}',
                        'data': None
                    })
                    continue
                
                # 从MinIO下载图片文件
                try:
                    logging.info(f"从MinIO下载文件: {object_key}")
                    response = minio_client.get_object(Bucket=minio_bucket, Key=object_key)
                    image_data = response['Body'].read()
                    
                    if len(image_data) == 0:
                        failed_count += 1
                        results.append({
                            'index': i,
                            'minio_path': minio_path,
                            'success': False,
                            'error': '下载的图片数据为空',
                            'data': None
                        })
                        continue
                    
                    # 获取文件名和内容类型
                    filename = original_filename if original_filename else object_key.split('/')[-1]
                    content_type = _get_content_type_by_filename(filename)
                    
                    # 创建FileStorage对象模拟上传的文件
                    image_stream = BytesIO(image_data)
                    file_storage = FileStorage(
                        stream=image_stream,
                        filename=filename,
                        content_type=content_type
                    )
                    
                    # 调用process_business_card_image函数处理图片
                    process_result = process_business_card_image(file_storage)
                    
                    if process_result.get('success', False):
                        # 记录成功解析的人才信息到parsed_talents表
                        try:
                            from app.core.data_parse.parse_task import record_parsed_talent
                            from app.core.data_parse.parse_system import get_brand_group_by_hotel
                            
                            talent_data = process_result.get('data')
                            if talent_data and isinstance(talent_data, dict):
                                # 在记录到parsed_talents表之前，设置image_path和origin_source
                                talent_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
                                }
                                talent_data['origin_source'] = [origin_source_entry]
                                
                                # 更新career_path中记录的image_path字段
                                if talent_data.get('career_path') and isinstance(talent_data['career_path'], list):
                                    for career_entry in talent_data['career_path']:
                                        if isinstance(career_entry, dict):
                                            career_entry['image_path'] = minio_path
                                
                                # 调用get_brand_group_by_hotel获取品牌和集团信息
                                if talent_data.get('hotel_zh'):
                                    try:
                                        brand_result = get_brand_group_by_hotel(talent_data['hotel_zh'])
                                        if brand_result.get('success') and brand_result.get('data'):
                                            brand_data = brand_result['data']
                                            # 赋值品牌和集团信息
                                            talent_data['brand_zh'] = brand_data.get('brand_name_zh', '')
                                            talent_data['brand_en'] = brand_data.get('brand_name_en', '')
                                            talent_data['affiliation_zh'] = brand_data.get('group_name_zh', '')
                                            talent_data['affiliation_en'] = brand_data.get('group_name_en', '')
                                            logging.info(f"成功获取品牌和集团信息: {brand_data}")
                                        else:
                                            logging.warning(f"获取品牌信息失败: {brand_result.get('message', '')}")
                                            # 设置默认值
                                            talent_data['brand_zh'] = ''
                                            talent_data['brand_en'] = ''
                                            talent_data['affiliation_zh'] = ''
                                            talent_data['affiliation_en'] = ''
                                    except Exception as brand_error:
                                        logging.error(f"调用get_brand_group_by_hotel失败: {str(brand_error)}")
                                        # 设置默认值
                                        talent_data['brand_zh'] = ''
                                        talent_data['brand_en'] = ''
                                        talent_data['affiliation_zh'] = ''
                                        talent_data['affiliation_en'] = ''
                                else:
                                    # 没有酒店信息，设置默认值
                                    talent_data['brand_zh'] = ''
                                    talent_data['brand_en'] = ''
                                    talent_data['affiliation_zh'] = ''
                                    talent_data['affiliation_en'] = ''
                                
                                record_result = record_parsed_talent(talent_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表: {talent_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
                        results.append({
                            'index': i,
                            'minio_path': minio_path,
                            'object_key': object_key,
                            'filename': filename,
                            'success': True,
                            'error': None,
                            'data': process_result.get('data'),
                            'message': process_result.get('message', '处理成功')
                        })
                        logging.info(f"成功处理第 {i+1} 个文件: {filename}")
                    else:
                        failed_count += 1
                        # 更新task_source中对应记录的状态
                        item['parse_flag'] = 1
                        item['status'] = '解析失败'
                        results.append({
                            'index': i,
                            'minio_path': minio_path,
                            'object_key': object_key,
                            'filename': filename,
                            'success': False,
                            'error': process_result.get('message', '处理失败'),
                            'data': None
                        })
                        logging.error(f"处理第 {i+1} 个文件失败: {process_result.get('message', '未知错误')}")
                    
                except Exception as download_error:
                    failed_count += 1
                    error_msg = f"下载MinIO文件失败: {str(download_error)}"
                    logging.error(error_msg, exc_info=True)
                    # 更新task_source中对应记录的状态
                    item['parse_flag'] = 1
                    item['status'] = '解析失败'
                    results.append({
                        'index': i,
                        'minio_path': minio_path,
                        'object_key': object_key,
                        'success': False,
                        'error': error_msg,
                        'data': None
                    })
                    
            except Exception as item_error:
                failed_count += 1
                error_msg = f"处理数组元素失败: {str(item_error)}"
                logging.error(error_msg, exc_info=True)
                # 更新task_source中对应记录的状态
                if isinstance(item, dict):
                    item['parse_flag'] = 1
                    item['status'] = '解析失败'
                results.append({
                    'index': i,
                    'minio_path': str(item) if isinstance(item, (str, dict)) else 'unknown',
                    'success': False,
                    'error': error_msg,
                    'data': None
                })
        
        # 根据处理结果更新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)
        
        return {
            'code': 500,
            'success': False,
            'message': error_msg,
            'data': None
        }


def _extract_object_key_from_url(minio_url):
    """
    从MinIO完整URL中提取对象键名
    
    Args:
        minio_url (str): 完整的MinIO URL，如 "http://host:port/bucket/path/to/file.jpg"
        
    Returns:
        str: 对象键名，如 "path/to/file.jpg"，失败时返回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 _get_content_type_by_filename(filename):
    """
    根据文件名获取内容类型
    
    Args:
        filename (str): 文件名
        
    Returns:
        str: 内容类型
    """
    if not filename:
        return 'application/octet-stream'
    
    file_ext = filename.lower().split('.')[-1] if '.' in filename else ''
    
    content_type_mapping = {
        'jpg': 'image/jpeg',
        'jpeg': 'image/jpeg',
        'png': 'image/png',
        'gif': 'image/gif',
        'bmp': 'image/bmp',
        'webp': 'image/webp'
    }
    
    return content_type_mapping.get(file_ext, 'image/jpeg')  # 默认为JPEG图片

def parse_business_card_with_qwen(image_data):
    """
    使用阿里云的 Qwen VL Max 模型解析图像中的名片信息
    
    Args:
        image_data (bytes): 图像的二进制数据
        
    Returns:
        dict: 解析的名片信息
    """
    try:
        # 将图片数据转为 base64 编码
        base64_image = base64.b64encode(image_data).decode('utf-8')
        
        # 初始化 OpenAI 客户端，配置为阿里云 API
        client = OpenAI(
            api_key=config.QWEN_API_KEY,
            base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
        )
        
        # 构建优化后的提示语
        prompt = """你是企业名片的信息提取专家。请仔细分析提供的图片，精确提取名片信息。

## 提取要求
- 区分中英文内容，分别提取
- 保持提取信息的原始格式（如大小写、标点）
- 对于无法识别或名片中不存在的信息，返回空字符串
- 名片中没有的信息，请不要猜测
## 需提取的字段
1. 中文姓名 (name_zh)
2. 英文姓名 (name_en)
3. 中文职位/头衔 (title_zh)
4. 英文职位/头衔 (title_en)
5. 中文酒店/公司名称 (hotel_zh)
6. 英文酒店/公司名称 (hotel_en)
7. 手机号码 (mobile) - 如有多个手机号码，使用逗号分隔，最多提取3个
8. 固定电话 (phone) - 如有多个，使用逗号分隔
9. 电子邮箱 (email)
10. 中文地址 (address_zh)
11. 英文地址 (address_en)
12. 中文邮政编码 (postal_code_zh)
13. 英文邮政编码 (postal_code_en)
14. 生日 (birthday) - 格式为YYYY-MM-DD，如1990-01-01
15. 年龄 (age) - 数字格式，如30
16. 籍贯 (native_place) - 出生地或户籍所在地信息
17. 居住地 (residence) - 个人居住地址信息
18. 品牌组合 (brand_group) - 如有多个品牌，使用逗号分隔
19. 职业轨迹 (career_path) - 如能从名片中推断，以JSON数组格式返回，包含当前日期，公司名称和职位。自动生成当前日期。
20. 隶属关系 (affiliation) - 如能从名片中推断，以JSON数组格式返回，包含公司名称和隶属集团名称
## 输出格式
请以严格的JSON格式返回结果，不要添加任何额外解释文字。JSON格式如下：
```json
{
  "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": []
}
```"""
        
        # 调用 Qwen VL Max  API（添加重试机制）
        logging.info("发送请求到 Qwen VL Max 模型")
        completion = client.chat.completions.create(
            # model="qwen-vl-plus",
            model="qwen-vl-max-latest",
            messages=[
                {
                    "role": "user",
                    "content": [
                        {"type": "text", "text": prompt},
                        {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}}
                    ]
                }
            ],
            temperature=0.1,  # 降低温度增加精确性
            response_format={"type": "json_object"}  # 要求输出JSON格式
        )
        
        # 解析响应
        response_content = completion.choices[0].message.content
        logging.info(f"成功从 Qwen 模型获取响应: {response_content}")
        
        # 尝试从响应中提取 JSON
        try:
            extracted_data = json.loads(response_content)
            logging.info("成功解析 Qwen 响应中的 JSON")
        except json.JSONDecodeError:
            logging.warning("无法解析 JSON，尝试从文本中提取信息")
            # 这里可以调用其他的解析函数，但为了简化，先返回错误
            raise Exception("无法解析 Qwen 返回的 JSON 格式")
        
        # 确保所有必要字段存在
        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'
        ]
        
        for field in required_fields:
            if field not in extracted_data:
                if field == 'career_path':
                    extracted_data[field] = []
                elif field == 'age':
                    extracted_data[field] = ""
                else:
                    extracted_data[field] = ""
        
        # 为career_path增加一条记录
        if extracted_data.get('hotel_zh') or extracted_data.get('hotel_en') or extracted_data.get('title_zh') or extracted_data.get('title_en'):
            career_entry = {
                'date': get_east_asia_date_str(),
                'hotel_en': extracted_data.get('hotel_en', ''),
                'hotel_zh': extracted_data.get('hotel_zh', ''),
                'image_path': '',
                'source': 'business_card_creation',
                'title_en': extracted_data.get('title_en', ''),
                'title_zh': extracted_data.get('title_zh', '')
            }
            
            # 直接清空原有的career_path内容，用career_entry写入
            extracted_data['career_path'] = [career_entry]
            logging.info(f"为解析结果设置了career_path记录: {career_entry}")
        
        return extracted_data
        
    except Exception as e:
        error_msg = f"Qwen VL Max 模型解析失败: {str(e)}"
        logging.error(error_msg, exc_info=True)
        raise Exception(error_msg)


def fallback_ocr_extraction(image_data):
    """
    备用OCR+规则提取方案
    当Qwen API不可用时使用
    
    Args:
        image_data (bytes): 图像的二进制数据
        
    Returns:
        dict: 基础的名片信息（可能不完整）
    """
    try:
        logging.info("开始备用OCR文本提取")
        
        # 使用PIL处理图像
        from PIL import Image
        import pytesseract
        
        # 将字节数据转换为PIL图像
        image = Image.open(BytesIO(image_data))
        
        # 使用OCR提取文本
        text = pytesseract.image_to_string(image, lang='chi_sim+eng')
        logging.info(f"OCR提取的文本: {text}")
        
        if not text.strip():
            logging.warning("OCR未能提取到任何文本")
            return None
        
        # 基础的规则提取
        extracted_data = {
            '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': '',
            'brand_group': '',
            'career_path': [],
            'affiliation': []
        }
        
        # 简单的规则提取
        lines = [line.strip() for line in text.split('\n') if line.strip()]
        
        # 提取邮箱
        email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
        email_matches = re.findall(email_pattern, text)
        if email_matches:
            extracted_data['email'] = email_matches[0]
        
        # 提取手机号码（中国手机号）
        mobile_pattern = r'\b1[3-9]\d{9}\b'
        mobile_matches = re.findall(mobile_pattern, text)
        if mobile_matches:
            extracted_data['mobile'] = ','.join(mobile_matches[:3])  # 最多3个
        
        # 提取固定电话
        phone_pattern = r'\b\d{3,4}-?\d{7,8}\b'
        phone_matches = re.findall(phone_pattern, text)
        if phone_matches:
            extracted_data['phone'] = ','.join(phone_matches[:2])  # 最多2个
        
        # 如果找到了一些基础信息，添加一个职业轨迹记录
        if extracted_data['email'] or extracted_data['mobile']:
            career_entry = {
                'date': get_east_asia_date_str(),
                'hotel_en': '',
                'hotel_zh': '',
                'image_path': '',
                'source': 'ocr_fallback',
                'title_en': '',
                'title_zh': ''
            }
            extracted_data['career_path'] = [career_entry]
        
        logging.info("备用OCR解析完成")
        return extracted_data
        
    except Exception as e:
        logging.error(f"备用OCR解析失败: {str(e)}")
        return None 