import json
import os
import re
import time
from typing import Any, Dict
from flask_app.general.llm.大模型通用函数 import read_txt_to_string
from flask_app.general.file2markdown import convert_file_to_markdown
from flask_app.general.format_change import get_pdf_page_count, pdf2docx
from flask_app.general.json_utils import extract_content_from_json
from flask_app.general.llm.model_continue_query import process_continue_answers
from flask_app.general.llm.通义千问long import upload_file, qianwen_long, qianwen_long_stream
from flask_app.general.llm.qianwen_plus import qianwen_plus


def remove_unknown_scores(data):
    if isinstance(data, dict):
        return {
            k: remove_unknown_scores(v)
            for k, v in data.items()
            if not (k == "评分" and v in ["未知", "/", ""])
        }
    elif isinstance(data, list):
        return [remove_unknown_scores(item) for item in data]
    else:
        return data

def combine_technical_and_business(data):
    '''
    后处理，区分技术评分和商务评分，给外键添加总的评分
    '''
    data = remove_unknown_scores(data)
    extracted_data = {
        '技术评分': {},
        '商务评分': {}
    }

    # 用于累加 '其他评分' 的总分
    other_total_score = 0
    other_items = {}
    def process_data(data: Dict[str, Any]) -> Dict[str, Any]:
        """
        处理给定的数据字典，如果存在外键 '技术评分' 且其子键符合 '技术评分-<数字>' 格式，
        则删除外键 '技术评分' 并将其子键提升到更高层级。即'技术评分-1' '技术评分-2'作为外键
        Args:
            data (Dict[str, Any]): 输入的数据字典。

        Returns:
            Dict[str, Any]: 处理后的数据字典。
        """
        # 定义匹配 '技术评分-数字' 的正则表达式
        pattern = re.compile(r'^技术评分-\d+$')

        # 创建一个新的字典以避免在遍历时修改原字典
        new_data = {}

        for key, value in data.items():
            if key == "技术评分" and isinstance(value, dict):
                # 检查所有子键是否符合 '技术评分-<数字>' 格式
                if all(pattern.match(child_key) for child_key in value.keys()):
                    # 如果符合，提升子键到更高层级
                    new_data.update(value)
                else:
                    # 如果不符合，保留原键值
                    new_data[key] = process_data(value) if isinstance(value, dict) else value
            elif isinstance(value, dict):
                # 递归处理子字典
                new_data[key] = process_data(value)
            else:
                # 对于非字典类型的值，直接保留
                new_data[key] = value

        return new_data

    def extract_nested(data):
        nonlocal other_total_score, other_items
        if isinstance(data, dict):
            for key, value in data.items():
                # 区分 '技术评分'
                if key.startswith('技术评分'):
                    total_score = compute_total_score({key: value})
                    if 0 < total_score <= 100:
                        new_key = f'{key}({total_score}分)'
                    else:
                        new_key = f'{key}'
                    extracted_data['技术评分'][new_key] = value
                    # 匹配到后，不再递归处理其子项
                    continue

                # 区分 '商务评分'
                elif key == '商务评分':
                    total_score = compute_total_score({key: value})
                    if 0 < total_score <= 100:
                        new_key = f'商务评分({total_score}分)'
                    else:
                        new_key = '商务评分'
                    extracted_data['商务评分'][new_key] = value
                    # 匹配到后，不再递归处理其子项
                    continue

                # 区分 '投标报价评分'
                elif key == '投标报价评分':
                    total_score = compute_total_score({key: value})
                    if 0 < total_score <= 100:
                        new_key = f'投标报价评分({total_score}分)'
                    else:
                        new_key = '投标报价评分'
                    extracted_data['商务评分'][new_key] = value
                    # 匹配到后，不再递归处理其子项
                    continue

                # 其他键名归为 '其他评分'
                else:
                    # 计算当前项的总分
                    current_score = compute_total_score({key: value})
                    other_total_score += current_score
                    # 保持原键名，不添加（分数）
                    other_items[key] = value
    processed_data=process_data(data)
    extract_nested(processed_data)

    # 如果有 '其他评分'，替换为包含总分的键名
    if other_items:
        if other_total_score > 0:
            new_key = f'其他评分({other_total_score}分)'
        else:
            new_key = '其他评分'
        extracted_data['商务评分'][new_key] = other_items

    return extracted_data


