import logging
import time
from datetime import datetime, timedelta
from online_streaming import online_llm_streaming
import json
import re
import traceback
import os
import csv
from typing import List, Tuple, Dict, Any, Optional, Union, Set
from dataclasses import dataclass, field
from tenacity import retry, stop_after_attempt, wait_exponential
from pathlib import Path
import yaml
import pandas as pd
import ast
from settings import SALES_VIOLATIONS, SALES_QUALITY_METRICS, LLMConfig, LogConfig
import concurrent.futures
import threading
import random
import jieba

# 添加线程锁用于日志记录
log_lock = threading.Lock()


def thread_safe_log(logger_func, message):
    """线程安全的日志记录"""
    with log_lock:
        logger_func(message)


def setup_logging():
    """配置日志系统"""
    # 创建logs目录（如果不存在）
    log_dir = 'logs'
    if not os.path.exists(log_dir):
        os.makedirs(log_dir)

    # 生成日志文件名（使用当前时间戳）
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    log_file = os.path.join(log_dir, f'sales_analysis_{timestamp}.log')

    # 配置日志格式和处理器
    logging.basicConfig(
        level=getattr(logging, LogConfig.LEVEL),
        format=LogConfig.FORMAT,
        handlers=[
            logging.FileHandler(log_file, encoding=LogConfig.ENCODING),
            logging.StreamHandler()
        ]
    )

    # 设置日志
    logger = logging.getLogger(__name__)
    logger.info(f"日志文件创建于: {log_file}")
    return logger


# 配置日志
logger = setup_logging()


@dataclass
class DialogueInfo:
    """对话信息数据类"""
    group_id: str
    customer_name: str
    sales_staff_name: str  # 改为更准确的字段名
    dialogue_content: str
    跟进日期: Optional[str] = None


@dataclass
class AnalysisResult:
    """分析结果数据类"""

    def __init__(self,
                 会话组编号: str,
                 客户名称: str,
                 销售人员名称: str,  # 改为更准确的字段名
                 客户现状: str,
                 客户需求: str,
                 客户潜在需求: str,
                 客户意向等级: str,
                 销售卡点: str,
                 次日动作: str,
                 客户购买潜力: str,
                 顾问卡点: str,
                 客户购买潜力依据: str,
                 顾问卡点依据: str,
                 跟进日期: str = None,
                 round: int = None,  # 添加round字段
                 ):
        # 初始化所有属性
        self.会话组编号 = 会话组编号
        self.客户名称 = 客户名称
        self.销售人员名称 = 销售人员名称
        self.客户现状 = 客户现状
        self.客户需求 = 客户需求
        self.客户潜在需求 = 客户潜在需求
        self.客户意向等级 = 客户意向等级
        self.销售卡点 = 销售卡点
        self.次日动作 = 次日动作
        self.客户购买潜力 = 客户购买潜力
        self.顾问卡点 = 顾问卡点
        self.客户购买潜力依据 = 客户购买潜力依据  # 添加依据字段
        self.顾问卡点依据 = 顾问卡点依据  # 添加依据字段
        self.跟进日期 = 跟进日期
        self.round = round  # 添加round属性

    def to_dict(self) -> Dict[str, Any]:
        return {
            "会话组编号": self.会话组编号,
            "客户名称": self.客户名称,
            "销售人员名称": self.销售人员名称,  # 改为更准确的字段名
            "客户现状": self.客户现状,
            "客户需求": self.客户需求,
            "客户潜在需求": self.客户潜在需求,
            "销售卡点": self.销售卡点,
            "次日动作": self.次日动作,
            "客户购买潜力": self.客户购买潜力,
            "顾问卡点": self.顾问卡点,
            "客户购买潜力依据": self.客户购买潜力依据,  # 添加依据字段
            "顾问卡点依据": self.顾问卡点依据,  # 添加依据字段
            "跟进日期": self.跟进日期,
            "round": self.round  # 添加round字段
        }

    def to_csv_row(self) -> List[str]:
        """将结果转换为CSV行格式"""
        return [
            str(self.会话组编号),
            str(self.客户名称),
            str(self.销售人员名称),  # 改为更准确的字段名
            str(self.客户现状),
            str(self.客户需求),
            str(self.客户潜在需求),
            str(self.销售卡点),
            str(self.次日动作),
            str(self.客户购买潜力),
            str(self.顾问卡点),
            str(self.跟进日期),
            str(self.客户购买潜力依据),  # 添加依据字段
            str(self.顾问卡点依据),  # 添加依据字段
            str(self.round)  # 添加round字段
        ]


