#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：security_report_service.py
模块描述：安全测试报告生成服务，生成 7 章节 Markdown 格式报告

作者：czj
创建日期：2026-07-21
最后修改：2026-07-21
"""

import os
import logging
from datetime import datetime
from typing import List, Dict, Any, Optional
from pathlib import Path

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, desc

from app.models.vulnerability_result import VulnerabilityResult
from app.models.execution import Execution
from app.models.security_config import SecurityConfig
from app.config import settings
from app.services.knowledge_base import KNOWLEDGE_BASE

logger = logging.getLogger(__name__)


# OWASP API Security Top 10 模块定义（用于覆盖矩阵）
OWASP_MODULES = [
    ("2.1",  "API1",  "对象级别授权失效"),
    ("2.2",  "API2",  "身份认证失效"),
    ("2.3",  "API3",  "对象属性级别授权失效"),
    ("2.4",  "API4",  "资源消耗不受限"),
    ("2.5",  "API5",  "功能级别授权失效"),
    ("2.6",  "API6",  "无限制访问敏感业务流"),
    ("2.7",  "API7",  "服务器端请求伪造"),
    ("2.8",  "API8",  "安全配置错误"),
    ("2.9",  "API9",  "库存管理不当"),
    ("2.10", "API10", "不安全的第三方API集成"),
]

# 风险等级映射（平台 level -> 报告显示）
LEVEL_MAP = {
    "critical": ("严重", "🔴🔴🔴"),
    "high": ("高危", "🔴"),
    "medium": ("中危", "🟠"),
    "low": ("低危", "🟡"),
    "info": ("信息", "🔵"),
}

# 已知安全问题清单（测试前已确认）
KNOWN_ISSUES = [
    ("KN-01", "维护平台验证码固定为 `csba`，可被暴力破解", "🟠中危", "需求文档"),
    ("KN-02", "登录密码使用 SHA256 加密（非加盐哈希），可能被彩虹表破解", "🟠中危", "网络抓包"),
    ("KN-03", "`company_secret` 明文出现在 URL 参数中（讯飞转录接口）", "🔴高危", "网络抓包"),
    ("KN-04", "内部接口 `/api/system/inner/*` 可从外部访问", "🔴高危", "接口清单"),
    ("KN-05", "无登录接口 `/api/cdthApi/noLogin/*` 可无需认证访问", "🔴高危", "接口清单"),
    ("KN-06", "登录请求头含 `x-sign` 签名但签名算法在前端 JS 可逆向", "🟠中危", "网络抓包"),
]

# 默认服务器架构信息（对齐参考实现）
DEFAULT_SERVER_ARCH = "X86架构-欧拉"

# 参考资料（内网盘路径，对齐参考实现）
REFERENCE_MATERIALS = [
    "华为安全红线资料：[Z:\\deploy\\18其它系统\\安全测试\\01安全测试资料]",
    "以往漏洞报告资料：[Z:\\deploy\\18其它系统\\安全测试\\02项目漏洞资料]",
    "Nginx配置文件：[Z:\\deploy\\18其它系统\\安全测试\\03Nginx配置文件]",
]


class SecurityReportService:
    """
    安全测试报告生成服务

    生成 7 章节 Markdown 格式报告，迁移自参考实现 report_generator.py。
    """

    def __init__(self, report_dir: Optional[str] = None):
        """
        初始化报告服务

        Args:
            report_dir: 报告输出目录，默认使用 settings.REPORT_DIR
        """
        self.report_dir = Path(report_dir or settings.REPORT_DIR)
        self.report_dir.mkdir(parents=True, exist_ok=True)

    async def generate_report(
        self,
        execution_id: str,
        db: AsyncSession,
    ) -> str:
        """
        生成安全测试 Markdown 报告

        Args:
            execution_id: 执行记录ID
            db: 数据库会话

        Returns:
            str: 报告文件路径
        """
        # 1. 获取执行记录
        execution = await self._get_execution(execution_id, db)
        if not execution:
            raise ValueError(f"执行记录不存在: {execution_id}")

        # 2. 获取漏洞测试结果
        results = await self._get_vulnerability_results(execution_id, db)

        # 3. 获取安全测试配置（用于提取目标URL）
        config = await self._get_security_config(execution, db)
        target_url = config.target_url if config else ""
        server_ip = config.server_ip if config else self._extract_ip(target_url)

        # 4. 生成报告内容
        markdown = self._generate_markdown(
            execution=execution,
            results=results,
            target_url=target_url,
            server_ip=server_ip,
        )

        # 5. 保存报告文件
        file_path = self._save_report(
            server_ip=server_ip,
            markdown=markdown,
        )

        logger.info(f"安全测试报告已生成: {file_path}")
        return file_path

    async def get_report_content(
        self,
        execution_id: str,
        db: AsyncSession,
    ) -> str:
        """
        获取报告内容（不保存文件）

        Args:
            execution_id: 执行记录ID
            db: 数据库会话

        Returns:
            str: Markdown 报告内容
        """
        execution = await self._get_execution(execution_id, db)
        if not execution:
            raise ValueError(f"执行记录不存在: {execution_id}")

        results = await self._get_vulnerability_results(execution_id, db)
        config = await self._get_security_config(execution, db)
        target_url = config.target_url if config else ""
        server_ip = config.server_ip if config else self._extract_ip(target_url)

        return self._generate_markdown(
            execution=execution,
            results=results,
            target_url=target_url,
            server_ip=server_ip,
        )

    # ==================== 数据查询 ====================

    async def _get_execution(
        self,
        execution_id: str,
        db: AsyncSession,
    ) -> Optional[Execution]:
        """获取执行记录"""
        result = await db.execute(
            select(Execution).where(Execution.id == execution_id)
        )
        return result.scalar_one_or_none()

    async def _get_vulnerability_results(
        self,
        execution_id: str,
        db: AsyncSession,
    ) -> List[VulnerabilityResult]:
        """获取漏洞测试结果列表"""
        result = await db.execute(
            select(VulnerabilityResult)
            .where(VulnerabilityResult.execution_id == execution_id)
            .order_by(VulnerabilityResult.level, VulnerabilityResult.test_id)
        )
        return list(result.scalars().all())

    async def _get_security_config(
        self,
        execution: Execution,
        db: AsyncSession,
    ) -> Optional[SecurityConfig]:
        """获取安全测试配置（优先从执行记录获取，回退到默认配置）"""
        config_id = execution.config.get("config_id") if execution.config else None
        if config_id:
            result = await db.execute(
                select(SecurityConfig).where(SecurityConfig.id == config_id)
            )
            config = result.scalar_one_or_none()
            if config:
                return config

        # 回退：使用默认配置
        result = await db.execute(
            select(SecurityConfig).where(SecurityConfig.is_default == True)
        )
        return result.scalar_one_or_none()

    # ==================== Markdown 生成 ====================

    def _generate_markdown(
        self,
        execution: Execution,
        results: List[VulnerabilityResult],
        target_url: str,
        server_ip: str,
    ) -> str:
        """
        生成 Markdown 报告内容

        Args:
            execution: 执行记录
            results: 漏洞测试结果列表
            target_url: 目标服务器URL
            server_ip: 服务器IP

        Returns:
            str: Markdown 报告内容
        """
        lines = []

        # 统计数据
        summary = self._get_summary(results)

        # ========== 标题与基本信息 ==========
        lines.append("# 接口安全测试总报告\n")
        lines.append(f"> **生成时间**：{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        lines.append(f"> **测试目标**：{target_url}")
        lines.append(f"> **测试标准**：OWASP API Security Top 10 (2019)")
        lines.append(f"> **测试工具**：平台自动化测试系统 - 安全测试模块")
        lines.append("")

        # ========== 一、执行概要 ==========
        lines.append("## 一、执行概要\n")
        lines.append(f"本次安全测试共执行 **{summary['total']}** 个测试用例。\n")

        # 1.1 测试统计总览
        lines.append("### 1.1 测试统计总览\n")
        lines.append("| 统计项 | 数值 |")
        lines.append("|--------|------|")
        lines.append(f"| 测试用例总数 | **{summary['total']}** |")
        lines.append(f"| 发现漏洞总数 | **{summary['vuln_total']}** |")
        lines.append(f"| 🔴 高危漏洞 | **{summary['high']}** |")
        lines.append(f"| 🟠 中危漏洞 | **{summary['medium']}** |")
        lines.append(f"| 🟡 低危漏洞 | **{summary['low']}** |")
        lines.append(f"| 🔵 信息类 | **{summary['info']}** |")
        lines.append(f"| 🟢 已验证安全 | **{summary['safe']}** |")
        lines.append(f"| 测试时间 | {self._format_time_range(execution)} |")
        lines.append("")

        # 1.2 OWASP 覆盖矩阵（对齐参考实现：5列，无"严重"列）
        lines.append("### 1.2 OWASP API Security Top 10 覆盖矩阵\n")
        lines.append("| 编号 | 安全风险 | 用例数 | 🔴高危 | 🟠中危 | 🟡低危 | 🔵信息 | 🟢安全 |")
        lines.append("|------|---------|--------|--------|--------|--------|--------|--------|")

        for prefix, mod_key, mod_name in OWASP_MODULES:
            mod_stats = self._get_module_stats(results, prefix)
            lines.append(
                f"| {mod_key} | {mod_name} | {mod_stats['total']} | "
                f"{mod_stats['high']} | {mod_stats['medium']} | "
                f"{mod_stats['low']} | {mod_stats['info']} | {mod_stats['safe']} |"
            )

        # 合计行
        lines.append(
            f"| **合计** | | **{summary['total']}** | "
            f"**{summary['high']}** | **{summary['medium']}** | "
            f"**{summary['low']}** | **{summary['info']}** | **{summary['safe']}** |"
        )
        lines.append("")

        # ========== 二、漏洞详情 ==========
        vuln_results = [r for r in results if r.is_vulnerable]
        lines.append("## 二、漏洞详情\n")
        lines.append(
            f"> 共发现 **{summary['vuln_total']}** 个漏洞"
            f"（高危 {summary['high']} / "
            f"中危 {summary['medium']} / "
            f"低危 {summary['low']} / 信息类 {summary['info']}）\n"
        )

        if vuln_results:
            # 按风险等级排序
            level_order = {"critical": 0, "high": 1, "medium": 2, "low": 3, "info": 4}
            vuln_results.sort(key=lambda x: level_order.get(x.level, 99))

            for idx, r in enumerate(vuln_results, 1):
                level_name, level_emoji = LEVEL_MAP.get(r.level, (r.level, "⚪"))
                lines.append(f"### 2.{idx} [{level_name}] {r.name}\n")
                lines.append(f"- **用例编号**: {r.test_id}")
                lines.append(f"- **风险等级**: {level_emoji} {level_name}")
                lines.append(f"- **漏洞描述**: {r.description}")
                lines.append("")

                if r.request_info:
                    lines.append("**复现步骤 — 请求信息：**")
                    lines.append("```")
                    lines.append(self._truncate(r.request_info, 2000))
                    lines.append("```")
                    lines.append("")

                if r.response_info:
                    lines.append("**响应信息：**")
                    lines.append("```")
                    lines.append(self._truncate(r.response_info, 2000))
                    lines.append("```")
                    lines.append("")

                if r.fix_suggestion:
                    lines.append(f"**修复建议**: {r.fix_suggestion}")
                    lines.append("")

                lines.append("---")
                lines.append("")
        else:
            lines.append("本次测试未发现安全漏洞。\n")

        # ========== 三、已验证安全项 ==========
        safe_results = [r for r in results if not r.is_vulnerable]
        lines.append("## 三、已验证安全项\n")

        if safe_results:
            for prefix, mod_key, mod_name in OWASP_MODULES:
                module_safe = [r for r in safe_results if r.test_id.startswith(prefix)]
                if module_safe:
                    lines.append(f"### {mod_key} {mod_name}\n")
                    lines.append("| 编号 | 测试用例 | 结果 |")
                    lines.append("|------|---------|------|")
                    for r in module_safe:
                        lines.append(f"| {r.test_id} | {r.name} | ✅ 安全 |")
                    lines.append("")
        else:
            lines.append("无已验证安全项。\n")

        # ========== 四、风险评估与修复建议 ==========
        lines.append("## 四、风险评估与修复建议\n")

        if summary["high"] > 0:
            lines.append("### 4.1 高危风险（需立即修复）\n")

            if any(r.level in ("critical", "high") and r.test_id.startswith("2.1") for r in vuln_results):
                lines.append("#### 越权漏洞（API1 / API3）")
                lines.append("- **问题描述**：普通用户可访问其他用户的数据（水平越权），普通用户可访问管理员接口（垂直越权）")
                lines.append("- **影响范围**：用户数据泄露、数据篡改、未授权操作")
                lines.append("- **修复建议**：")
                lines.append("  1. 后端接口增加严格的资源所有权校验，确保用户只能操作属于自己的数据")
                lines.append("  2. 对所有写操作（修改/删除）进行资源归属验证")
                lines.append("  3. 实现基于 RBAC 的权限控制，确保普通用户无法调用管理接口")
                lines.append("  4. 在 API 网关层增加统一的权限拦截器")
                lines.append("")

            if any(r.level in ("critical", "high") and r.test_id.startswith("2.2") for r in vuln_results):
                lines.append("#### 身份认证缺陷（API2）")
                lines.append("- **问题描述**：存在 Token 伪造可用、暴力破解无限制等问题")
                lines.append("- **影响范围**：账号被盗用、未授权访问")
                lines.append("- **修复建议**：")
                lines.append("  1. 加强 Token 校验机制，增加 IP 绑定或设备指纹校验")
                lines.append("  2. 实现 Token 黑名单机制，支持主动注销")
                lines.append("  3. 登录接口增加速率限制（如 5 次失败后锁定 15 分钟）")
                lines.append("  4. 验证码应随机生成，不应使用固定值")
                lines.append("")

            if any(r.level in ("critical", "high") and r.test_id.startswith("2.8") for r in vuln_results):
                lines.append("#### 安全配置错误（API8）")
                lines.append("- **问题描述**：中间件管理界面可匿名访问、敏感信息泄露")
                lines.append("- **影响范围**：系统配置泄露、攻击面扩大")
                lines.append("- **修复建议**：")
                lines.append("  1. Nginx 层面限制 `/nacos` 等路径仅内网可访问")
                lines.append("  2. 生产环境禁用 Swagger 或限制内网访问")
                lines.append("  3. 配置全局异常处理器，隐藏技术细节")
                lines.append("")

        if summary["medium"] > 0:
            lines.append("### 4.2 中危风险（建议尽快修复）\n")
            lines.append("请在漏洞详情章节查看具体中危漏洞的修复建议。\n")

        if summary["low"] > 0:
            lines.append("### 4.3 低危风险（建议择期修复）\n")
            lines.append("请在漏洞详情章节查看具体低危漏洞的修复建议。\n")

        if summary["vuln_total"] == 0:
            lines.append("本次测试未发现安全漏洞，无需修复。\n")

        # ========== 五、已知安全问题清单 ==========
        lines.append("## 五、已知安全问题清单\n")
        lines.append("> 以下问题在测试前已确认，非本次测试发现。\n")
        lines.append("| 编号 | 问题描述 | 风险等级 | 来源 |")
        lines.append("|------|---------|---------|------|")
        for kn_id, desc, level, source in KNOWN_ISSUES:
            lines.append(f"| {kn_id} | {desc} | {level} | {source} |")
        lines.append("")

        # ========== 五.五、历史漏洞回归验证 ==========
        regression_results = self._get_regression_results(results)
        if regression_results:
            lines.append("## 五.五、历史漏洞回归验证\n")
            lines.append("> 以下测试用例基于历史漏洞报告和Nginx配置分析，验证已知安全问题是否已修复。\n")
            lines.append("| 编号 | 漏洞来源 | 测试项 | 当前状态 | 风险等级 | 详情 |")
            lines.append("|------|---------|--------|---------|---------|------|")

            for r in regression_results:
                source = self._get_vuln_source(r.test_id)
                status = "🔴 未修复" if r.is_vulnerable else "✅ 已修复/已验证安全"
                level_name, _ = LEVEL_MAP.get(r.level, (r.level, ""))
                desc_short = r.description[:80] + "..." if len(r.description) > 80 else r.description
                lines.append(f"| {r.test_id} | {source} | {r.name} | {status} | {level_name} | {desc_short} |")
            lines.append("")

        # ========== 五.六、华为安全红线合规性检查 ==========
        hw_results = self._get_huawei_redline_results(results)
        if hw_results:
            lines.append("## 五.六、华为安全红线合规性检查\n")
            lines.append("> 以下检查项基于华为安全红线要求，验证系统是否符合安全基线标准。\n")
            lines.append("| 检查编号 | 检查类别 | 要求 | 测试结果 | 合规状态 |")
            lines.append("|---------|---------|------|---------|---------|")

            for r in hw_results:
                hw_info = self._get_hw_info(r.test_id)
                hw_id = hw_info.get("check_id", r.test_id)
                hw_category = hw_info.get("category", "")
                hw_requirement = hw_info.get("requirement", r.name)
                if len(hw_requirement) > 60:
                    hw_requirement = hw_requirement[:57] + "..."
                compliance = "🔴 不合规" if r.is_vulnerable else "✅ 合规"
                lines.append(f"| {hw_id} | {hw_category} | {hw_requirement} | {r.name} | {compliance} |")
            lines.append("")

        # ========== 六、测试环境与工具 ==========
        lines.append("## 六、测试环境与工具\n")
        lines.append("| 项目 | 详情 |")
        lines.append("|------|------|")
        lines.append(f"| 目标服务器 | {server_ip} ({DEFAULT_SERVER_ARCH}) |")
        lines.append("| 测试工具 | Python + requests + pycryptodome |")
        lines.append("| 测试框架 | 自研 ApiSecurityTest |")
        lines.append("| 测试标准 | OWASP API Security Top 10 (2019) + 华为安全红线 + 历史漏洞回归 |")
        lines.append("| 报告格式 | Markdown（一份总报告） |")
        lines.append("")

        # ========== 七、参考资料 ==========
        lines.append("## 七、参考资料\n")
        for ref in REFERENCE_MATERIALS:
            lines.append(f"- {ref}")
        lines.append("")

        return "\n".join(lines)

    # ==================== 统计辅助方法 ====================

    def _get_summary(self, results: List[VulnerabilityResult]) -> Dict[str, int]:
        """
        获取统计数据（对齐参考实现：critical 合并到 high，5 列矩阵）

        Returns:
            dict: 包含 high/medium/low/info/safe/total/vuln_total
        """
        summary = {
            "total": len(results),
            "high": 0,       # critical 合并到 high
            "medium": 0,
            "low": 0,
            "info": 0,
            "safe": 0,
            "vuln_total": 0,
        }

        for r in results:
            if r.is_vulnerable:
                # critical 合并到 high，其他保持不变
                mapped_level = "high" if r.level == "critical" else r.level
                if mapped_level in summary:
                    summary[mapped_level] += 1
                summary["vuln_total"] += 1
            else:
                summary["safe"] += 1

        return summary

    def _get_module_stats(self, results: List[VulnerabilityResult], prefix: str) -> Dict[str, int]:
        """
        获取指定 OWASP 模块的统计数据（对齐参考实现：critical 合并到 high）

        Args:
            results: 漏洞测试结果列表
            prefix: test_id 前缀（如 "2.1"）

        Returns:
            dict: 包含 total/high/medium/low/info/safe
        """
        related = [r for r in results if r.test_id.startswith(prefix)]
        stats = {
            "total": len(related),
            "high": 0,       # critical 合并到 high
            "medium": 0,
            "low": 0,
            "info": 0,
            "safe": 0,
        }

        for r in related:
            if r.is_vulnerable:
                # critical 合并到 high
                mapped_level = "high" if r.level == "critical" else r.level
                if mapped_level in stats:
                    stats[mapped_level] += 1
            else:
                stats["safe"] += 1

        return stats

    def _get_regression_results(self, results: List[VulnerabilityResult]) -> List[VulnerabilityResult]:
        """获取历史漏洞回归测试结果"""
        # 通过 test_id 前缀 HV- 识别，或通过 extra_data.is_regression
        regression = []
        for r in results:
            if r.test_id.startswith("HV-"):
                regression.append(r)
            elif r.extra_data and r.extra_data.get("is_regression"):
                regression.append(r)
        return regression

    def _get_huawei_redline_results(self, results: List[VulnerabilityResult]) -> List[VulnerabilityResult]:
        """获取华为安全红线检查结果"""
        # 通过 test_id 前缀 HW- 识别，或通过 extra_data.is_huawei_redline
        hw_results = []
        for r in results:
            if r.test_id.startswith("HW-"):
                hw_results.append(r)
            elif r.extra_data and r.extra_data.get("is_huawei_redline"):
                hw_results.append(r)
        return hw_results

    def _get_vuln_source(self, test_id: str) -> str:
        """从知识库获取漏洞来源"""
        historical_vulns = KNOWLEDGE_BASE.get("historical_vulns", [])
        for v in historical_vulns:
            if v.get("vuln_id") == test_id:
                return v.get("source_project", "历史漏洞报告")
        return "历史漏洞报告"

    def _get_hw_info(self, test_id: str) -> Dict[str, str]:
        """从知识库获取华为红线信息"""
        huawei_redlines = KNOWLEDGE_BASE.get("huawei_redlines", [])
        for r in huawei_redlines:
            if r.get("check_id") == test_id:
                return {
                    "check_id": r.get("check_id", ""),
                    "category": r.get("category", ""),
                    "requirement": r.get("requirement", ""),
                }
        return {}

    # ==================== 工具方法 ====================

    def _format_time_range(self, execution: Execution) -> str:
        """格式化时间范围"""
        start = execution.start_time.strftime("%Y-%m-%d %H:%M:%S") if execution.start_time else "未开始"
        end = execution.end_time.strftime("%Y-%m-%d %H:%M:%S") if execution.end_time else "未结束"
        return f"{start} ~ {end}"

    def _extract_ip(self, url: str) -> str:
        """从 URL 提取 IP"""
        if not url:
            return "unknown"
        url = url.strip().rstrip("/")
        ip = url.replace("https://", "").replace("http://", "")
        ip = ip.split(":")[0]
        return ip

    def _truncate(self, text: str, max_len: int) -> str:
        """截断文本"""
        if len(text) <= max_len:
            return text
        return text[:max_len-3] + "..."

    def _save_report(self, server_ip: str, markdown: str) -> str:
        """保存报告文件"""
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"{server_ip}_安全测试报告_{timestamp}.md"
        filepath = self.report_dir / filename

        filepath.write_text(markdown, encoding="utf-8")
        return str(filepath)