# 防止外键只有一个'一包'的情况
def process_data_based_on_key(data):
    exclude_word = ["包", "未知", "评分因素"]
    # 获取字典的键列表
    keys = list(data.keys())
    # 检查键的数量是否为1并且 exclude_word 中的任何词包含在 keys[0] 中
    if len(keys) == 1 and any(word in keys[0] for word in exclude_word):
        # 返回内层的字典
        return data[keys[0]]

    # 如果条件不满足，则返回原始字典
    return data

def compute_total_score(data):
    """
    计算传入字典的总分。

    规则：
    - 输入字典只有一个外层键。
    - 遍历该外层键的所有子键：
        - 如果子键名中包含 '(XX分)' 或 '（XX分）'，提取 XX 并累加到总分中，不再处理其子项。
        - 如果子键名中不包含这样的分数，遍历其子项，查找键名为 '评分' 的键，提取分数并累加。
            - '评分' 的值可以是 'XX分' 或整数。
        - 如果没有找到 '评分' 键，则该项分数为 0。
    """
    total = 0

    # 确保输入数据为字典且只有一个外层键
    if not isinstance(data, dict) or len(data) != 1:
        raise ValueError("输入数据必须是一个只有一个外层键的字典。")

    # 获取唯一的外层键和值
    outer_key, outer_value = next(iter(data.items()))

    # 更新后的正则表达式，匹配中英文括号中的分数，如 '(24分)' 或 '（24分）'   #考虑了空格的影响
    score_pattern = re.compile(r'[（(]\s*(\d+)\s*分\s*[)）]')

    def process_node(node):
        nonlocal total
        if isinstance(node, dict):
            for key, value in node.items():
                # 检查键名中是否包含 '(XX分)' 或 '（XX分）'
                match = score_pattern.search(key)
                if match:
                    score = int(match.group(1))
                    total += score
                    # 匹配到后，不再递归处理其子项
                    continue
                elif key == '评分':
                    if isinstance(value, str):
                        # 提取 '评分' 键的值中的数字，如 '20分'
                        match_score = re.match(r'\s*(\d+)\s*分\s*', value)
                        if match_score:
                            score = int(match_score.group(1))
                            total += score
                        else:
                            # 如果 '评分' 值不符合格式，默认加 0
                            total += 0
                    elif isinstance(value, int):
                        # 如果 '评分' 键的值是整数，直接累加
                        total += value
                    else:
                        # 如果 '评分' 键的值既不是字符串也不是整数，默认加 0
                        total += 0
                else:
                    # 如果键名不包含分数，递归处理其子项
                    process_node(value)
        elif isinstance(node, list):
            for item in node:
                process_node(item)
        else:
            # 如果是其他类型的数据，忽略
            pass

    # 开始递归处理外层键的值
    process_node(outer_value)

    return total

def reorganize_data(input_dict, include=None):
    """
    重组输入字典，将“技术评分”和“商务评分”提升为最外层键，
    并将包含在 include 列表中的包名的数据嵌套在相应的评分类别下。

    如果 input_dict 的顶层键不包含任何 include 列表中的项，则返回原始字典。

    :param input_dict: 原始输入字典
    :param include: 包名列表，例如 ['一包', '二包', '三包']
    :return: 重组后的字典
    """
    if include is None:
        include = []

    # 检查是否有任何顶层键包含在 include 列表中
    has_include = any(key in include for key in input_dict.keys())

    if not has_include:
        # 没有包含任何指定的包名，直接返回原始字典
        return input_dict

    # 初始化新的字典结构
    reorganized = {
        "技术评分": {},
        "商务评分": {}
    }

    # 遍历每一个包（如 "一包", "二包"）
    for package, categories in input_dict.items():
        # 处理技术评分
        if "技术评分" in categories:
            reorganized["技术评分"][package] = categories["技术评分"]

        # 处理商务评分
        if "商务评分" in categories:
            reorganized["商务评分"][package] = categories["商务评分"]

    return reorganized

