# -*- coding: utf-8 -*-
"""
safety_filter.py — AI 输出安全过滤器

三层过滤机制：
  1. 危险命令黑名单：正则匹配命中后删除整行
  2. 无关性检测：检查步骤是否与问题相关
  3. 白名单验证：确认为允许的操作类型
  4. 敏感信息过滤：自动过滤 IP、密码等敏感信息

用途：确保发给现场同事的排查步骤绝对安全，仅包含只读操作。
"""

import re

# ============================================================
# 第一层：危险命令黑名单（正则匹配）
# ============================================================

DANGEROUS_PATTERNS = [
    # === 删除操作 ===
    (r'\brm\s+(-rf?|--recursive)', '危险：递归删除文件'),
    (r'\brmdir\b', '危险：删除目录'),
    (r'\brm\s+\w', '危险：删除文件'),
    (r'\bdocker\s+rm', '危险：删除容器'),
    (r'\bdocker\s+system\s+prune', '危险：清理 Docker 系统'),
    (r'\bdocker\s+volume\s+rm', '危险：删除 Docker 卷'),
    (r'\bDROP\s+(TABLE|DATABASE|SCHEMA)', '危险：删除数据库表/库'),
    (r'\bDELETE\s+FROM', '危险：删除数据库记录'),
    (r'\bTRUNCATE\s+(TABLE\s+)?', '危险：清空数据库表'),
    (r'\bdel\b.*\b(\.conf|\.cfg|\.ini|\.yml|\.yaml|\.json|\.xml)', '危险：删除配置文件'),

    # === 重启/停止操作 ===
    (r'\breboot\b', '危险：重启系统'),
    (r'\bshutdown\b', '危险：关闭系统'),
    (r'\binit\s+[0-6]\b', '危险：改变系统运行级别'),
    (r'\bdocker\s+(restart|stop)', '危险：重启/停止容器'),
    (r'\bdocker-compose\s+down', '危险：停止 docker-compose 服务'),
    (r'\bdocker-compose\s+restart', '危险：重启 docker-compose 服务'),
    (r'\bsystemctl\s+(restart|stop)', '危险：重启/停止系统服务'),
    (r'\bservice\s+\w+\s+(restart|stop)', '危险：重启/停止服务'),
    (r'\bkill\b', '危险：终止进程'),
    (r'\bpkill\b', '危险：批量终止进程'),
    (r'\bkillall\b', '危险：批量终止进程'),
    (r'\bsupervisorctl\s+(restart|stop)', '危险：重启/停止 supervisor 进程'),

    # === 修改操作 ===
    (r'\bchmod\s+777\b', '危险：开放所有权限'),
    (r'\bchmod\s+[0-7]*[2367][0-7]*\s+/etc', '危险：修改系统目录权限'),
    (r'\bchown\b', '危险：修改文件所有者'),
    (r'\b>\/etc\/', '危险：覆盖系统配置文件'),
    (r'\bsed\s+.*-i\b.*\/etc\/', '危险：直接修改系统配置文件'),
    (r'\btee\s+/etc/', '危险：写入系统配置文件'),
    (r'\bmv\s+.*\/etc\/', '危险：移动文件到系统目录'),
    (r'\biptables\s+-[AID]', '危险：修改防火墙规则'),
    (r'\bfirewall-cmd\s+--(add|remove|--permanent)', '危险：修改防火墙规则'),
    (r'\busermod\b', '危险：修改用户'),
    (r'\buseradd\b', '危险：添加用户'),
    (r'\buserdel\b', '危险：删除用户'),
    (r'\bpasswd\b', '危险：修改密码'),
    (r'\bmount\b', '危险：挂载文件系统'),

    # === 清空操作 ===
    (r'\bFLUSHALL\b', '危险：清空 Redis 所有数据'),
    (r'\bFLUSHDB\b', '危险：清空 Redis 数据库'),
    (r'\b>\/dev\/\w+', '危险：覆盖设备文件'),
    (r':\s*>\s*\S+\.(conf|cfg|ini|yml|yaml|json|xml)', '危险：覆盖配置文件'),

    # === 系统级危险操作 ===
    (r'\bfdisk\b', '危险：磁盘分区操作'),
    (r'\bmkfs\.', '危险：格式化文件系统'),
    (r'\bdd\s+if=', '危险：磁盘写入操作'),
    (r'\bdocker\s+exec.*(-it|bash|sh)', '危险：进入容器交互模式（需研发组执行）'),
    (r'\bssh\s+root@', '危险：以 root 身份 SSH'),
]

# ============================================================
# 第二层：允许操作白名单
# ============================================================

