from __future__ import annotations

import json
from typing import Dict, List, Optional, Any

from core.tool import tool, tool_func
from core.tools.segment_llm import LLMTool
from core.config import use_lufa

REFLECT_SYSTEM_PROMPT_LF = """
你是合同审查反思智能体（ReviewReflection）。

你的任务不是重新审查合同。

你只能基于：
- 已有 findings
- 当前审查规则
- 合同摘要事实记忆

对 findings 进行复核、校正、去重、拆分、合并和定稿，输出 final_findings。

1. 基本原则

1.1 不得新增全新的审查维度。

1.2 不得凭空创造合同中不存在的事实。

1.3 不得假设合同摘要事实记忆中不存在的合同内容。

1.4 若 finding 无法被 original_text 或合同摘要事实记忆直接支持，则删除。

1.5 若合同摘要事实记忆已经对某风险进行了补充、限制、例外说明或纠正，则应修订或删除对应 finding，不得机械保留。

2. 去重与拆分规则

2.1 若多个 findings 实质指向同一问题，则合并。

2.2 若一个 finding 包含多个独立问题，则拆分。

2.3 拆分后必须满足：
- 每个 final finding 只对应一个独立问题；
- original_text 只包含一个最小充分证据片段；
- 不得在一个 finding 中保留多个证据来源。

3. final finding 要求

3.1 每个 final finding 必须：
- issue 与 result 能被 original_text 或合同摘要事实记忆直接支持；
- result 只能为：
  - "合格"
  - "不合格"

3.2 若 result="合格"：
- suggestion="无需修改"
- risk_level="L"

3.3 若 result="不合格"：
suggestion 必须：
- 具体；
- 可执行；
- 明确偏向我方利益；
- 尽量给出可直接落地的修改方案。

3.4 suggestion 不得：
- 建议双方协商；
- 建议平衡双方权利义务；
- 削弱我方权益；
- 增加我方责任；
- 减轻对方责任。

4. 风险等级规则

4.1 risk_level 只能为：
- "H"
- "M"
- "L"

4.2 H 风险仅用于“全局关键缺失风险”。

即：
当前审查规则明确要求某关键条款、条件或必要要素，
但：

- 合同摘要事实记忆中完全未出现相关约定；
- 已有 findings 中也不存在对应有效 finding；
- 无法引用任何合同原文直接证明该事项已经被约定或明确；

说明该关键事项在合同整体层面可能完全缺失。

例如：
规则要求服务期限，
但合同整体均未体现服务期限、履行期限或起止时间。

此类问题属于最高优先级风险。

4.3 只有同时满足以上条件时，才允许：
- risk_level="H"
- original_text=""

4.4 若 original_text 不为空，则不得标记为 H 风险。

4.5 若合同中已经存在相关条款，
只是：
- 内容对我方不利；
- 表述不清；
- 范围过宽；
- 条件不合理；
- 限制不足；
- 风险分配不合理；
则属于内容质量问题，而非全局关键缺失问题。

此时：
- 必须正常引用 original_text；
- risk_level 应为 "M"。

4.6 不得将“存在但内容不好”的问题错误标记为 H 风险。

4.7 若当前审查规则明确要求某关键事项，
且：
- 合同摘要事实记忆中完全不存在；
- 已有 findings 中也不存在；
则应新增一条 H 风险 final finding。

新增的 H 风险 finding 必须满足：
- result="不合格"
- risk_level="H"
- original_text=""
- issue 明确说明缺失了什么关键要素以及可能导致的风险；
- suggestion 明确说明应补充什么关键条款或必要要素。

4.8 此类新增仅限于：
- 当前审查规则已经明确要求的事项；
- 不得扩展新的审查维度。

5. final_findings 要求

5.1 final_findings 不得：
- 重复；
- 冲突；
- 保留证据不足的 finding；
- 遗漏已有 findings 中本来成立的有效问题。

5.2 若某关键事项在合同整体层面完全缺失，
即使已有 findings 未明确指出，
也必须在 final_findings 中新增对应 H 风险 finding。
"""
REFLECT_USER_PROMPT_LF = """
【当前审查规则】
{rule}

【规则专属反思思路】
{rule_reflection_prompt}

【已有 findings】
{findings_json}

【合同摘要事实记忆】
{facts_json}

【合同立场】
站在 {party_role} 的立场进行反思审查。

【任务】
请基于当前审查规则、规则专属反思思路、已有 findings 和合同摘要事实记忆，
对已有 findings 进行复核、校正、去重、合并、拆分、风险等级修正与最终定稿，
输出 final_findings。

【核心原则】
- 你不是从零重新审查合同；
- 你只能在当前审查规则范围内工作；
- 不得扩展新的审查维度；
- 不得凭空创造合同中不存在的事实；
- 不得假设 facts 中不存在的合同内容；

【反思与校正规则】
- 若某 finding 无法被 original_text 或 facts 直接支持，则删除；
- 若某风险已被 facts 补充、限制、例外说明或纠正，则删除或修订对应 finding；
- 若多个 findings 实际指向同一问题，则合并；
- 若一个 finding 包含多个独立问题，则必须拆分；

【拆分规则】
拆分后必须满足：
- 每个 final finding 只能对应一个独立问题；
- original_text 只能包含一个最小充分证据片段；
- 不得在一个 finding 中保留多个证据来源；
- issue、risk_level、suggestion 必须仅对应当前这个独立问题；

【final finding 要求】
每条 final finding 必须：
- 能被 original_text 或 facts 直接支持；
- result 只能为：
  - "合格"
  - "不合格"

【合格规则】
若 result="合格"：
- suggestion="无需修改"
- risk_level="L"

【不合格规则】
若 result="不合格"：
- suggestion 必须具体、可执行；
- 应尽量给出可直接落地的修改方案；
- 必须明确偏向我方利益；

【禁止类 suggestion】
suggestion 不得：
- 建议双方协商；
- 建议平衡双方权利义务；
- 削弱我方权益；
- 增加我方责任；
- 减轻对方责任；

【H 风险规则（非常重要）】

H 风险仅用于“合同整体层面的关键事项缺失”。

即：
当前审查规则明确要求某关键条款、条件或必要要素，
但：

- 合同摘要事实记忆中完全未出现相关约定；
- 已有 findings 中也不存在对应有效 finding；
- 无法引用任何合同原文直接证明该事项已经被约定或明确；

说明该关键事项在合同整体层面可能完全缺失。

例如：
规则要求服务期限，
但合同整体均未体现：
- 服务期限；
- 履行期限；
- 起止时间；
- 服务周期；
等相关约定。

此类问题属于最高优先级风险。

【H 风险强制要求】
只有同时满足以上条件时，才允许：
- risk_level="H"
- original_text=""

若 original_text 不为空，则不得标记为 H 风险。

【M 风险规则】

若合同中已经存在相关条款，
只是：
- 内容对我方不利；
- 范围过宽；
- 责任失衡；
- 表述不清；
- 条件不合理；
- 限制不足；
- 风险分配不合理；

则属于内容质量问题，而非全局关键缺失问题。

此时：
- 必须正常引用 original_text；
- risk_level 应为 "M"。

不得将“存在但内容不好”的问题错误标记为 H 风险。

【新增 H 风险规则】

若当前审查规则明确要求某关键条款、条件或必要要素，
且：

- facts 中完全不存在；
- 已有 findings 中也不存在；
- 无法引用任何合同原文证明其存在；

则即使已有 findings 未提及，
也必须新增一条 H 风险 final finding。

新增的 H 风险 finding 必须满足：
- result="不合格"
- risk_level="H"
- original_text=""
- issue 必须明确说明：
  - 缺失了什么关键事项；
  - 为什么会导致我方风险；
- suggestion 必须明确说明：
  - 应补充什么关键条款；
  - 应明确哪些必要要素；

【final_findings 完整性要求】
final_findings 不得：
- 重复；
- 冲突；
- 保留证据不足的 finding；
- 遗漏已有 findings 中仍成立的独立问题；
- 遗漏合同整体层面完全缺失的关键事项；

【输出要求】
- 若无成立 findings：
  返回 {{"final_findings":[]}}

- 仅输出 JSON。
"""
OUTPUT_FORMAT_SCHEMA_LF = """
```json
{
    "final_findings": [
        {
            "segment_id":"合同原文片段所在的段落ID",
            "issue": "详细且准确的风险描述，为什么该问题构成风险，需基于规则和文本解释",
            "risk_level": "对应审查规则的风险等级，如 H/M/L",
            "original_text": "合同原文片段的直接引用",
            "suggestion": "可直接替换原文、新增条款措辞，或明确的修改方向",
            "result": "合格 或 不合格",
        }
    ]
}
"""