def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type):
    '''
    传入截取后的评分章节，输出评分细则
    '''
    # 定义默认的评审结果字典
    DEFAULT_EVALUATION_REVIEW = {
        "技术评分": "未解析到'技术评分'项！",
        "商务评分": "未解析到'商务评分'项！"
    }
    # 如果 truncate_file 是空字符串，直接返回包含空字符串的字典
    if not evaluation_method_path:
        return DEFAULT_EVALUATION_REVIEW.copy()

    def generate_prompt(zb_type):
        common_prompt="""你是一个对招投标业务非常熟悉的专家。根据该文档中的评标办法表格，请你列出该文件的技术评分，商务评分，投标报价评分以及它们对应的具体评分要求，请以JSON格式返回结果。
**格式要求**：
1.总体结构：
    -JSON 的最外层包含三个键：'技术评分'、'商务评分' 和 '投标报价评分'。
    -每个大项（如技术评分、商务评分）下包含具体的评分项，评分项按以下规则表示。
2.评分项表示规则：
    -每个评分项由评分因素和评分标准组成。
    -评分因素：
        若评分因素（内容、项）存在嵌套关系（通过表格结构判断）：主评分因素需附加括号，括号中注明该主评分因素的总分，若无具体评分，则无需添加该括号，例如：产品技术响应(8分)；子评分因素作为嵌套键名，列在主评分因素之下，无需再附加括号表示评分。
            -注意：禁止通过归纳、推测或自行总结来生成子评分因素，尤其不可根据'评分标准'中的打分要求来反向总结'子评分因素'。
        若评分因素（内容、项）不存在嵌套关系：键名直接为评分因素，无需附加括号表示总分。
    -评分标准：
        -评分因素的键值为评分标准，它是列表形式，列表中包含描述分值及评分细则的字典。
            -字典个数：
                默认为1个字典，若某评分因素包括多个评分细则（通过表格结构判断，一个单元格视为一个评分细则），字典个数等于评分细则数。
            -字典结构如下：
                评分：一个打分点的分值即最高分（如'8分'），字符串类型；不能是一个范围数字（如0-8分）；若为定性指标（如“合格制”），可标明相应的定性指标；无评分时可删去'评分'键值对。
                要求：一个打分点的评分细则，即一个表格单元格内的所有内容，禁止拆分。
                    例："拥有一级证书得3分，拥有二级证书得1分，其他不得分。"
                    示例输出：{
                        "评分": "3分",
                        "要求": "拥有一级证书得3分，拥有二级证书得1分，其他不得分。"
                    }
                    禁止拆分出三个字典：
                    {
                        "评分": "3分",
                        "要求": "拥有一级证书得3分"
                    },
                    {
                        "评分": "1分",
                        "要求": "拥有二级证书得1分"
                    },
                    {
                        "评分": "0分",
                        "要求": "其他不得分。"
                    }
            -禁止情况：
                禁止将同个单元格内的内容拆分至多个字典中；禁止遗漏单元格内任何信息，包括注释的内容。
3.备注信息：
    -若评分部分包含附加信息（如大项评分的整体要求，未直接归属于具体评分项），需添加一个 备注 键，值为该附加信息。

**要求与指南**：
1. 请首先定位评分表，请不要遗漏任何一个评分表的信息，不要回答有关资格审查、符合审查的内容，也不要从评标办法正文中提取回答。
2. 若表中的评分大项不是这三个，或者有额外的评分大项：
      -请你根据语义及你对招投标业务的熟悉，分别映射到'技术评分'、'商务评分'、'投标报价评分'，而不必按照表格中大项名称。
      -映射关系可以是“一对一”或“多对一”，例如两个评分大项可以同时归类到“技术评分”。关键是不遗漏表中的评分大项，确保每个大项都被映射。
    若表中只有评分因素而无评分大项：
      -请将评分因素正确地归类到'技术评分'、'商务评分'、'投标报价评分'，不要遗漏任何一个评分因素。
3. 若表格中商务和技术评分混合一起，或者有部分评分因素无法通过表格结构明确归类：
    -请根据你对招投标业务的熟悉，对表格中的评分因素进行准确分类。关键是确保每个评分因素都能被归类到'技术评分'或'商务评分'或'投标报价评分'，不要遗漏任何一个评分因素。
    
**特殊情况**：
    1. 缺失评分项：若大项的'xx评分'要求未在文中说明，则键名'xx评分'的键值设为字符串'本项目无xx评分项'，例如"技术评分":"本项目无技术评分项"，而非默认的字典格式，请基于提供的实际招标文件内容，禁止捏造回答。 
    2. 其他评分：默认情况大项评分仅有'技术评分''商务评分''投标报价评分'，若在充分归类之后，表格中仍有评分因素未被归类，才添加大项评分'其他评分'保存该内容。
    3. 多包评分：默认只有一包，最外层键名为各大评分项，而不是'一包'；但是如果该招标、采购活动有多个分包且每个分包有独自的评分表，则最外层键名为对应的包名，如'一包''二包'，内部才是各大评分项。
    4. 多张技术评分表：若同一包下有多张技术评分表，请不要遗漏任何一个评分表的信息,此时最外层键名'技术评分'替换为'技术评分-d'，d为自然数，从1开始，分别保存每张技术评分表的信息。
        -例如有'技术评分标准（1）其他项目'，'技术评分标准（2）施工类'，算作两个技术评分表，最外层的键名分别为'技术评分-1''技术评分-2'，替换默认的'技术评分'

**禁止内容**：
1. 确保所有输出内容均基于提供的实际招标文件内容（除了最外层的三个评分大项名称），不使用任何预设的示例作为回答，也禁止捏造评分标准。
2. 不得擅自添加不属于评审因素的键名以及 `'备注'` 之外的其他键名。
3. 不得遗漏评分表中的任何评分因素，确保每个评分因素都被正确归类到评分大项下。
        """
        if zb_type == 1:
            example_output="""
以下为示例输出，仅供格式参考：
    {
        "技术评分": {
            "实施方案(16分)":{
                "总体实施方案":[
                    {
                        "评分":8,
                        "要求":"根据投标人总体实施方案进行评分"
                    }
                ],
                "项目实施要点":[
                    {
                        "评分":8,
                        "要求":"根据投标人对项目实施要点、难点进行评分。"
                    }
                ]
            },
            "设计创意": [
                {
                    "评分": "10分",
                    "要求": "主题突出，形式多样，内容与形式完美统一，得10分，其他酌情打分。"
                }
            ],
            "备注": "技术标采用暗标形式，暗标不得出现投标人名称、人员姓名。"
        },
        "商务评分": {
            "主要监理岗位的职责": [
                {
                    "评分": "4分",
                    "要求": "1、总监理工程师的职责全面、清晰、合理得 2 分；一般的 1 分。2、其他主要监理人员及岗位的职责全面、清晰、合理得 2 分；一般的 1 分。"
                     }
                 ],
                 "制造商实力": [
                     {
                         "评分": "3分",
                         "要求": "一级证书得3分，二级证书得1分，其他不得分。"
                     },
                     {
                         "评分": "2分",
                         "要求": "行业销量排名连续前 2 名，得 2 分，第 3-6 名得 0.5 分，其他不得分。"
                     }
                 ]
             },
        "投标报价评分": {
            "投标报价是否出现违反计价规范": [
                {
                    "评分": "合格制",
                    "要求": "A:投标报价未违反计价规范的，评审意见为“合格”；B：投标报价违反计价规范的，评审意见为“不合格”"
                }
            ]
        }
    }
        """
        elif zb_type == 2:
            example_output="""
以下为示例输出，仅供格式参考：
    {
        "技术评分": {
            "产品技术响应(8分)":{
                "常规参数符合":[
                    {
                        "评分":"4分",
                        "要求":"未标★项为常规参数每条负偏离扣1分，本项满分4分。"
                    }
                ],
                "控制系统":[
                    {
                        "评分":"4分",
                        "要求":"所投电梯控制系统技术先进、市场美誉度高，与整梯同品牌，得4分；所投电梯控制系统的技术基本先进、市场美誉度较高，与整梯不同品牌，得2分。"
                    }
                ]
            },
            "实施方案": [
                {
                    "评分": "10分",
                    "要求": "实施方案清晰、完整、合理、可行的得 10 分。实施方案一般的得5分"
                }
            ],
            "备注": "注：若不满足“与公安部、省公安厅、随州市公安局高清视频会议系统无缝对接互联互通”的要求，则本项技术部分不得分。"
        },
        "商务评分": {
            "主要监理岗位的职责": [
                {
                    "评分": "4分",
                    "要求": "1、总监理工程师的职责全面、清晰、合理得 2 分；一般的 1 分。2、其他主要监理人员及岗位的职责全面、清晰、合理得 2 分；一般的 1 分。"
                }
            ],
            "制造商实力": [
                {
                    "评分": "3分",
                    "要求": "一级证书得3分，二级证书得1分，其他不得分"
                },
                {
                    "评分": "2分",
                    "要求": "行业销量排名连续前 2 名，得 2 分，第 3-6 名得 0.5 分，其他不得分"
                }
            ]
        },
        "投标报价评分": {
            "投标报价是否出现违反计价规范": [
                {
                    "评分": "合格制",
                    "要求": "A:投标报价未违反计价规范的，评审意见为“合格”；B：投标报价违反计价规范的，评审意见为“不合格”"
                }
            ]
        }
    }
            """
        else:
            raise ValueError("Invalid zb_type. 请输入 1 或 2。")
            # 拼接最终的提示内容
        final_prompt = common_prompt + example_output
        return final_prompt

    def run_first_query(file_path):
        print("判断有无评分")
        # 上传文件并获取文件ID
        file_id = upload_file(file_path)

        # 定义用户查询
        query = (
            """请根据以下指南判断该文档是否包含关于技术评分、商务评分或投标报价的具体评分要求和标准：
        1. 若文档中以表格形式展示了评分要求，且包含评分因素（如“技术评分”或“商务评分”或更细的评分因素）及相应的评分标准；即使评分方式为定性（无具体分值），也应视为满足要求。
        2. 如果文档中仅描述了评标流程，但未提供具体的评分标准，则应视为不满足条件。
        3. 虽然竞争性磋商文件通常不包含评分要求，但若文档满足第1条的内容，也应视为符合要求。
        请仅返回“是”或“否”，不附加其他解释或内容。
        """
        )  # 应对竞争性谈判这种无评分要求的情况

        # 执行查询
        return qianwen_long(file_id, query),file_id
    def run_second_qeury(file_id,processed_filepath,model_type):
        print("获取评分项...")
        # 执行 user_query 相关的逻辑
        user_query=generate_prompt(zb_type)
        if model_type==4:
            full_text = read_txt_to_string(processed_filepath)
            user_query = f"文本内容：\n{full_text}\n" + user_query
        questions_to_continue = []
        temp_final={}
        if model_type==4:
            # evaluation_res=doubao_model(user_query,True)
            evaluation_res = qianwen_plus(user_query, True)
        else:
            evaluation_res = qianwen_long_stream(file_id, user_query,2,1,True)  # 有些重复的键名，只有qianwen_long_text能保留
        message = evaluation_res[0]
        # print(message)
        total_tokens = evaluation_res[1]
        # 清理和处理响应
        cleaned_evaluation_res = extract_content_from_json(message,True)  # 带上True处理重复键名的情况-》生成列表
        # print(json.dumps(cleaned_evaluation_res,ensure_ascii=False,indent=4))
        max_tokens = 7900 if model_type == 4 else 5900
        if not cleaned_evaluation_res and total_tokens > max_tokens:
            print(f"total_tokens: {total_tokens}")
            questions_to_continue.append((user_query, message))
        else:
            temp_final.update(cleaned_evaluation_res)
        if questions_to_continue:
            continued_results = process_continue_answers(questions_to_continue, model_type, file_id)
            temp_final.update(continued_results)
        result_data = process_data_based_on_key(temp_final)  # 处理不知名外键的情况
        include = ['一包', '二包', '三包', '四包', '五包']
        updated_jsons = {}
        # 检查是否有外层键匹配 include 列表
        if any(key for key in result_data if
               any(included in key for included in include)):  # 检查result_data中的任何键是否包含include列表中的任意一个项。
            # 有匹配的项，处理这些项
            for key in result_data:
                if any(item in key for item in include):
                    inner_dict = result_data[key]
                    updated_jsons[key] = combine_technical_and_business(inner_dict)  # 对于分包，单独对分包内的'技术评分''商务评分'作处理
        else:
            # 没有匹配的项，对整个字典运行
            updated_jsons = combine_technical_and_business(result_data)
        final_res = reorganize_data(updated_jsons, include)  # 重新组织字典，尤其是分包的情况
        return final_res

    try:
        judge_res, file_id = run_first_query(evaluation_method_path)
        print(judge_res)
        eval_path = os.path.abspath(evaluation_method_path)
        invalid_eval_path = os.path.abspath(invalid_path)
        # 获取 evaluation_method_path 所在的目录
        eval_dir = os.path.dirname(eval_path)
        # extract3.txt是invalid_path->markdown，不一定存在
        extract3_path = os.path.join(eval_dir, "extract3.txt")
        if '是' in judge_res:
            if get_pdf_page_count(evaluation_method_path) <= 20:
                model_type = 4  # qianwen-plus
                processed_filepath = convert_file_to_markdown(evaluation_method_path, "extract2.txt")
            else:
                if eval_path == invalid_eval_path:
                    if os.path.exists(extract3_path):
                        print("qianwen-plus1:extract3.txt")
                        processed_filepath = extract3_path
                        model_type = 4  # 使用 qianwen-plus
                    else:
                        processed_filepath = ""
                        file_id = upload_file(invalid_eval_path)
                        model_type = 3  # 默认 qianwen-long-stream
                else:
                    evaluation_method_docxpath = pdf2docx(evaluation_method_path)
                    file_id = upload_file(evaluation_method_docxpath)
                    processed_filepath = ""
                    model_type = 3  # qianwen-long_stream
            return run_second_qeury(file_id, processed_filepath, model_type)

        # 如果 judge_res 不包含 '是' 并且路径不同，处理 invalid_path
        if eval_path != invalid_eval_path:
            judge_res_invalid, file_id_invalid = run_first_query(invalid_path)
            if '是' in judge_res_invalid:
                if os.path.exists(extract3_path):
                    print("qianwen-plus2:extract3.txt")
                    processed_filepath = extract3_path
                    model_type = 4  # qianwen-plus
                else:
                    processed_filepath = ""
                    model_type = 3  # 默认 qianwen-long_stream
                return run_second_qeury(file_id_invalid, processed_filepath, model_type)

        # 如果路径相同或未找到 '是'，返回默认结果
        return DEFAULT_EVALUATION_REVIEW.copy()

    except Exception as e:
        print(f"Error in combine_evaluation_standards: {e}")
        # 出错时返回默认结果
        return DEFAULT_EVALUATION_REVIEW.copy()

