"""
本地大模型脱敏客户端

支持两阶段配置：
- 阶段一（测试）：使用外部API测试候选模型
- 阶段二（生产）：使用本地部署模型（Qwen3-8B/30B）

根据 wlast.md 文档第3.2节和8.3节设计
"""

import re
import logging
from typing import Optional, Dict, Any, List
from openai import AsyncOpenAI

from foundation.infrastructure.config.config import config_handler

logger = logging.getLogger(__name__)


class DesensitizeModelClient:
    """本地大模型脱敏客户端（仅限内网 Qwen3-8B / Qwen3-30B）

    ⚠️ 安全约束：
      - 生产阶段 SERVER_URL 必须为内网地址（192.168.x.x / 10.x.x.x / 172.x.x.x）
      - 禁止配置任何外部公网大模型 API 地址
      - 脱敏处理必须在调用外部审查 API 之前完成

    阶段配置：
      - DEPLOY_PHASE=test: 使用外部API测试（仅测试数据集）
      - DEPLOY_PHASE=local: 使用本地部署模型（生产环境）
    """

    # 脱敏 Prompt 模板（根据 wlast.md 文档附录B）
    DESENSITIZE_PROMPT_TEMPLATE = """你是专业的施工方案数据脱敏专家，请对以下内容进行{level}级别脱敏：

【脱敏规则】
1. PII（个人身份信息）：
   - 姓名 → [项目经理A]、[安全员B] 等角色占位符
   - 手机号 → [手机号脱敏]
   - 身份证号 → [证件号脱敏]
   - 证书编号 → [证件号脱敏]
   - 邮箱 → [邮箱脱敏]

2. 地理坐标（工程相对化）：
   - 绝对桩号（如D1K86+279.91）→ 转换为相对桩号（K0+000为起点）
   - 桥隧名称（如映雪特大桥）→ [1号特大桥] 等序列化命名
   - 高程数据（海拔1850.5m）→ [中山区] 等地形描述
   - 行政地名（四川省成都市）→ [某地区]

3. 企业标识：
   - 公司名称 → [建设单位]、[总承包单位]、[监理单位] 等标准角色
   - 项目名称 → [某工程项目]
   - 合同编号 → [合同编号脱敏]

4. 财务数据：
   - 金额（￥1,250,000.00）→ [金额脱敏]元
   - 设备租赁单价 → [单价脱敏]/单位时间
   - 材料单价 → [单价脱敏]/单位
   - 工程量数值 → [数量脱敏]+单位

【要求】
- 保持原文结构、段落格式和工程逻辑
- 保留技术参数（如混凝土标号、钢筋规格）
- 保留工艺工序描述
- 保留安全规范条款
- 保留组织架构关系（仅替换名称为占位符）

请直接返回脱敏后的完整文本："""

    def __init__(self):
        """初始化模型客户端"""
        self._load_config()
        self.client = AsyncOpenAI(
            base_url=self.server_url,
            api_key=self.api_key
        )

        logger.info(f"[DesensitizeModelClient] 初始化完成: "
                   f"phase={self.deploy_phase}, model={self.model}, "
                   f"scheme={self.scheme if self.deploy_phase == 'local' else 'N/A'}")

    def _load_config(self):
        """加载配置"""
        base_cfg = config_handler.get_section("desensitize")
        self.deploy_phase = base_cfg.get("DEPLOY_PHASE", "test")

        if self.deploy_phase == "test":
            # 阶段一：外部 API 测试验证
            self._load_test_config()
        else:
            # 阶段二：本地部署生产模式
            self._load_local_config()

    def _load_test_config(self):
        """加载测试阶段配置"""
        test_cfg = config_handler.get_section("desensitize_model_test")
        test_model = test_cfg.get("TEST_MODEL", "gemma3_12b")
        cfg = config_handler.get_section(f"desensitize_model_test_{test_model}")

        self.server_url = cfg.get("SERVER_URL", "")
        self.api_key = cfg.get("API_KEY", "dummy")
        self.model = cfg.get("MODEL_NAME", "")
        self.max_tokens = int(cfg.get("MAX_TOKENS", "4096"))
        self.temperature = float(cfg.get("TEMPERATURE", "0.1"))

        # 测试阶段特有配置
        self.selective = False
        self.confidence_threshold = 0.0
        self.scheme = "test"

        # 打印警告
        logger.warning(
            f"[脱敏] 当前为测试阶段（DEPLOY_PHASE=test），"
            f"使用外部模型 {self.model}，请勿传入真实生产文档"
        )

        # 检查 API KEY 是否为环境变量引用
        if self.api_key.startswith("${") and self.api_key.endswith("}"):
            env_var = self.api_key[2:-1]
            import os
            self.api_key = os.environ.get(env_var, "")

    def _load_local_config(self):
        """加载本地生产配置"""
        base_cfg = config_handler.get_section("desensitize")
        self.scheme = base_cfg.get("DESENSITIZE_SCHEME", "scheme_a")

        cfg = config_handler.get_section(f"desensitize_model_{self.scheme}")
        self.server_url = cfg.get("SERVER_URL", "")
        self.api_key = cfg.get("API_KEY", "dummy")
        self.model = cfg.get("MODEL_NAME", "")
        self.max_tokens = int(cfg.get("MAX_TOKENS", "2048"))
        self.temperature = float(cfg.get("TEMPERATURE", "0.1"))

        # 本地特有配置
        self.selective = cfg.get("SELECTIVE_INFERENCE", "false").lower() == "true"
        self.confidence_threshold = float(cfg.get("CONFIDENCE_THRESHOLD", "0.7"))
        self.max_input_length = int(cfg.get("MAX_INPUT_LENGTH", "8192"))

        # 生产阶段：强制校验必须为内网地址
        if not self._is_internal_ip(self.server_url):
            raise ValueError(
                f"[脱敏] 生产阶段只允许内网地址，拒绝外网地址: {self.server_url}\n"
                f"请将 DEPLOY_PHASE 改为 test，或将 SERVER_URL 改为内网地址"
            )

    def _is_internal_ip(self, url: str) -> bool:
        """检查是否为内网地址"""
        internal_prefixes = [
            "http://192.168.",
            "http://10.",
            "http://172.16.",
            "http://172.17.",
            "http://172.18.",
            "http://172.19.",
            "http://172.20.",
            "http://172.21.",
            "http://172.22.",
            "http://172.23.",
            "http://172.24.",
            "http://172.25.",
            "http://172.26.",
            "http://172.27.",
            "http://172.28.",
            "http://172.29.",
            "http://172.30.",
            "http://172.31.",
            "https://192.168.",
            "https://10.",
        ]
        return any(url.startswith(p) for p in internal_prefixes)

    async def desensitize_text(self, text: str, level: str = "standard") -> str:
        """使用大模型进行智能脱敏

        Args:
            text: 待脱敏文本
            level: 脱敏级别 (minimal/standard/strict)

        Returns:
            脱敏后的文本
        """
        # 截断过长的输入
        if len(text) > self.max_input_length:
            logger.warning(f"[DesensitizeModelClient] 输入文本过长({len(text)}), 截断至{self.max_input_length}")
            text = text[:self.max_input_length]

        system_prompt = self.DESENSITIZE_PROMPT_TEMPLATE.format(level=level)

        try:
            response = await self.client.chat.completions.create(
                model=self.model,
                messages=[
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": text}
                ],
                temperature=self.temperature,
                max_tokens=self.max_tokens
            )

            result = response.choices[0].message.content

            # 清理可能的代码块标记
            result = self._clean_code_blocks(result)

            logger.debug(f"[DesensitizeModelClient] 脱敏完成: {len(text)} -> {len(result)} chars")
            return result

        except Exception as e:
            logger.error(f"[DesensitizeModelClient] 模型调用失败: {e}")
            raise

    async def desensitize_chunks(self, chunks: List[str], level: str = "standard") -> List[str]:
        """批量脱敏多个文本块

        Args:
            chunks: 文本块列表
            level: 脱敏级别

        Returns:
            脱敏后的文本块列表
        """
        results = []
        for i, chunk in enumerate(chunks):
            try:
                desensitized = await self.desensitize_text(chunk, level)
                results.append(desensitized)
                logger.debug(f"[DesensitizeModelClient] 块{i+1}/{len(chunks)}脱敏完成")
            except Exception as e:
                logger.error(f"[DesensitizeModelClient] 块{i+1}脱敏失败: {e}")
                # 失败时保留原文（或可根据策略阻断）
                results.append(chunk)
        return results

    def _clean_code_blocks(self, text: str) -> str:
        """清理代码块标记"""
        # 移除 markdown 代码块标记
        text = re.sub(r'^```\w*\n?', '', text)
        text = re.sub(r'\n?```$', '', text)
        return text.strip()

    async def check_confidence(self, text: str) -> Dict[str, Any]:
        """检查文本脱敏置信度（用于方案A的选择性推理）

        返回置信度分数和可能需要人工检查的片段
        """
        # 简单启发式检查
        risk_patterns = [
            (r'\b1[3-9]\d{9}\b', 'phone'),
            (r'\b\d{17}[\dXx]\b', 'id_card'),
            (r'[\u4e00-\u9fa5]{2,4}(?:集团|有限公司|局)', 'company'),
        ]

        risks = []
        for pattern, risk_type in risk_patterns:
            if re.search(pattern, text):
                risks.append(risk_type)

        confidence = 1.0 - (len(risks) * 0.3)  # 简单线性计算
        confidence = max(0.0, min(1.0, confidence))

        return {
            "confidence": confidence,
            "risks": risks,
            "needs_review": confidence < self.confidence_threshold
        }