ALLOWED_PATTERNS = [
    # 查看状态
    r'\bdocker\s+ps\b',
    r'\bdocker\s+inspect\b',
    r'\bsystemctl\s+status\b',
    r'\bsystemctl\s+is-active\b',
    r'\bsystemctl\s+is-enabled\b',
    r'\bss\s+-tln',
    r'\bnetstat\s+-tln',
    r'\bip\s+addr\b',
    r'\bfirewall-cmd\s+--list',
    r'\biptables\s+-[nL]\b',
    r'\bgetenforce\b',

    # 查看日志
    r'\bdocker\s+logs\b',
    r'\btail\b',
    r'\bhead\b',
    r'\bless\b',
    r'\bjournalctl\b',
    r'\bdmesg\b',

    # 连通性测试
    r'\bnc\s+-z\b',
    r'\bcurl\b',
    r'\bwget\b',
    r'\bping\b',
    r'\btraceroute\b',
    r'\btelnet\b',
    r'\b/dev/tcp/',

    # 系统信息
    r'\bdf\s+-h\b',
    r'\bdu\s+-sh\b',
    r'\bfree\s+-h\b',
    r'\bdate\b',
    r'\btimedatectl\b',
    r'\bps\s+(aux|ef|-ef)\b',
    r'\btop\b',
    r'\bhtop\b',
    r'\buname\b',
    r'\bhostname\b',
    r'\blscpu\b',
    r'\blsblk\b',
    r'\blsusb\b',
    r'\bcrontab\s+-l\b',

    # 配置查看
    r'\bcat\b',
    r'\bgrep\b',
    r'\bfind\b',
    r'\bls\b',
    r'\bwhich\b',
    r'\bwhereis\b',
    r'\btype\b',
    r'\bmd5sum\b',
    r'\bsha256sum\b',
    r'\bdiff\b',
    r'\bwc\b',

    # 无风险的检查
    r'\bdocker\s+exec.*emqx\s+ctl\s+status',
    r'\bdocker\s+exec.*cat\b',
    r'\bdocker\s+exec.*grep\b',
    r'\bdocker\s+exec.*ls\b',
    r'\bdocker\s+exec.*date\b',
    r'\bdocker\s+exec.*curl\b',
    r'\bdocker\s+exec.*ss\b',

    # 其他安全操作
    r'\becho\b',
    r'\bprintenv\b',
    r'\bexport\b',
    r'\bsource\b',
    r'\balias\b',
]


def check_dangerous(text):
    """
    检查文本是否包含危险命令。
    返回：(has_danger, warnings)
    """
    warnings = []
    for pattern, description in DANGEROUS_PATTERNS:
        matches = re.finditer(pattern, text, re.IGNORECASE)
        for match in matches:
            # 提取上下文（前后 30 个字符）
            start = max(0, match.start() - 15)
            end = min(len(text), match.end() + 15)
            context = text[start:end].replace('\n', ' ')
            warnings.append({
                'pattern': pattern,
                'description': description,
                'matched': match.group(),
                'context': context,
                'line': text[:match.end()].count('\n') + 1,
            })

    return len(warnings) > 0, warnings


def is_allowed_operation(line):
    """
    检查某行是否为允许的只读操作。
    返回 True 表示该行至少匹配一个白名单模式。
    """
    line = line.strip()
    # 空行和无命令的纯文本行直接通过
    if not line:
        return True
    if not re.search(r'[a-zA-Z]', line):  # 没有英文（可能纯中文描述）
        return True
    if re.match(r'^(Step|步骤|\d+[\.\、])', line):  # 步骤标题
        return True
    if re.match(r'^(#|##|###|>|-)', line):  # Markdown 标题/列表行
        return True

    # 检查是否匹配任一白名单
    for pattern in ALLOWED_PATTERNS:
        if re.search(pattern, line, re.IGNORECASE):
            return True

    return False


def check_relevance(step_text, query_keywords):
    """
    检查排查步骤是否与用户问题相关。
    返回：相关性得分 0.0~1.0
    """
    if not query_keywords:
        return 1.0  # 无关键词时默认通过

    step_lower = step_text.lower()
    hit_count = 0
    for kw in query_keywords:
        if kw.lower() in step_lower:
            hit_count += 1

    return hit_count / len(query_keywords) if query_keywords else 1.0


def extract_query_keywords(query):
    """从用户问题中提取关键词"""
    # 去除标点，分词
    query_clean = re.sub(r'[^一-鿿\w\s]', ' ', query)
    words = query_clean.split()
    # 过滤短词和停用词
    stopwords = {'的', '了', '是', '在', '有', '我', '不', '和', '与', '或',
                 'the', 'a', 'an', 'is', 'are', 'was', 'were', 'to', 'of', 'in',
                 'and', 'or', 'not', '这个', '那个', '怎么', '为什么', '哪里',
                 '什么', '可以', '应该', '需要', '请', '帮', '一下', '吗', '呢'}
    keywords = [w for w in words if len(w) > 1 and w.lower() not in stopwords]
    return keywords[:10]  # 最多 10 个关键词


