#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：security_erp_task_service.py
模块描述：安全测试 ERP 任务创建服务，解析报告摘要、获取基础数据、构建任务数据、创建 ERP 任务
          参考实现：ApiSecurityTest/utils/task_creator.py + report_parser.py

作者：czj
创建日期：2026-08-17
最后修改：2026-08-17
"""

import logging
import re
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any

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

from app.config import settings
from app.models.erp_config import ErpConfig
from app.services.erp_task_client import ERPTaskClient
from app.services.security_report_service import SecurityReportService

logger = logging.getLogger(__name__)


# ==================== 默认任务配置 ====================
# 对齐参考实现 config.yaml 的 erp.task 节点
DEFAULT_TASK_CONFIG = {
    "type_id": 3,             # 任务类型：3 = 后端开发
    "status_id": 1,           # 任务状态：1 = 新建
    "level": 4,               # 默认紧急程度：4 = 常规处理
    "excepthour": 21,         # 默认期望工时（小时）
    "user_id": 101,           # 默认责任人 ID：李立键
    "createuser_id": 441,     # 默认创建人 ID：陈泽键
    "deadline_days": 7,       # 默认截止天数
    "develop_search_str": "【新统一平台】公司内部安全测试扫描及修复验证",
}

# 任务类型/状态 ID → 名称映射（用于日志展示，从 ERP 实时获取优先）
LEVEL_NAMES = {
    1: "低优先级",
    2: "正常优先级",
    3: "中优先级",
    4: "高优先级",
    5: "最高优先级",
}

URGENCY_DESC = {
    5: "【最高优先级】有高危漏洞，需立即处理",
    4: "【高优先级】有中危漏洞，建议优先处理",
    3: "【中优先级】有低危漏洞，建议按计划修复",
    2: "【正常优先级】仅有信息类问题",
    1: "【低优先级】无安全漏洞",
}


class SecurityErpTaskService:
    """
    安全测试 ERP 任务创建服务

    流程：
    1. 生成报告 → 解析摘要
    2. 获取 ERP 基础数据（任务类型/状态/人员）
    3. 搜索关联需求
    4. 构建任务数据（名称/描述/截止时间）
    5. 创建 ERP 任务
    """

    def __init__(self, db: AsyncSession):
        self.db = db

    # ==================== 对外 API ====================

    async def get_task_preview(self, execution_id: str) -> dict:
        """
        获取任务创建预览数据

        生成报告并解析摘要、获取基础数据、搜索需求，
        供前端在创建任务前展示和配置。

        Args:
            execution_id: 安全测试执行 ID

        Returns:
            dict: 预览数据（报告摘要 + 基础数据 + 默认配置）
        """
        # 1. 生成报告并解析摘要
        markdown = await self._get_report_markdown(execution_id)
        summary = parse_report_summary(markdown)
        server_ip = summary.get("server_ip") or self._extract_ip(summary.get("target", ""))

        # 2. 基础数据
        client = await self._get_erp_client()
        task_types = client.get_task_types() or []
        task_statuses = client.get_task_statuses() or []
        staff_list = client.get_staff_list() or []

        # 3. 搜索需求
        requirement = self._search_requirement(client)

        # 4. 计算紧急程度
        calculated_level = calculate_urgency_level(summary)
        task_name = generate_task_name(server_ip)

        return {
            "summary": summary,
            "task_name": task_name,
            "calculated_level": calculated_level,
            "task_types": task_types,
            "task_statuses": task_statuses,
            "staff_list": staff_list[:200],  # 限制返回数量，避免前端下拉过大
            "requirement": requirement,
            "default_config": {
                "type_id": DEFAULT_TASK_CONFIG["type_id"],
                "status_id": DEFAULT_TASK_CONFIG["status_id"],
                "level": calculated_level if calculated_level else DEFAULT_TASK_CONFIG["level"],
                "excepthour": DEFAULT_TASK_CONFIG["excepthour"],
                "user_id": DEFAULT_TASK_CONFIG["user_id"],
                "createuser_id": DEFAULT_TASK_CONFIG["createuser_id"],
                "deadline_days": DEFAULT_TASK_CONFIG["deadline_days"],
            },
        }

    async def create_task(self, execution_id: str, config: Dict[str, Any]) -> dict:
        """
        创建 ERP 任务

        Args:
            execution_id: 安全测试执行 ID
            config: 任务配置（type_id/status_id/level/excepthour/user_id/createuser_id/deadline_days/develop_id）

        Returns:
            dict: 创建结果（success/task_id/task_name/message）
        """
        # 1. 生成报告并解析摘要
        markdown = await self._get_report_markdown(execution_id)
        summary = parse_report_summary(markdown)
        server_ip = summary.get("server_ip") or self._extract_ip(summary.get("target", ""))

        # 2. 解析漏洞（获取高危漏洞名称列表用于描述）
        high_risk_list = parse_high_risk_vulnerabilities(markdown)
        medium_count = summary.get("medium", 0)

        # 3. 获取 ERP 客户端
        client = await self._get_erp_client()

        # 4. 构建任务数据
        task_data = build_task_data(
            summary=summary,
            server_ip=server_ip,
            high_risk_list=high_risk_list,
            medium_count=medium_count,
            config=config,
        )

        # 5. 创建任务
        result = client.create_task(task_data)
        if result and result.get("success") == 1:
            data = result.get("data", {})
            task_id = data.get("create")
            task_name = task_data.get("name", "")
            logger.info(f"✓ ERP 任务创建成功: ID={task_id}, 名称={task_name}")
            return {
                "success": True,
                "task_id": task_id,
                "task_name": task_name,
                "message": "ERP任务创建成功",
            }
        else:
            logger.error("✗ ERP 任务创建失败")
            return {
                "success": False,
                "task_id": None,
                "task_name": task_data.get("name", ""),
                "message": "ERP任务创建失败，请检查 ERP 配置和网络连接",
            }

    # ==================== 报告处理 ====================

    async def _get_report_markdown(self, execution_id: str) -> str:
        """
        生成报告并返回 Markdown 内容

        Args:
            execution_id: 安全测试执行 ID

        Returns:
            str: Markdown 报告内容
        """
        report_service = SecurityReportService()
        return await report_service.get_report_content(execution_id, self.db)

    # ==================== ERP 客户端 ====================

    async def _get_erp_client(self) -> ERPTaskClient:
        """
        从数据库读取 ERP 配置，创建任务客户端

        Returns:
            ERPTaskClient: ERP 任务客户端
        """
        # 读取 ERP 配置（复用 erp_configs 表）
        result = await self.db.execute(select(ErpConfig).where(ErpConfig.id == 1))
        config = result.scalar_one_or_none()

        base_url = (config.base_url if config and config.base_url else "") or settings.ERP_BASE_URL or ""
        api_key = (config.api_key if config and config.api_key else "") or settings.ERP_API_KEY or ""

        return ERPTaskClient(
            base_url=base_url,
            api_key=api_key,
            timeout=30,
            max_retries=3,
            retry_interval=2,
            cache_duration=3600,
        )

    def _search_requirement(self, client: ERPTaskClient) -> Optional[Dict]:
        """
        搜索关联需求

        Args:
            client: ERP 任务客户端

        Returns:
            需求信息字典，未找到返回 None
        """
        search_str = DEFAULT_TASK_CONFIG.get("develop_search_str", "")
        if not search_str:
            return None

        try:
            data_list = client.search_develop(search_str=search_str, page_num=1, page_size=10)
            if data_list and len(data_list) > 0:
                req = data_list[0]
                logger.info(f"✓ 找到需求: {req.get('name', '')} (ID: {req.get('id', '')})")
                return req
        except Exception as e:
            logger.error(f"搜索需求异常: {e}")

        logger.warning("未找到关联需求，任务将不关联需求")
        return None

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

    @staticmethod
    def _extract_ip(url: str) -> str:
        """从 URL 提取 IP 地址"""
        if not url:
            return ""
        ip_match = re.search(r"(\d+\.\d+\.\d+\.\d+)", url)
        return ip_match.group(1) if ip_match else ""


# ==================== 模块级函数（可独立测试） ====================


def parse_report_summary(markdown: str) -> Dict[str, Any]:
    """
    从 Markdown 安全测试报告提取摘要

    对齐参考实现 report_parser.py 的正则格式。

    Args:
        markdown: 报告 Markdown 内容

    Returns:
        dict: 摘要数据（target/server_ip/start_time/end_time/total/high/medium/low/info/safe）
    """
    summary = {
        "target": "",
        "server_ip": "",
        "start_time": "",
        "end_time": "",
        "total": 0,
        "high": 0,
        "medium": 0,
        "low": 0,
        "info": 0,
        "safe": 0,
    }

    if not markdown:
        return summary

    # 测试目标（> **测试目标**：https://192.168.5.44）
    target_match = re.search(r"> \*\*测试目标\*\*[：:]\s*(https?://[^\s\n]+)", markdown)
    if target_match:
        summary["target"] = target_match.group(1).strip()

    # 服务器 IP（从目标 URL 提取）
    ip_match = re.search(r"(\d+\.\d+\.\d+\.\d+)", summary["target"])
    if ip_match:
        summary["server_ip"] = ip_match.group(1)

    # 测试时间（| 测试时间 | 2026-08-17 10:00:00 ~ 2026-08-17 11:30:00 |）
    time_match = re.search(r"\|\s*测试时间\s*\|\s*(.+?~.+?)\s*\|", markdown)
    if time_match:
        time_range = time_match.group(1).strip()
        times = re.split(r"\s*~\s*", time_range)
        if len(times) == 2:
            summary["start_time"] = times[0].strip()
            summary["end_time"] = times[1].strip()

    # 测试用例总数（| 测试用例总数 | **145** |）
    total_match = re.search(r"\|\s*测试用例总数\s*\|\s*\*\*(\d+)\*\*", markdown)
    if total_match:
        summary["total"] = int(total_match.group(1))

    # 各等级漏洞数
    summary["high"] = _parse_count(markdown, r"\|\s*🔴 高危漏洞\s*\|\s*\*\*(\d+)\*\*")
    summary["medium"] = _parse_count(markdown, r"\|\s*🟠 中危漏洞\s*\|\s*\*\*(\d+)\*\*")
    summary["low"] = _parse_count(markdown, r"\|\s*🟡 低危漏洞\s*\|\s*\*\*(\d+)\*\*")
    summary["info"] = _parse_count(markdown, r"\|\s*🔵 信息类\s*\|\s*\*\*(\d+)\*\*")
    summary["safe"] = _parse_count(markdown, r"\|\s*🟢 已验证安全\s*\|\s*\*\*(\d+)\*\*")

    logger.info(
        f"报告摘要解析: 目标={summary['target']}, 总数={summary['total']}, "
        f"高危={summary['high']}, 中危={summary['medium']}, 低危={summary['low']}, "
        f"信息={summary['info']}, 安全={summary['safe']}"
    )
    return summary


def calculate_urgency_level(summary: Dict[str, Any]) -> int:
    """
    根据漏洞数量计算紧急程度（1-5）

    返回:
        5 - 有高危漏洞（最紧急）
        4 - 有中危漏洞
        3 - 有低危漏洞
        2 - 仅有信息类
        1 - 无漏洞
    """
    high = summary.get("high", 0)
    medium = summary.get("medium", 0)
    low = summary.get("low", 0)
    info = summary.get("info", 0)

    if high > 0:
        return 5
    elif medium > 0:
        return 4
    elif low > 0:
        return 3
    elif info > 0:
        return 2
    return 1


def generate_task_name(server_ip: str) -> str:
    """
    生成 ERP 任务名称

    格式：{目标IP}接口安全测试及漏洞修复_{YYYYMMDD}

    Args:
        server_ip: 服务器 IP 地址

    Returns:
        str: 任务名称
    """
    date_str = datetime.now().strftime("%Y%m%d")
    if server_ip:
        return f"{server_ip}接口安全测试及漏洞修复_{date_str}"
    return f"接口安全测试及漏洞修复_{date_str}"


def parse_high_risk_vulnerabilities(markdown: str) -> List[str]:
    """
    解析高危漏洞名称列表

    Args:
        markdown: 报告 Markdown 内容

    Returns:
        list: 高危漏洞名称列表
    """
    high_risk = []

    if not markdown:
        return high_risk

    # 匹配格式：### 2.1 [高危] 漏洞名称 或 ### 2.1 [严重] 漏洞名称
    pattern = r"### \d+\.\d+ \[(高危|严重)\]\s+([^\n]+)"
    matches = re.findall(pattern, markdown)
    for level, name in matches:
        high_risk.append(name.strip())

    return high_risk


def build_task_data(
    summary: Dict[str, Any],
    server_ip: str,
    high_risk_list: List[str],
    medium_count: int,
    config: Dict[str, Any],
) -> Dict[str, Any]:
    """
    构建 ERP 任务数据

    Args:
        summary: 报告摘要
        server_ip: 服务器 IP
        high_risk_list: 高危漏洞名称列表
        medium_count: 中危漏洞数量
        config: 用户配置（type_id/status_id/level/excepthour/user_id/createuser_id/deadline_days/develop_id）

    Returns:
        dict: POST /openclaw/task 的请求体
    """
    # 任务名称
    task_name = config.get("task_name") or generate_task_name(server_ip)

    # 类型/状态
    type_id = int(config.get("type_id") or DEFAULT_TASK_CONFIG["type_id"])
    status_id = int(config.get("status_id") or DEFAULT_TASK_CONFIG["status_id"])

    # 紧急程度：优先用户指定，否则按漏洞自动计算
    level = int(config.get("level") or 0)
    if level < 1 or level > 5:
        level = calculate_urgency_level(summary)

    # 期望工时 / 截止时间
    excepthour = int(config.get("excepthour") or DEFAULT_TASK_CONFIG["excepthour"])
    deadline_days = int(config.get("deadline_days") or DEFAULT_TASK_CONFIG["deadline_days"])
    deadline = datetime.now() + timedelta(days=deadline_days)
    excepttime = deadline.strftime("%Y-%m-%d %H:%M:%S")

    # 责任人 / 创建人
    user_id = int(config.get("user_id") or DEFAULT_TASK_CONFIG["user_id"])
    createuser_id = int(config.get("createuser_id") or DEFAULT_TASK_CONFIG["createuser_id"])

    # 任务描述 HTML
    content = generate_task_content(
        summary=summary,
        high_risk_list=high_risk_list,
        medium_count=medium_count,
        nas_report_path="",  # 平台不上传网盘，参考实现中为可选
    )

    task_data = {
        "name": task_name,
        "type_id": type_id,
        "status_id": status_id,
        "level": level,
        "excepthour": excepthour,
        "excepttime": excepttime,
        "content": content,
        "user_id": user_id,
        "createuser_id": createuser_id,
    }

    # 可选字段：关联需求
    develop_id = config.get("develop_id")
    if develop_id:
        task_data["develop_id"] = int(develop_id)

    logger.info(
        f"任务数据: 名称={task_name}, 类型={type_id}, 状态={status_id}, "
        f"紧急程度={level}, 工时={excepthour}h, 截止={excepttime}, "
        f"责任人={user_id}, 创建人={createuser_id}"
    )
    return task_data


def generate_task_content(
    summary: Dict[str, Any],
    high_risk_list: List[str],
    medium_count: int,
    nas_report_path: str = "",
) -> str:
    """
    生成 ERP 任务描述 HTML

    对齐参考实现 report_parser.py 的 generate_task_content。

    Args:
        summary: 报告摘要
        high_risk_list: 高危漏洞名称列表
        medium_count: 中危漏洞数量
        nas_report_path: 网盘报告路径（可选，平台暂不传）

    Returns:
        str: HTML 格式任务描述
    """
    html_parts = []

    # 标题
    html_parts.append("<h3>接口安全测试报告</h3>")

    # 测试概览
    html_parts.append("<h4>一、测试概览</h4>")
    html_parts.append(
        "<table border='1' cellpadding='5' cellspacing='0' style='border-collapse: collapse; width:100%;'>"
    )
    html_parts.append(
        f"<tr><td style='width:20%;'><strong>测试目标：</strong></td><td>{summary.get('target', 'N/A')}</td></tr>"
    )
    html_parts.append(
        f"<tr><td><strong>测试时间：</strong></td><td>{summary.get('start_time', 'N/A')} ~ {summary.get('end_time', 'N/A')}</td></tr>"
    )
    html_parts.append("<tr><td><strong>测试标准：</strong></td><td>OWASP API Security Top 10 (2019)</td></tr>")
    html_parts.append(
        "<tr><td><strong>测试工具：</strong></td><td>平台自动化测试系统 - 安全测试模块</td></tr>"
    )
    html_parts.append("</table>")

    # 漏洞统计
    html_parts.append("<h4>二、漏洞统计</h4>")
    html_parts.append(
        "<table border='1' cellpadding='5' cellspacing='0' style='border-collapse: collapse;'>"
    )
    html_parts.append("<tr><th>总计</th><th>高危</th><th>中危</th><th>低危</th><th>信息类</th><th>安全</th></tr>")
    html_parts.append(f"<tr><td>{summary.get('total', 0)}</td>")
    html_parts.append(f"<td style='color:red; font-weight:bold;'>{summary.get('high', 0)}</td>")
    html_parts.append(f"<td style='color:orange; font-weight:bold;'>{summary.get('medium', 0)}</td>")
    html_parts.append(f"<td style='color:#FFD700; font-weight:bold;'>{summary.get('low', 0)}</td>")
    html_parts.append(f"<td style='color:blue; font-weight:bold;'>{summary.get('info', 0)}</td>")
    html_parts.append(f"<td style='color:green; font-weight:bold;'>{summary.get('safe', 0)}</td></tr>")
    html_parts.append("</table>")

    # 高危漏洞列表
    if high_risk_list:
        html_parts.append("<h4>三、高危漏洞详情</h4>")
        html_parts.append("<p><strong>本次测试发现以下高危漏洞，需优先处理：</strong></p>")
        html_parts.append("<ol>")
        for idx, vuln in enumerate(high_risk_list[:10], 1):
            html_parts.append(f"<li style='color: red; margin-bottom: 5px;'><strong>{idx}. {vuln}</strong></li>")
        if len(high_risk_list) > 10:
            html_parts.append(f"<li>...及其他 {len(high_risk_list) - 10} 个高危漏洞</li>")
        html_parts.append("</ol>")

    # 中危漏洞统计
    if medium_count > 0:
        html_parts.append("<h4>四、中危漏洞</h4>")
        html_parts.append(f"<p>发现中危漏洞 <strong>{medium_count}</strong> 个，建议优先修复。</p>")

    # 修复建议
    html_parts.append("<h4>五、修复建议</h4>")
    html_parts.append("<ul>")
    html_parts.append("<li><strong>优先处理高危漏洞：</strong>所有高危漏洞应在确认后24小时内修复</li>")
    html_parts.append("<li><strong>中危漏洞：</strong>建议在本测试周期内完成修复</li>")
    html_parts.append("<li><strong>低危漏洞：</strong>可在下一版本迭代中修复</li>")
    html_parts.append("<li><strong>修复验证：</strong>漏洞修复完成后，请通知测试团队进行回归验证</li>")
    html_parts.append("<li><strong>复查要求：</strong>所有漏洞修复后需要重新进行安全测试验证</li>")
    html_parts.append("</ul>")

    # 报告链接（当前平台不传网盘路径，跳过该章节）
    if nas_report_path:
        html_parts.append("<h4>六、测试报告</h4>")
        html_parts.append("<p>详细测试报告已上传至网盘：</p>")
        html_parts.append(f"<p><code>{nas_report_path}</code></p>")
        html_parts.append("<p>请下载查看完整报告了解所有漏洞详情和修复建议。</p>")

    # 紧急度说明
    calculated_level = calculate_urgency_level(summary)
    desc = URGENCY_DESC.get(calculated_level, "")
    if desc:
        html_parts.append(f"<p><strong>{desc}</strong></p>")

    # 联系信息
    html_parts.append("<h4>七、联系方式</h4>")
    html_parts.append("<p>如有疑问或需要协助，请联系测试团队。</p>")

    return "\n".join(html_parts)


def _parse_count(markdown: str, pattern: str) -> int:
    """从 Markdown 中解析数字统计"""
    match = re.search(pattern, markdown)
    if match:
        return int(match.group(1))
    return 0