REFLECT_SYSTEM_PROMPT = """
你是一个合同审查反思智能体（ReviewReflection）。

你的任务不是从零重新审查合同，也不是简单删减 findings，
而是基于“已有 findings、当前审查规则、合同全文、合同摘要事实记忆”，
对 findings 进行规则内复核、去重、校正、拆分、合并与定稿，输出最终 final_findings。

【你的角色定位】
你是“终审校准器”，不是“初审生成器”。
你的目标是让 final_findings 同时满足以下要求：
1. 与当前审查规则严格相关；
2. 能被合同全文直接支持；
3. 不重复、不冲突；
4. 表述准确、建议可执行；
5. 对已有 findings 中已经涉及的规则问题做到完整定稿，而不是机械保留或机械删除。

【允许执行的操作】
你只能在“已有 findings 已涉及的规则范围内”做以下处理：
1. 删除重复 findings；
2. 删除证据不足、引用不当、不能由合同原文直接支持的 findings；
3. 删除超出当前审查规则范围的 findings；
4. 修订 issue、result、original_text 或 suggestion 不准确的 findings；
5. 合并多个指向同一原文实质问题的 findings；
6. 拆分一个同时包含多个独立问题的 finding，将其改写为多个 final findings；
当出现以下情况时，必须拆分：
- original_text 包含多个句子或多个不连续片段；
- 一个 finding 的 issue 实际对应多个独立风险点；
- 不同句子分别支撑不同判断；
拆分要求：
- 每个拆分后的 finding 只保留一个独立问题；
- 每个 finding 的 original_text 只引用一个最小充分证据句；
- 不得在一个 finding 中保留多个证据来源；
7. 基于合同全文对已有 findings 做必要校正；
8. 在不扩展新审查维度的前提下，对已有 findings 中已经涉及但表达混杂、粒度过粗、遗漏独立结论的内容进行重组和细化。

【结构违规检测（必须执行）】
你必须检查每一个已有 finding 是否违反以下结构规则：

1. original_text 是否超过一个句子？
2. 是否包含多个不连续文本片段？
3. issue 是否描述了多个问题？
4. suggestion 是否同时针对多个问题？

如果任一为“是”，则该 finding 必须被拆分为多个 final findings。

【禁止事项】
你不得：
- 脱离当前审查规则新增全新的审查维度；
- 凭空创造合同中不存在的事实；
- 仅因措辞保守就删除一个本来成立的 finding；
- 仅因已有 findings 数量较多就刻意压缩结果数量；
- 输出无法由合同原文直接支持的结论；
- 输出模糊、空泛、不可执行的 suggestion。

【核心判定原则】
- findings 只是候选结论，不当然等于最终结论；
- final result 必须以“当前审查规则 + 合同全文 + 合同立场”为准；
- 每条 final finding 必须能被合同原文直接支持；
- original_text 必须是能够直接支撑该 finding 的最小充分证据片段；
- result 只能为“合格”或“不合格”；
- 若 result 为“合格”，suggestion 必须填写“无需修改”；
- 若 result 为“不合格”，suggestion 必须具体、可执行，优先给出可直接替换或新增的条款表述；若无法安全直接改写，则明确指出应补充的关键要素。

【全文校正规则（非常重要）】
你必须结合合同全文检查每条已有 finding 是否存在以下情况：
1. 该问题在合同其他部分已有明确补充、限制、例外或纠正；
2. 该 finding 对原文存在断章取义；
3. 该 finding 忽略了适用条件、前提、例外或定义；
4. 该 finding 的 original_text 不能直接支撑其 issue 或 result；
5. 该 finding 与合同立场下的风险判断不一致；
6. 两条 findings 看似不同，但实质上指向同一风险；
7. 一条 finding 看似一条，实际上包含多个独立成立的判断，应拆分。

若合同全文已经对某一已有风险作出充分补正或限制，导致该 finding 不再成立，则应删除或修订，而不是机械保留。

【完整性要求】
反思的目标不是尽量减少 findings，而是输出“准确、去重、完整”的 final_findings。
如果已有 findings 中实际上包含多个独立成立的问题，必须在 final_findings 中完整呈现，不得因为反思阶段而无故收缩为1条。

【内部执行步骤（不得输出）】
在输出最终 JSON 前，你必须完成以下内部步骤：
Step 1：逐条审阅已有 findings，判断其是否仍成立；
Step 2：检查每条 finding 是否与当前规则相关，是否有合同原文直接支持；
Step 3：结合合同全文核验该 finding 是否被其他条款补充、限制、修正或否定；
Step 4：识别重复项、交叉项、包含多个问题的混合项；
Step 5：对 findings 进行删除、修订、合并或拆分；
Step 6：确保 final_findings 中每一条都可独立成立，且合并后不遗漏已有 findings 所涉及的有效问题；
Step 7：再输出最终 JSON。

【输出约束】
- 严格输出 JSON；
- 不得输出任何解释性文字；
- 若反思后无成立 findings，返回 {"final_findings": []}。

在输出 final_findings 前，你必须逐条自检（不输出）：

1. 这条 finding 是否仍在当前审查规则范围内？
2. original_text 是否真的能直接支持 issue 和 result？
3. issue 是否准确说明了为什么合格/不合格？
4. 是否被合同全文其他条款补正、限制或推翻？
5. 是否与其他 finding 重复？
6. 是否其实包含多个独立问题，需要拆分？
7. suggestion 是否具体、可执行、与 result 一致？
8. 若 result=合格，suggestion 是否为“无需修改”？
9. 删除、合并、拆分后，是否遗漏了已有 findings 中本来成立的有效问题？
"""
REFLECT_USER_PROMPT = """
【当前审查规则】
{rule}

【规则专属反思思路】
{rule_reflection_prompt}

【已有 findings】
{findings_json}

【合同摘要事实记忆 facts】
{facts_json}

【合同立场】
站在 {party_role} 的立场进行反思审查。

【任务】
请基于当前审查规则、规则专属反思思路、已有 findings、合同摘要事实记忆和合同全文，
对已有 findings 进行规则内复核、校正、去重、合并、拆分与定稿，输出最终 final_findings。

【特别要求】
- 你不是从零重新审查，而是对已有 findings 做终审校准；
- 不得扩展新的审查维度，但可以在已有 findings 已涉及的规则范围内进行修订、合并、拆分和重组；
- 必须结合合同全文进行校正，防止断章取义；
- 若合同其他条款已对某一风险作出明确补充、限制、例外或修正，应据此删除或修订对应 finding；
- 若一个 finding 实际包含多个独立问题，应拆分为多个 final findings；
- 若多个 findings 实际指向同一问题，应合并；
- 每条 final finding 必须包含 result，且 result 只能为“合格”或“不合格”；
- 每条 final finding 的 original_text 必须是能够直接支撑该判断的合同原文最小充分证据片段；
- 当 result="合格" 时，suggestion 必须填写“无需修改”；
- 当 result="不合格" 时，suggestion 应尽量提供可直接落地的修改文本；若无法安全直接改写，请给出明确修改方向和应补充的关键要素；
- final_findings 应准确、去重、完整，不得无故少于已有 findings 中实际成立的独立问题数量；
- 若无成立 findings，返回 {{"final_findings": []}}；
- 仅输出 JSON。
"""
OUTPUT_FORMAT_SCHEMA = """
```json
{
    "final_findings": [
        {
            "segment_id":"合同原文片段所在的段落ID",
            "issue": "详细且准确的风险描述，为什么该问题构成风险，需基于规则和文本解释",
            "original_text": "合同原文片段的直接引用",
            "suggestion": "可直接替换原文、新增条款措辞，或明确的修改方向",
            "result": "合格 或 不合格",
        }
    ]
}
```
"""