if __name__ == "__main__":
    start_time=time.time()
    # truncate_file=r"C:\Users\Administrator\Desktop\招标文件-采购类\tmp2\2024-新疆-塔城地区公安局食药环分局快检实验室项目_evaluation_method.pdf"
    evaluation_method_path = r'C:\Users\Administrator\Downloads\2022-广东-鹏华基金管理有限公司深圳深业上城办公室装修项目.pdf'
    invalid_path=r'C:\Users\Administrator\Downloads\2022-广东-鹏华基金管理有限公司深圳深业上城办公室装修项目.pdf'
    # truncate_file = "C:\\Users\\Administrator\\Desktop\\货物标\\output2\\2-招标文件（统计局智能终端二次招标）_evaluation_method.pdf"
    # truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output2\\广水市妇幼招标文件最新（W改）_evaluation_method.pdf"
    # truncate_file = "C:\\Users\\Administrator\\Desktop\\fsdownload\\2d481945-1f82-45a5-8e56-7fafea4a7793\\ztbfile_evaluation_method.pdf"
    # truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\ztbfile_evaluation_method.pdf"
    res = combine_evaluation_standards(evaluation_method_path,invalid_path,2)
    print("------------------------------")
    print(json.dumps(res, ensure_ascii=False, indent=4))
    end_time=time.time()
    print("elapsed time:"+str(end_time-start_time))