def filter_output(response_text, original_query):
    """
    对 AI 生成的排查输出执行完整安全检查。

    参数：
      response_text: AI 返回的完整文本
      original_query: 用户原始问题描述

    返回：
      {
          "safe": True/False,           # 整体是否安全
          "filtered_text": "...",       # 过滤后的安全文本
          "warnings": [...],            # 警告列表
          "removed_lines": [...],       # 被移除的行
          "relevance_score": 0.0~1.0,  # 整体相关性得分
      }
    """
    warnings = []
    removed_lines = []
    relevance_issues = []

    # 提取用户问题关键词
    query_keywords = extract_query_keywords(original_query)

    # 逐行检查
    lines = response_text.split('\n')
    safe_lines = []
    irrelevant_lines = []

    for i, line in enumerate(lines):
        line_stripped = line.strip()

        # 第一层：危险命令检查
        has_danger, danger_warnings = check_dangerous(line)
        if has_danger:
            removed_lines.append({
                'line_number': i + 1,
                'content': line_stripped,
                'reason': '包含危险命令',
                'details': danger_warnings,
            })
            # 替换为安全提示
            safe_lines.append(f"⚠️ [原输出包含危险操作，已自动过滤] 如需执行相关操作，请联系研发组。")
            continue

        # 第二层：相关性检查（仅对含命令的行检查）
        if re.search(r'[a-zA-Z]', line) and not re.match(r'^(#|##|###)', line):
            relevance = check_relevance(line, query_keywords)
            if relevance < 0.3 and query_keywords:
                irrelevant_lines.append({
                    'line_number': i + 1,
                    'content': line_stripped,
                    'relevance_score': relevance,
                })

        safe_lines.append(line)

    # 如果无关行过多（超过 30% 的总命令行），标记为整体可疑
    command_lines_count = sum(1 for l in lines if re.search(r'[a-zA-Z]', l))
    if command_lines_count > 0:
        irrelevance_ratio = len(irrelevant_lines) / command_lines_count
    else:
        irrelevance_ratio = 0

    # 组装结果
    result = {
        'safe': len(removed_lines) == 0,
        'filtered_text': '\n'.join(safe_lines),
        'warnings': warnings,
        'removed_lines': removed_lines,
        'irrelevant_lines_count': len(irrelevant_lines),
        'irrelevance_ratio': irrelevance_ratio,
    }

    if irrelevance_ratio > 0.5:
        result['note'] = '部分排查步骤可能与问题关联度较低，建议结合实际情况判断。'

    return result


# ============================================================
# 第四层：敏感信息过滤（新增功能）
# ============================================================

# 敏感信息正则规则
SENSITIVE_PATTERNS = [
    # IP 地址（保留内网 IP 的前缀，如 192.168.x.x 只显示 192.168.***.***）
    (r'\b(\d{1,3}\.\d{1,3})\.(\d{1,3}\.\d{1,3})\b', r'\1.***.***'),

    # 密码相关（password/passwd/pwd = xxx 或 : xxx）
    (r'(password|passwd|pwd)\s*[=:]\s*\S+', r'\1 = ***'),

    # 数据库连接串（mysql://user:pass@host → mysql://***@host）
    (r'(mysql|redis|mongodb|postgresql)://[^@\s]+@', r'\1://***@'),

    # 手机号（1xxxxxxxxxx → 1**********）
    (r'\b(1[3-9]\d{9})\b', r'1**********'),

    # 邮箱（xxx@xxx.xxx → ***@***.***）
    (r'\b([\w.+-]+@[\w.-]+\.\w+)\b', r'***@***.***'),

    # API Key / Token（显示为 ***）
    (r'(api[_-]?key|token|secret|auth)\s*[=:]\s*[\w-]{10,}', r'\1 = ***'),

    # SSH 私钥路径
    (r'(/home/[^/]+/\.ssh/[^\s]+)', r'/home/***/.ssh/***'),

    # 数据库名（CREATE DATABASE xxx → CREATE DATABASE ***）
    (r'(CREATE\s+DATABASE\s+)(\w+)', r'\1***'),

    # 用户名（user/userame = xxx → user = ***）
    (r'(user|username)\s*[=:]\s*[a-zA-Z0-9_-]{3,}', r'\1 = ***'),
]


def filter_sensitive_info(text):
    """
    过滤文本中的敏感信息。

    参数：
      text: 待过滤的文本

    返回：
      过滤后的安全文本
    """
    for pattern, replacement in SENSITIVE_PATTERNS:
        text = re.sub(pattern, replacement, text, flags=re.IGNORECASE)

    return text


def filter_output_with_sensitive(response_text, original_query):
    """
    对 AI 生成的排查输出执行完整安全检查（包含敏感信息过滤）。

    参数：
      response_text: AI 返回的完整文本
      original_query: 用户原始问题描述

    返回：
      {
          "safe": True/False,           # 整体是否安全
          "filtered_text": "...",       # 过滤后的安全文本（含敏感信息过滤）
          "warnings": [...],            # 警告列表
          "removed_lines": [...],       # 被移除的行
          "relevance_score": 0.0~1.0,  # 整体相关性得分
      }
    """
    # 1. 执行原有的危险命令过滤
    result = filter_output(response_text, original_query)

    # 2. 新增：敏感信息过滤
    result['filtered_text'] = filter_sensitive_info(result['filtered_text'])

    return result