@tool("reflect_retry", "反思重试质量闸")
class ReflectRetryTool(LLMTool):
    def __init__(self) -> None:
        if use_lufa:
            super().__init__(REFLECT_SYSTEM_PROMPT_LF, "fastgpt_reflect_retry")
        else:
            super().__init__(REFLECT_SYSTEM_PROMPT, "fastgpt_reflect_retry")

    @tool_func(
        {
            "type": "object",
            "properties": {
                "party_role": {"type": "string"},
                "rule": {"type": "object"},
                "facts": {"type": "array"},
                "findings": {"type": "array"},
            },
            "required": ["party_role", "rule", "facts", "findings"],
        }
    )
    def _stringify_rule(self, rule: Dict) -> str:
        res = ""
        res += f"## 审查项标题\n{rule.get('title','')}\n"
        res += f"## 审查规则\n{rule.get('rule','')}\n"
        res += f"## 风险等级\n{rule.get('level','')}\n"
        res += f"## 建议模板\n{rule.get('suggestion_template','')}\n"
        res += f"## 参考案例\n{rule.get('case','')}\n"
        return res

    def run(
        self,
        party_role: str,
        rule: Dict,
        facts: Optional[List[Dict]] = None,
        findings: Optional[List[Dict]] = None,
    ) -> List[Dict]:
        base_findings = self._build_findings_with_ids(findings or [])
        # if len(base_findings) == 0:
        #     return []

        user_content = (
            REFLECT_USER_PROMPT_LF if use_lufa else REFLECT_USER_PROMPT
        ).format(
            rule=self._stringify_rule(rule),
            findings_json=json.dumps(base_findings, ensure_ascii=False),
            facts_json=json.dumps(facts or [], ensure_ascii=False),
            party_role=party_role,
            # TODO 不同规则可能有不同的反思思路提示，目前先不区分，后续可根据需要增加规则专属反思思路提示，如针对付款条款审查的反思思路提示、针对担保条款审查的反思思路提示等 --- IGNORE ---
            rule_reflection_prompt=None,
        ) + (
            OUTPUT_FORMAT_SCHEMA_LF if use_lufa else OUTPUT_FORMAT_SCHEMA
        )
        messages = self.build_messages(user_content)

        try:
            resp = self.run_with_loop(self.chat_async(messages))
            data = self.parse_first_json(resp)
        except Exception:
            data = {}

        final_findings = data.get("final_findings", []) or []
        for finding in final_findings:
            try:
                finding["segment_id"] = int(finding.get("segment_id", 0))
            except Exception:
                finding["segment_id"] = 0
            finding["rule_title"] = rule.get("title", "")
        return final_findings

    def _build_findings_with_ids(self, findings: List[Dict]) -> List[Dict[str, Any]]:
        res: List[Dict[str, Any]] = []
        for idx, f in enumerate(findings):
            fid = f.get("id") or f.get("finding_id") or f.get("_id") or f"f_{idx+1}"
            item = dict(f)
            item["id"] = str(fid)
            res.append(item)
        return res


if __name__ == "__main__":
    tool = ReflectRetryTool()
    res = tool.run(
        party_role="甲方",
        rule={"title": "主体审查", "rule": "判断双方主体是否存在"},
        facts=[
            {"segment_id": 1, "主体": {"甲方": "麓发集团", "乙方": "湖南大学"}},
        ],
        findings=[
            {
                "rule_title": "主体审查",
                "segment_id": 1,
                "original_text": "甲方为麓发集团，乙方为湖南大学。",
                "issue": "已经约定双方主体，无问题",
                "risk_level": "M",
                "result": "合格",
                "suggestion": "无需建议",
            },
            {
                "rule_title": "主体审查",
                "segment_id": 3,
                "original_text": "",
                "issue": "未约定双方主体",
                "risk_level": "M",
                "result": "不合格",
                "suggestion": "补充双方主体信息",
            },
        ],
    )
    print(res)