def validate_dialogue_content(content: str) -> bool:
    """验证对话内容格式"""
    if not content or len(content.strip()) < 10:
        return False

    # 检查基本格式要求
    required_patterns = [
        # 时间戳格式
        r'\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}',
        # 对话者标识（包括更多格式）
        r'(?:销售组或客户|客户成功组|银河VIP经理)[-企微通话数据]*[-单聊|-群聊]*[：:]',
        # 对话内容
        r'[：:].+',
        # 轮次标记格式
        r'round\d+:\d{4}-\d{2}-\d{2}'
    ]

    # 至少要匹配一个基本格式
    basic_format_matched = False
    for pattern in required_patterns:
        if re.search(pattern, content):
            basic_format_matched = True
            break

    if not basic_format_matched:
        return False

    # 检查对话完整性
    lines = content.strip().split('\n')
    valid_lines = 0

    for line in lines:
        line = line.strip()
        if not line:
            continue

        # 检查是否是标准对话行
        if re.search(r'\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+[^：:]+[：:].+', line):
            valid_lines += 1
            continue

        # 检查是否是轮次标记行
        if re.match(r'round\d+:\d{4}-\d{2}-\d{2}', line):
            valid_lines += 1
            continue

        # 检查是否是引用消息
        if '这是一条引用/回复消息：' in line:
            valid_lines += 1
            continue

        # 检查是否是引用内容
        if line.startswith('"') and line.endswith('"'):
            valid_lines += 1
            continue

        # 检查是否是分隔线
        if line.startswith('------'):
            valid_lines += 1
            continue

        # 检查是否是相关文件/图片/链接
        if line in ['相关文件', '相关图片', '相关链接']:
            valid_lines += 1
            continue

    # 至少要有两个有效的对话行（通常是一问一答）
    return valid_lines >= 2


@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
def extract_dialogue_groups(content: str) -> List[DialogueInfo]:
    """从内容中提取对话组，带重试机制"""
    if not content:
        raise ValueError("输入内容为空")

    # 使用更精确的正则表达式来匹配对话组
    groups = re.split(r'\n(?=对话组[：:]\s*\d+\s*\n)', content.strip())

    dialogue_groups = []
    total_groups = 0

    for group in groups:
        try:
            # 跳过空组
            if not group.strip():
                continue

            # 提取对话组编号
            group_match = re.match(r'^对话组[：:]\s*(\d+)\s*\n', group)
            if not group_match:
                continue

            total_groups += 1
            group_id = group_match.group(1)

            # 获取对话内容（去掉对话组标记行）
            dialogue_content = re.sub(r'^对话组[：:]\s*\d+\s*\n', '', group).strip()

            if not dialogue_content:
                logger.warning(f"对话组 {group_id} 内容为空")
                continue

            # 验证对话内容格式
            if not validate_dialogue_content(dialogue_content):
                logger.warning(f"对话组 {group_id} 格式验证失败")
                continue

            # 检查是否包含有效时间戳
            if not re.search(r'\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}', dialogue_content):
                logger.warning(f"对话组 {group_id} 不包含有效时间戳格式")
                continue

            # 检查是否包含有效对话者标识
            if not re.search(
                    r'(?:销售组|客户(?:\(\d+\))?|客户成功组|银河VIP经理|生活管家|转介运营组|生活顾问1组|华北销售五部|华北销售四部|华南销售三部|华南销售二部|华南销售六部)[-企微通话数据|-企微|-语音通话]*[-单聊|-群聊]*[：:]',
                    dialogue_content):
                logger.warning(f"对话组 {group_id} 不包含有效对话者标识")
                continue

            # 处理按轮次分组的对话内容
            dialogue_lines = dialogue_content.split('\n')
            processed_lines = []
            current_round = None
            跟进日期 = None

            # 检查是否包含round标记
            has_round_markers = any(re.match(r'round\d+:\d{4}-\d{2}-\d{2}', line) for line in dialogue_lines)

            if has_round_markers:
                # 如果包含round标记，按round分割
                rounds = re.split(r'\n(?=round\d+:\d{4}-\d{2}-\d{2})', dialogue_content)
                logger.info(f"对话组 {group_id} 包含 {len(rounds)} 轮对话")

                for round_content in rounds:
                    if not round_content.strip():
                        continue

                    # 提取round的日期
                    round_match = re.match(r'round\d+:(\d{4}-\d{2}-\d{2})', round_content)
                    if round_match:
                        跟进日期 = round_match.group(1)
                        logger.info(f"对话组 {group_id} 发现对话轮次，日期: {跟进日期}")

            # 从对话内容中提取客户ID
            customer_id = None
            for line in dialogue_lines:
                customer_match = re.search(r'客户\((\d+)\)', line)
                if customer_match:
                    customer_id = customer_match.group(1)
                    break

            # 从对话内容中提取销售人员名称
            sales_name = "银河销售人员"  # 默认值
            for line in dialogue_lines:
                sales_match = re.search(r'(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+[^-]+)', line)
                if sales_match and "客户" not in sales_match.group(1):
                    name_parts = sales_match.group(1).split()
                    if len(name_parts) >= 3:
                        sales_name = name_parts[2]
                        break

            dialogue_groups.append(DialogueInfo(
                group_id=group_id,
                customer_name=f"客户{customer_id}" if customer_id else f"客户{group_id}",
                sales_staff_name=sales_name,
                dialogue_content=dialogue_content,
                跟进日期=跟进日期
            ))
            logger.debug(f"成功处理对话组 {group_id}")

        except Exception as e:
            logger.error(f"处理对话组时发生错误: {str(e)}")
            logger.error(traceback.format_exc())
            continue

    successful_groups = len(dialogue_groups)
    if successful_groups == 0:
        logger.warning("未能成功提取任何对话组")
    else:
        logger.info(f"成功提取 {successful_groups}/{total_groups} 个对话组")

    return dialogue_groups


def format_dialogue(dialogue: str, group_id: str) -> DialogueInfo:
    """格式化对话内容"""
    try:
        # 提取客户名称
        customer_name = f"客户{group_id}"

        # 提取销售名称 - 改进的逻辑
        sales_name = "银河销售人员"  # 默认值
        sales_department = None  # 销售部门

        # 尝试从对话中提取销售人员信息
        lines = dialogue.split('\n')
        for line in lines:
            # 跳过空行和客户的发言
            if not line or "客户" in line:
                continue

            # 首先去除时间戳（如果存在）
            parts = line.split(' ', 2)  # 最多分割2次
            if len(parts) >= 2:
                # 检查第一部分是否是时间戳格式（YYYY-MM-DD HH:MM:SS）
                if re.match(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}', ' '.join(parts[:2])):
                    line = parts[2] if len(parts) > 2 else ''

            # 匹配销售名称（在冒号或者说话内容之前）
            sales_match = re.match(r'^([^:：]+)[：:](.*)', line)
            if sales_match:
                potential_name = sales_match.group(1).strip()
                # 清理销售名称中的特殊标记
                cleaned_name = re.sub(r'-通话数据|-企微-单聊|-企微-群聊|-企微|通话数据|-.*', '', potential_name)
                cleaned_name = cleaned_name.strip()

                # 如果清理后的名称不为空且不是特殊字符
                if cleaned_name and not re.match(r'^[\d\s]+$', cleaned_name):
                    # 检查是否包含部门信息
                    dept_match = re.search(r'(华[东南北]销售[一二三四五六七八九十]+部|销售[一二三四五六七八九十]+部)',
                                           cleaned_name)
                    if dept_match:
                        sales_department = dept_match.group(1)
                        # 从名称中移除部门信息
                        cleaned_name = re.sub(
                            r'(华[东南北]销售[一二三四五六七八九十]+部|销售[一二三四五六七八九十]+部)', '',
                            cleaned_name).strip()

                    # 如果清理后的名称不是通用词，则使用该名称
                    if cleaned_name.lower() not in ['销售', '客服', 'sales', 'service', 'sop组', 'sop', '银河']:
                        sales_name = cleaned_name
                        if sales_department:
                            sales_name = f"{sales_department}-{sales_name}"
                        break
                    elif sales_department:
                        # 如果只有部门信息，使用部门作为销售名称
                        sales_name = sales_department
                        break

        # 提取真实的会话组编号
        real_group_id = group_id
        for line in lines:
            # 尝试从对话内容中匹配会话组编号
            id_match = re.search(r'销售组或客户\((\d+)\)', line)
            if id_match:
                real_group_id = id_match.group(1)
                customer_name = f"客户{real_group_id}"
                break

        return DialogueInfo(
            group_id=real_group_id,
            customer_name=customer_name,
            sales_staff_name=sales_name,
            dialogue_content=dialogue
        )
    except Exception as e:
        logger.error(f"格式化对话时发生错误: {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
        return DialogueInfo(
            group_id=group_id,
            customer_name=f"客户{group_id}",
            sales_staff_name="银河销售人员",
            dialogue_content=dialogue
        )


@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
def read_source_file(target_dir) -> str:
    """读取源文件内容，带重试机制"""
    try:
        with open(os.path.join(target_dir, 'source.md'), 'r', encoding='utf-8') as f:
            content = f.read()
            if not content:
                raise ValueError("文件内容为空")
            logger.info(f"文件总字符数: {len(content)}")
            logger.info(f"文件总行数: {content.count(chr(10)) + 1}")
            return content
    except Exception as e:
        logger.error(f"读取文件时发生错误: {str(e)}")
        raise


def get_analysis_prompt(dialogue_content: str, corpus_examples: List[Tuple[int, str, str, str]]) -> str:
    """构建分析提示词"""

    # 用户概况
    customer_profile = """ """

    # 历史对话摘要
    history_dialog_summary = """ """

    # 添加新的标签定义
    tag_definitions = """ """

    # 运营提供的背景知识
    operate_context = ''' 
    一、其中'客户购买潜力'如下，并且只可以从中进行选择：
    1.意向客户可跟进-考虑生意：客户有意向通过在香港开设实体店（如餐饮、零售等）作为申请香港身份或续签的方式，具备实际业务需求，可重点跟进。
    2.意向客户可跟进-考虑工位：客户希望通过设立在香港的办公室（租赁办公桌/工位）来满足身份申请或续签的条件，有实际办公需求。
    3.意向客户可跟进-只考虑身份续签：客户当前已有香港身份，关注点集中在如何合法续签，暂未表露其他业务意图，可跟进其续签所需材料或方案。
    4.意向客户可跟进-考虑自雇续签：客户希望以自雇（如注册公司担任董事/高管）方式完成香港身份续签，可能需协助设计合理业务架构。
    5.意向客户可跟进-考虑教育择校：客户关注子女在香港接受教育。仅限于：幼儿园、小学、中学。
    6.意向客户可跟进-需求不明确：客户表达出兴趣但未明确具体需求（如生意、工位、身份等），需进一步沟通挖掘其真实意向。
    7.意向客户可跟进-有房产意向：客户对香港房地产有投资或置业的兴趣，可能与身份申请、教育等诉求相关联，需关联其他业务场景挖掘需求。
    8.孵化类客户-保险受雇：客户计划通过受雇于香港保险公司获取香港身份，但真实性或可执行性仍需核实，处于业务孵化阶段。
    9.孵化类客户-想香港身份挂靠：客户无实际业务运营意向，仅希望通过挂靠公司方式获取香港身份，存在合规风险，需谨慎评估。
    10.孵化类客户-想找工作：客户希望以受雇身份（特别是专才/受雇签证）在香港工作，尚未明确雇主或工作机会，处于初步探索阶段。
    11.孵化类客户-受雇真实性待挖掘：客户声称以受雇方式留港，但身份真实性或职位匹配度存疑，需要进一步验证其工作背景及雇主情况。
    12.孵化类客户-已有续签方案_非银河：客户已有其他机构的身份续签方案，非银河公司提供，但仍可争取合作或二次转化。
    13.其他-不知道需求：客户目前未表达任何具体需求，完全空白状态，需要从身份、教育、投资等方向全面试探引导。
    14.已复购商务未至永居-观望：客户已购买商务身份产品，但尚未达到永居年限，目前处于观望状态，可能对服务效果或政策持观望态度。
    15.已复购商务未至永居-考虑商务：客户已购买商务身份，目前考虑继续发展香港业务（如注册新公司、开实体店等），有后续合作潜力。
    16.其他-生意星专项：特殊项目客户，可能参与公司针对香港创业/生意的专项扶持计划（如“生意星”项目），需对接专项资源。
    17.已复购商务至永居-无需求：客户成功获香港永居身份，目前无进一步业务或身份服务需求，维持关系即可。
    18.已复购商务至永居-考虑生意：客户已获永居，但仍希望继续在香港开展或拓展生意，可能有实体投资、选址、注册等需求。
    19.已复购商务至永居-考虑工位：客户已获永居，考虑继续维持或扩大香港办公场所，可能用于实际业务运营或身份合规。
    20.真实在港运营公司-无需求：客户拥有真实在港运营公司，当前无额外身份或业务服务需求，重点在维护客户关系。
    21.真实在港运营公司-考虑工位：客户公司真实运营，但有扩大办公空间或改善办公环境的需求，可提供工位租赁等服务。
    22.真实在港运营公司-考虑商务：客户公司实际运作中，考虑进一步拓展业务（如新公司设立、业务转型等），可提供商务咨询支持。
    23.长期运营-已真实受雇于香港：客户已在香港有真实受雇工作，身份稳定，业务合作空间有限，但可作为优质资源维护。
    24.长期运营-放弃身份：客户主动放弃香港身份。
    25.长期运营-永居客户：客户已取得香港永居，身份稳定，可作为高质量客户维护，或引导其在港投资、置业等。
    26.长期运营-留学在读中：客户或其子女在香港留学，已具身份或签证，关注教育服务、身份衔接等，需定期触达。
    27.长期运营-专才：客户通过“输入内地人才计划”等方式来港，具备专业背景和工作履历，身份稳定，有较高服务延展空间。


    二、其中'顾问卡点'如下，并且只可以从中进行选择：
    1.价格异议
    2.竞品对比
    3.购买紧迫性
    4.有意向但客户联系不上
    5.续签永居整体成本超预期
    6.未考虑好是否维持身份    
    7.合同条款/退费问题
    8.与家人或朋友商量
    9.担忧投入及续签成功率
    10.其他

    '''


    # 构建JSON示例
    json_example = '''{"会话session_id": "例如：916713",
                       "客户名称":"实际上就是客户(9873)中括号中的数字提取出来，组合成为客户9873，以此类推。例如：客户916713",
                       "跟进日期":"就是时间戳中日期的部分，年月日,没有的话就写无。例如：2025年05月17日",
                       "销售人员名称":"就是时间戳中销售人员的名字，如果是华北，华东，华南，华西开头的销售人员，优先选择。例如：华南销售六部",
                       "客户现状":"描述客户当前业务、痛点、外部环境等动态状态等",
                       "客户需求":"客户明确提出的需求或期望等且还没有被解决的,并且给出判断依据和原对话，不可以低于100个字",
                       "客户潜在需求": "基于上下文隐含的、更深层次需求，且还没有被解决的，并给出判断依据和原对话。不可以低于100个字",
                       "客户意向等级":"根据对话内容，判断客户的意向等级（S/A/B/C/孵化期/不愿意沟通），给出判断依据和原对话。例如：S，给出判断依据和原对话",
                       "销售卡点":"指出在销售与客户沟通过程中出现的阻碍或异议，且还没有被解决的，并给出判断依据和原对话。不可以低于100个字",
                       "次日动作":"给出销售人员下一步动作的建议，且还没有被解决的，不可以低于100个字",
                       "客户购买潜力"："",
                       "顾问卡点":"",
                       "客户购买潜力依据"："",
                       "顾问卡点依据":""
                        }
                    '''
    # 构建基础提示词
    prompt = f'''你是一个香港身份业务的高级营销专家，请将下面```中的对话内容和重点业务知识理解进行全面深度分析。
                 ```
                 重点业务知识理解：
                 {operate_context}
                 对话内容：
                 {dialogue_content}
                 ```
                 进行全面深度分析后，输出要求如下：
                 1.严格按照{json_example}的json格式输出,拒绝```json```的形式。
                 2.输出的内容中：
                    (1) 客户购买潜力:需要在指定范围内进行选取客户购买潜力，最多可选择三个，按照优先级排序，必须使用纯文本用逗号隔开。
                    (2) 客户购买潜力依据: 根据(1)选取出来的客户购买潜力给出一一对应的客户语料原文和分析依据。（原文依据必须保留时间按戳）
                        客户购买潜力依据的输出形式如下：
                        长期运营-已真实受雇于香港：2001-01-01 11:11:11 客户(927966) 客户语料原文。分析依据：针对长期运营-已真实受雇于香港对应的客户语料原文进行分析的依据内容；
                        ...
                    (3) 顾问卡点：需要在指定范围内进行选取顾问卡点，最多可选择两个，按照优先级排序，必须使用纯文本用逗号隔开
                    (4) 顾问卡点依据: 根据(3)选取出来的顾问卡点，给出一一对应的对话语料原文依据。（原文依据必须保留有时间按戳）
                        顾问卡点依据的输出形式形式如下：
                        价格异议：2001-01-01 11:11:11 客户(927966) 客户语料原文。分析依据：针对顾问卡点价格异议对应的客户语料原文进行分析依据内容；
                        ...
              '''
    return prompt


def process_dialogue_group(dialogue_info: DialogueInfo) -> List[AnalysisResult]:
    """处理单个对话组"""
    try:
        # 按round分割对话内容
        rounds = re.split(r'\n(?=round\d+:\d{4}-\d{2}-\d{2})', dialogue_info.dialogue_content)
        results = []

        logger.info(f"开始处理对话组 {dialogue_info.group_id}，共 {len(rounds)} 轮对话")

        for round_idx, round_content in enumerate(rounds, 1):
            if not round_content.strip():
                continue

            # 提取round的日期
            round_match = re.match(r'round\d+:(\d{4}-\d{2}-\d{2})', round_content)
            if not round_match:
                logger.warning(f"对话组 {dialogue_info.group_id} 的第 {round_idx} 轮对话没有有效的日期标记")
                continue

            跟进日期 = round_match.group(1)
            logger.info(f"处理对话组 {dialogue_info.group_id} 的第 {round_idx} 轮对话，日期: {跟进日期}")

            # 移除round标记行
            content_without_round = re.sub(r'^round\d+:\d{4}-\d{2}-\d{2}\n', '', round_content).strip()

            # 检查对话内容是否为空
            if not content_without_round:
                logger.warning(f"对话组 {dialogue_info.group_id} 的第 {round_idx} 轮对话内容为空")
                continue

            # 调用大模型进行分析
            analysis_result = analyze_dialogue_with_gpt(content_without_round)

            if not analysis_result:
                logger.warning(f"对话组 {dialogue_info.group_id} 在日期 {跟进日期} 的分析结果为空")
                # 为这一轮创建默认结果
                result = AnalysisResult(
                    会话组编号=dialogue_info.group_id,
                    客户名称=dialogue_info.customer_name,
                    销售人员名称=dialogue_info.sales_staff_name,
                    客户现状="对话过短，暂无更多信息",
                    客户需求="对话过短，暂无更多信息",
                    客户潜在需求="对话过短，暂无更多信息",
                    客户意向等级="对话过短，暂无更多信息",
                    销售卡点="对话过短，暂无更多信息",
                    次日动作="对话过短，暂无更多信息",
                    客户购买潜力="对话过短，暂无更多信息",
                    顾问卡点="对话过短，暂无更多信息",
                    客户购买潜力依据="对话过短，暂无更多信息",
                    顾问卡点依据="对话过短，暂无更多信息",
                    跟进日期=跟进日期,
                    round=round_idx
                )
                results.append(result)
                continue

            # 创建分析结果对象
            result = AnalysisResult(
                会话组编号=dialogue_info.group_id,
                客户名称=dialogue_info.customer_name,
                销售人员名称=dialogue_info.sales_staff_name,
                客户现状=analysis_result.get("客户现状", ""),
                客户需求=analysis_result.get("客户需求", ""),
                客户潜在需求=analysis_result.get("客户潜在需求", ""),
                客户意向等级=analysis_result.get("客户意向等级", ""),
                销售卡点=analysis_result.get("销售卡点", ""),
                次日动作=analysis_result.get("次日动作", ""),
                客户购买潜力=analysis_result.get("客户购买潜力", ""),
                顾问卡点=analysis_result.get("顾问卡点", ""),
                客户购买潜力依据=analysis_result.get("客户购买潜力依据", ""),
                顾问卡点依据=analysis_result.get("顾问卡点依据", ""),
                跟进日期=跟进日期,
                round=round_idx
            )
            results.append(result)
            logger.info(f"对话组 {dialogue_info.group_id} 的第 {round_idx} 轮对话分析完成")

        if not results:
            logger.warning(f"对话组 {dialogue_info.group_id} 没有生成任何分析结果")
            # 返回默认结果
            return [get_default_analysis_result(dialogue_info)]

        logger.info(f"对话组 {dialogue_info.group_id} 处理完成，共生成 {len(results)} 条分析结果")
        return results
    except Exception as e:
        thread_safe_log(logging.error, f"处理对话组时出错: {str(e)}")
        thread_safe_log(logging.error, traceback.format_exc())
        return [get_default_analysis_result(dialogue_info)]


def get_default_analysis_result(dialogue_info: DialogueInfo) -> AnalysisResult:
    """获取默认的分析结果"""
    return AnalysisResult(
        会话组编号=dialogue_info.group_id,
        客户名称=dialogue_info.customer_name,
        销售人员名称=dialogue_info.sales_staff_name,
        客户现状="分析失败",
        客户需求="分析失败",
        客户潜在需求="分析失败",
        客户意向等级="分析失败",
        销售卡点="分析失败",
        次日动作="分析失败",
        客户购买潜力="分析失败",
        顾问卡点="分析失败",
        客户购买潜力依据="分析失败",
        顾问卡点依据="分析失败",
        跟进日期=dialogue_info.跟进日期,
        round=None
    )


def write_result_to_csv(results: List[AnalysisResult], output_file: str):
    """将分析结果写入CSV文件"""
    try:
        with open(output_file, 'w', newline='', encoding='utf-8-sig') as f:
            fieldnames = [
                '会话组编号', '客户名称', '销售人员名称',
                '客户现状', '客户需求', '客户潜在需求', '客户意向等级', '销售卡点', '次日动作', '客户购买潜力',
                '顾问卡点', '客户购买潜力依据', '顾问卡点依据', '跟进日期', 'round']
            writer = csv.DictWriter(f, fieldnames=fieldnames)
            writer.writeheader()

            # 按会话组编号、round和跟进日期排序结果
            sorted_results = sorted(results, key=lambda x: (
                int(x.会话组编号) if x.会话组编号.isdigit() else float('inf'),  # 会话组编号
                x.round if x.round is not None else float('inf'),  # round
                x.跟进日期 if x.跟进日期 else ''  # 跟进日期
            ))

            for result in sorted_results:
                # 如果跟进日期为空，使用默认值
                跟进日期 = result.跟进日期 if result.跟进日期 else ''

                row = {
                    '会话组编号': result.会话组编号,
                    '客户名称': result.客户名称,
                    '销售人员名称': result.销售人员名称,
                    '客户现状': result.客户现状,
                    '客户需求': result.客户需求,
                    '客户潜在需求': result.客户潜在需求,
                    '客户意向等级': result.客户意向等级,
                    '销售卡点': result.销售卡点,
                    '次日动作': result.次日动作,
                    '客户购买潜力': result.客户购买潜力,
                    '顾问卡点': result.顾问卡点,
                    '客户购买潜力依据': result.客户购买潜力依据,
                    '跟进日期': 跟进日期,
                    '顾问卡点依据': result.顾问卡点依据,
                    # 添加round字段
                    'round': result.round
                }
                writer.writerow(row)

        logger.info(f"结果已成功写入文件: {output_file}")
        logger.info(f"共写入 {len(results)} 条记录")
    except Exception as e:
        logger.error(f"写入CSV文件时发生错误: {str(e)}")
        logger.error(traceback.format_exc())


@retry(stop=stop_after_attempt(2), wait=wait_exponential(multiplier=1, min=4, max=10))
def online_llm_streaming(prompt: str, request_timeout: int = 300) -> str:
    """使用在线流式LLM服务处理提示词，支持并发

    Args:
        prompt: 提示词文本
        request_timeout: 请求超时时间（秒）

    Returns:
        str: LLM返回的结果
    """
    try:
        # 使用已导入的online_streaming模块
        from online_streaming import online_llm_streaming as llm_service

        # 创建实例并调用
        ai_generate = llm_service(
            input_query=prompt,
            # api_key="app-Ipg9sBRE3FRKYX5TMVO6OthV", app-nRiSkZij2Bzzgf11VnFAFXs7
            api_key="app-nRiSkZij2Bzzgf11VnFAFXs7",
            route="/workflows/run",
            response_mode="blocking"
        )

        # 运行并获取结果
        response = ai_generate.run(timeout=request_timeout)

        if not response:
            thread_safe_log(logger.error, "AI生成返回空结果")
            return "{}"

        return response

    except Exception as e:
        thread_safe_log(
            logger.error,
            f"调用在线LLM服务时发生错误: {str(e)}"
        )
        thread_safe_log(logger.error, traceback.format_exc())
        return "{}"


def analyze_dialogue_with_gpt(dialogue_content: str) -> Dict[str, Any]:
    """使用GPT分析对话内容"""
    try:
        # 设置更小的分段大小以确保不超过token限制
        max_chunk_size = 120000  # 大约对应40000个token

        # 检查内容长度并决定是否需要分段
        if len(dialogue_content) > max_chunk_size:
            logger.info(f"对话内容超过{max_chunk_size}字符，进行分段处理")

            # 按对话行分割内容
            dialogue_lines = dialogue_content.split('\n')
            chunks = []
            current_chunk = []
            current_size = 0

            for line in dialogue_lines:
                line_size = len(line) + 1  # +1 for newline
                if current_size + line_size > max_chunk_size and current_chunk:
                    chunks.append('\n'.join(current_chunk))
                    current_chunk = [line]
                    current_size = line_size
                else:
                    current_chunk.append(line)
                    current_size += line_size

            if current_chunk:
                chunks.append('\n'.join(current_chunk))

            logger.info(f"对话被分为{len(chunks)}段进行处理")

            # 处理每个分段
            results = []
            for i, chunk in enumerate(chunks, 1):
                logger.info(f"处理第{i}/{len(chunks)}段内容")
                prompt = get_analysis_prompt(chunk, [])
                response = online_llm_streaming(prompt)
                chunk_result = extract_json_from_text(response)
                if chunk_result and all(k in chunk_result for k in
                                        ["客户现状", "客户需求", "客户潜在需求", "客户意向等级", "销售卡点", "次日动作",
                                         "客户购买潜力", "顾问卡点", "客户购买潜力依据", "顾问卡点依据"]):
                    results.append(chunk_result)

            # 合并所有分段的结果
            if results:
                # 初始化合并结果
                merged_result = results[0].copy()

                # 收集所有分段的结果
                all_customer_status = []
                all_customer_needs = []
                all_customer_potential_needs = []
                all_customer_intention_levels = []
                all_sales_card_points = []
                all_next_actions = []

                for result in results:
                    all_customer_status.append(result.get("客户现状", ""))
                    all_customer_needs.append(result.get("客户需求", ""))
                    all_customer_potential_needs.append(result.get("客户潜在需求", ""))
                    all_customer_intention_levels.append(result.get("客户意向等级", ""))
                    all_sales_card_points.append(result.get("销售卡点", ""))
                    all_next_actions.append(result.get("次日动作", ""))
                    all_customer_potential = result.get("客户购买潜力", "")
                    all_customer_card = result.get("顾问卡点", "")
                    all_customer_potential_reason = result.get("客户购买潜力依据", "")
                    all_customer_card_reason = result.get("顾问卡点依据", "")

                # 合并结果，使用最新的非空值
                merged_result["客户现状"] = all_customer_status[-1] if all_customer_status else ""
                merged_result["客户需求"] = all_customer_needs[-1] if all_customer_needs else ""
                merged_result["客户潜在需求"] = all_customer_potential_needs[-1] if all_customer_potential_needs else ""
                merged_result["客户意向等级"] = all_customer_intention_levels[
                    -1] if all_customer_intention_levels else ""
                merged_result["销售卡点"] = all_sales_card_points[-1] if all_sales_card_points else ""
                merged_result["次日动作"] = all_next_actions[-1] if all_next_actions else ""
                merged_result["客户购买潜力"] = all_customer_potential if all_customer_potential else ""
                merged_result["顾问卡点"] = all_customer_card if all_customer_card else ""
                merged_result["客户购买潜力依据"] = all_customer_potential_reason if all_customer_potential_reason else ""
                merged_result["顾问卡点依据"] = all_customer_card_reason if all_customer_card_reason else ""

                return merged_result
            return {}
        else:
            # 内容未超过限制，只调用一次
            prompt = get_analysis_prompt(dialogue_content, [])
            response = online_llm_streaming(prompt)
            # result = extract_json_from_text(response)
            try:
                result = json.loads(response.replace('\n',''))
            except Exception as e:
                logger.error(f"提取JSON时发生错误: {str(e)}")
                logger.error(traceback.format_exc())
                print('出错的response:',response)
                result = {}

            # 确保结果包含所有必要字段
            if result:
                if "客户现状" not in result:
                    result["客户现状"] = ""
                if "客户需求" not in result:
                    result["客户需求"] = ""
                if "客户潜在需求" not in result:
                    result["客户潜在需求"] = ""
                if "客户意向等级" not in result:
                    result["客户意向等级"] = ""
                if "销售卡点" not in result:
                    result["销售卡点"] = ""
                if "次日动作" not in result:
                    result["次日动作"] = ""
                if "客户购买潜力" not in result:
                    result["客户购买潜力"] = ""
                if "顾问卡点" not in result:
                    result["顾问卡点"] = ""
                if "客户购买潜力依据" not in result:
                    result["客户购买潜力依据"] = ""
                if "顾问卡点依据" not in result:
                    result["顾问卡点依据"] = ""
                return result
            return {}

    except Exception as e:
        logger.error(f"分析对话时发生错误: {str(e)}")
        logger.error(traceback.format_exc())
        return {}


def merge_analysis_results(results: List[Dict[str, Any]]) -> Dict[str, Any]:
    """合并多个分析结果

    Args:
        results: 分析结果列表

    Returns:
        合并后的结果
    """
    if not results:
        return {}

    # 使用第一个结果作为基础
    merged = results[0].copy()

    # 处理新增的标签字段
    tag_fields = ["客户现状", "客户需求", "客户潜在需求", "客户意向等级", "销售卡点", "次日动作", "客户购买潜力",
                  "顾问卡点", "客户购买潜力依据", "顾问卡点依据"]
    for field in tag_fields:
        # 使用最后一个非空值
        for result in reversed(results):
            if field in result and result[field]:
                merged[field] = result[field]
                break

    return merged


def process_dialogue_batch(dialogues: List[DialogueInfo], max_workers: int = 10) -> List[AnalysisResult]:
    """并发处理对话组

    Args:
        dialogues: 对话组列表
        max_workers: 最大并发数

    Returns:
        处理结果列表
    """
    results = []
    total = len(dialogues)

    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        # 提交所有任务
        future_to_dialogue = {
            executor.submit(process_dialogue_group, dialogue): dialogue
            for dialogue in dialogues
        }

        # 处理完成的任务
        completed = 0
        for future in concurrent.futures.as_completed(future_to_dialogue):
            dialogue = future_to_dialogue[future]
            try:
                result = future.result()
                if result:
                    results.extend(result)
                completed += 1
                thread_safe_log(
                    logger.info,
                    f"处理进度: {completed}/{total} ({completed / total * 100:.1f}%)"
                )
            except Exception as e:
                thread_safe_log(
                    logger.error,
                    f"处理对话组 {dialogue.group_id} 时发生错误: {str(e)}"
                )
                thread_safe_log(logger.error, traceback.format_exc())

    return results


def extract_json_from_text(text: str) -> Dict[str, Any]:
    """从文本中提取JSON内容

    Args:
        text: 包含JSON的文本

    Returns:
        解析后的JSON字典
    """
    try:
        # 首先尝试解析API响应
        try:
            api_response = json.loads(text)
            if isinstance(api_response, dict) and 'data' in api_response:
                if 'outputs' in api_response['data'] and 'output' in api_response['data']['outputs']:
                    output_str = api_response['data']['outputs']['output']
                    try:
                        # 清理JSON字符串
                        output_str = re.sub(r'[\n\r\t]', ' ', output_str)  # 移除换行和制表符
                        output_str = re.sub(r'\s+', ' ', output_str)  # 合并多个空格
                        output_str = re.sub(r',\s*([}\]])', r'\1', output_str)  # 移除末尾多余的逗号
                        output_str = re.sub(r'。(?=\s*[}\]])', '.', output_str)  # 将中文句号转换为英文句号
                        output_str = re.sub(r'E:\s*无意向', 'E', output_str)  # 修正意向标签格式

                        result = json.loads(output_str)

                        # 验证必要字段
                        required_fields = ["会话session_id", "客户名称", "销售人员名称",
                                           "客户现状", "客户需求", "客户潜在需求", "客户意向等级",
                                           "销售卡点", "次日动作", "客户购买潜力", "顾问卡点",
                                           "客户购买潜力依据", "顾问卡点依据"]
                        if all(field in result for field in required_fields):
                            return result
                        else:
                            logger.error("JSON缺少必要字段")
                            return {}
                    except json.JSONDecodeError as e:
                        logger.error(f"解析output字段中的JSON时出错: {str(e)}")
                        return {}
        except:
            pass

        # 如果API响应解析失败，尝试直接查找JSON对象
        json_match = re.search(r'\{[\s\S]*?\}', text)
        if json_match:
            json_str = json_match.group(0)
            # 清理JSON字符串
            json_str = re.sub(r'[\n\r\t]', ' ', json_str)
            json_str = re.sub(r'\s+', ' ', json_str)
            json_str = re.sub(r',\s*([}\]])', r'\1', json_str)
            json_str = re.sub(r'。(?=\s*[}\]])', '.', json_str)
            json_str = re.sub(r'E:\s*无意向', 'E', json_str)

            result = json.loads(json_str)
            return result

        logger.error("未找到有效的JSON内容")
        return {}

    except json.JSONDecodeError as e:
        logger.error(f"JSON解析错误: {str(e)}")
        return {}
    except Exception as e:
        logger.error(f"提取JSON时发生错误: {str(e)}")
        logger.error(traceback.format_exc())
        return {}


def customer_day_analysis(data_file_dt=''):
    """主函数"""
    try:
        # 配置日志
        setup_logging()

        # 记录开始时间
        start_time = datetime.now()
        logger.info("=== 开始运行销售对话分析系统 ===")
        logger.info(f"开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")

        # 创建输出目录
        output_dir = 'user_day_research_result'
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)
            logger.info(f"创建输出目录: {output_dir}")

        # 读取对话数据文件
        logger.info("正在读取对话数据文件...")
        if data_file_dt == '':
            target_dir = f'dialog_source_data/{datetime.today().strftime('%Y%m%d')}'
        else:
            target_dir = f'dialog_source_data/{data_file_dt}'
        content = read_source_file(target_dir)
        logger.info(f"文件总字符数: {len(content)}")
        logger.info(f"文件总行数: {len(content.splitlines())}")

        # 提取对话组
        logger.info("开始提取对话组...")
        dialogue_groups = extract_dialogue_groups(content)

        # 并发处理对话组
        logger.info(f"开始并发处理 {len(dialogue_groups)} 个对话组...")
        results = process_dialogue_batch(dialogue_groups)

        # 写入结果到CSV文件
        if results:
            logger.info("正在将结果写入CSV文件...")
            if data_file_dt == '':
                # timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                timestamp = datetime.now().strftime("%Y%m%d")
                output_file = os.path.join(output_dir, f'user_day_research_result_{timestamp}.csv')
            else:
                output_file = os.path.join(output_dir, f'user_day_research_result_{data_file_dt}.csv')
            write_result_to_csv(results, output_file)
            logger.info(f"结果已写入文件: {output_file}")
        else:
            logger.warning("没有生成任何分析结果")

        # 记录结束时间和统计信息
        end_time = datetime.now()
        duration = (end_time - start_time).total_seconds()
        logger.info("=== 处理完成 ===")
        logger.info(f"总耗时: {duration:.2f} 秒")
        logger.info(f"成功处理对话组数量: {len(dialogue_groups)}/{len(dialogue_groups)}")
        logger.info(f"生成分析结果数量: {len(results)} 条")
        logger.info(f"结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")

    except Exception as e:
        logger.error(f"程序执行过程中发生错误: {str(e)}")
        logger.error(traceback.format_exc())


if __name__ == "__main__":
    customer_day_analysis()
