#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：security_erp_upload.py
模块描述：安全测试 ERP 上传 API 路由，提供安全测试报告上传到 ERP 测试单/项目资料/协作文档的接口

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

import logging
import os
from pathlib import Path
from typing import Optional, List

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import get_db
from app.config import settings
from app.services.erp_service import ErpService
from app.services.security_report_service import SecurityReportService

logger = logging.getLogger(__name__)

router = APIRouter()

# ==================== Pydantic 模型 ====================


class UploadToErpRequest(BaseModel):
    """上传安全报告到 ERP 请求"""
    developtesting_id: Optional[int] = Field(None, description="测试单ID，默认使用系统配置")
    copyuser_names: List[str] = Field(default_factory=list, description="抄送人姓名列表")
    createuser_name: Optional[str] = Field(None, description="创建人姓名")


class UploadToErpResponse(BaseModel):
    """上传到 ERP 响应"""
    success: bool = Field(..., description="是否成功")
    report_id: Optional[int] = Field(None, description="ERP报告ID")
    message: str = Field(..., description="提示信息")


class UploadToProjectRequest(BaseModel):
    """上传文件到 ERP 项目资料请求"""
    project_id: int = Field(..., description="关联的项目ID")
    name: Optional[str] = Field(None, description="文件名称（默认取文件名）")


class UploadToCooperationRequest(BaseModel):
    """上传文件到 ERP 协作文档请求"""
    project_id: int = Field(..., description="关联的项目ID")
    name: Optional[str] = Field(None, description="文件名称（默认取文件名）")
    group_id: Optional[int] = Field(None, description="分组ID（可选）")


class UploadFileToErpResponse(BaseModel):
    """上传文件到 ERP（项目资料/协作文档）响应"""
    success: bool = Field(..., description="是否成功")
    file_id: Optional[int] = Field(None, description="ERP文件ID")
    message: str = Field(..., description="提示信息")


# ==================== 辅助函数 ====================


def get_erp_service() -> ErpService:
    """获取 ERP 服务实例"""
    return ErpService(
        api_key=settings.ERP_API_KEY,
        base_url=settings.ERP_BASE_URL,
        developtesting_id=settings.ERP_DEVELOPTESTING_ID,
        type_id=settings.ERP_TYPE_ID,
        image_url_prefix=settings.ERP_IMAGE_URL_PREFIX,
    )


async def _generate_and_convert_report(
    execution_id: str,
    db: AsyncSession,
) -> str:
    """
    生成安全测试报告并转换为 DOCX 格式

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

    Returns:
        str: 生成的 DOCX 文件路径

    Raises:
        ValueError: 报告生成失败
    """
    report_service = SecurityReportService()

    # 1. 生成 Markdown 报告
    md_path = await report_service.generate_report(execution_id, db)
    if not md_path or not os.path.exists(md_path):
        raise ValueError("安全测试报告生成失败")

    # 2. 将 Markdown 转换为 DOCX
    docx_path = _convert_markdown_to_docx(md_path)
    if not docx_path or not os.path.exists(docx_path):
        raise ValueError("报告转换为 DOCX 失败")

    logger.info(f"安全报告已转换为 DOCX: {docx_path}")
    return docx_path


def _convert_markdown_to_docx(md_path: str) -> str:
    """
    将 Markdown 文件转换为 DOCX 文件

    Args:
        md_path: Markdown 文件路径

    Returns:
        str: DOCX 文件路径
    """
    from docx import Document
    from docx.shared import Pt, RGBColor, Inches
    from docx.enum.text import WD_ALIGN_PARAGRAPH

    # 读取 Markdown 内容
    with open(md_path, "r", encoding="utf-8") as f:
        md_content = f.read()

    # 生成 DOCX 路径（与 MD 同目录）
    md_file = Path(md_path)
    docx_path = str(md_file.with_suffix(".docx"))

    doc = Document()

    # 设置默认字体
    style = doc.styles["Normal"]
    font = style.font
    font.name = "Microsoft YaHei"
    font.size = Pt(10.5)

    lines = md_content.split("\n")
    i = 0
    in_code_block = False
    code_buffer = []
    in_table = False
    table_headers = []
    table_rows = []
    table_separator_seen = False

    while i < len(lines):
        line = lines[i]

        # 代码块处理
        if line.strip().startswith("```"):
            if in_code_block:
                # 结束代码块，写入为段落
                code_text = "\n".join(code_buffer)
                p = doc.add_paragraph()
                run = p.add_run(code_text)
                run.font.size = Pt(9)
                run.font.name = "Courier New"
                p.paragraph_format.left_indent = Inches(0.3)
                code_buffer = []
                in_code_block = False
                i += 1
                continue
            else:
                in_code_block = True
                i += 1
                continue

        if in_code_block:
            code_buffer.append(line)
            i += 1
            continue

        # 表格处理
        if "|" in line and line.strip().startswith("|"):
            cells = [c.strip() for c in line.strip().strip("|").split("|")]

            if not in_table:
                # 开始新表格
                in_table = True
                table_headers = cells
                table_separator_seen = False
                table_rows = []
            elif not table_separator_seen:
                # 分隔行（|---|---|），跳过
                table_separator_seen = True
            else:
                # 数据行
                table_rows.append(cells)
            i += 1
            continue
        else:
            # 结束表格
            if in_table and table_headers:
                _add_table_to_doc(doc, table_headers, table_rows)
                in_table = False
                table_headers = []
                table_rows = []

        # 空行
        if not line.strip():
            # 如果前一个段落不是空段落，添加一个空段落
            if doc.paragraphs and doc.paragraphs[-1].text.strip():
                doc.add_paragraph("")
            i += 1
            continue

        # 标题
        if line.startswith("## "):
            text = line[3:].strip()
            _add_heading(doc, text, 2)
        elif line.startswith("### "):
            text = line[4:].strip()
            _add_heading(doc, text, 3)
        elif line.startswith("#### "):
            text = line[5:].strip()
            _add_heading(doc, text, 4)
        elif line.startswith("# "):
            text = line[2:].strip()
            _add_heading(doc, text, 1)
        elif line.startswith("> "):
            # 引用
            text = line[2:].strip()
            p = doc.add_paragraph()
            run = p.add_run(text)
            run.font.italic = True
            run.font.color.rgb = RGBColor(0x66, 0x66, 0x66)
            p.paragraph_format.left_indent = Inches(0.3)
        elif line.startswith("- "):
            # 无序列表
            text = line[2:].strip()
            p = doc.add_paragraph(style="List Bullet")
            p.add_run(text)
        elif line.startswith("**") and line.endswith("**"):
            # 粗体行
            text = line.strip("*")
            p = doc.add_paragraph()
            run = p.add_run(text)
            run.bold = True
        else:
            # 普通段落（处理内联粗体）
            p = doc.add_paragraph()
            _add_inline_text(p, line)

        i += 1

    # 处理末尾的表格
    if in_table and table_headers:
        _add_table_to_doc(doc, table_headers, table_rows)

    doc.save(docx_path)
    logger.info(f"Markdown 已转换为 DOCX: {docx_path}")
    return docx_path


def _add_heading(doc, text: str, level: int):
    """添加标题"""
    heading = doc.add_heading(text, level=level)
    for run in heading.runs:
        run.font.name = "Microsoft YaHei"


def _add_inline_text(p, text: str):
    """添加带内联格式的文本（支持 **粗体**）"""
    parts = text.split("**")
    for idx, part in enumerate(parts):
        if not part:
            continue
        run = p.add_run(part)
        if idx % 2 == 1:
            run.bold = True


def _add_table_to_doc(doc, headers: list, rows: list):
    """添加表格"""
    if not rows:
        return

    table = doc.add_table(rows=1 + len(rows), cols=len(headers))
    table.style = "Light Grid Accent 1"

    # 表头
    for j, h in enumerate(headers):
        cell = table.rows[0].cells[j]
        cell.text = h
        for p in cell.paragraphs:
            for run in p.runs:
                run.bold = True
                run.font.size = Pt(9)

    # 数据行
    for i, row in enumerate(rows):
        for j, cell_text in enumerate(row):
            if j < len(headers):
                cell = table.rows[i + 1].cells[j]
                cell.text = cell_text
                for p in cell.paragraphs:
                    for run in p.runs:
                        run.font.size = Pt(9)

    doc.add_paragraph("")  # 表后空行


# ==================== API 路由 ====================


@router.post(
    "/executions/{execution_id}/upload-to-erp",
    response_model=UploadToErpResponse,
    summary="上传安全测试报告到ERP",
)
async def upload_report_to_erp(
    execution_id: str,
    request: UploadToErpRequest,
    db: AsyncSession = Depends(get_db),
):
    """
    将安全测试报告上传到 ERP 系统

    - 自动生成安全测试报告（Markdown）
    - 转换为 DOCX 格式
    - 上传到 ERP 测试单
    - 可选：匹配抄送人姓名、创建人姓名
    """
    try:
        # 1. 生成报告并转换为 DOCX
        docx_path = await _generate_and_convert_report(execution_id, db)
    except ValueError as e:
        raise HTTPException(status_code=500, detail=str(e))

    # 2. 处理抄送人
    copyuser_ids = []
    erp_service = get_erp_service()
    if request.copyuser_names:
        stuff_list = erp_service.get_stuff_list()
        if stuff_list:
            copyuser_ids = erp_service.match_names_to_ids(request.copyuser_names, stuff_list)

    # 3. 处理创建人
    createuser_id = None
    if request.createuser_name:
        stuff_list = erp_service.get_stuff_list()
        if stuff_list:
            matched = erp_service.match_names_to_ids([request.createuser_name], stuff_list)
            if matched:
                createuser_id = matched[0]

    # 4. 上传到 ERP
    success = erp_service.upload_report_to_erp(
        file_path=docx_path,
        developtesting_id=request.developtesting_id,
        copyuser_list=copyuser_ids,
        createuser_id=createuser_id,
    )

    if success:
        return UploadToErpResponse(success=True, message="报告上传ERP成功")
    else:
        return UploadToErpResponse(success=False, message="报告上传ERP失败，请查看日志")


@router.post(
    "/executions/{execution_id}/upload-to-project",
    response_model=UploadFileToErpResponse,
    summary="上传安全报告到 ERP 项目资料",
)
async def upload_to_project(
    execution_id: str,
    request: UploadToProjectRequest,
    db: AsyncSession = Depends(get_db),
):
    """
    将安全测试报告上传到 ERP 项目资料（仅归档，不创建协作文档）
    """
    try:
        docx_path = await _generate_and_convert_report(execution_id, db)
    except ValueError as e:
        raise HTTPException(status_code=500, detail=str(e))

    name = request.name or os.path.basename(docx_path)
    erp_service = get_erp_service()
    file_info = erp_service.upload_file_to_project(docx_path, request.project_id, name)

    if not file_info:
        return UploadFileToErpResponse(success=False, message="项目资料上传失败，请查看日志")

    return UploadFileToErpResponse(
        success=True,
        file_id=file_info.get("id"),
        message=f"项目资料上传成功，文件ID: {file_info.get('id')}",
    )


@router.post(
    "/executions/{execution_id}/upload-to-cooperation",
    response_model=UploadFileToErpResponse,
    summary="上传安全报告到 ERP 协作文档",
)
async def upload_to_cooperation(
    execution_id: str,
    request: UploadToCooperationRequest,
    db: AsyncSession = Depends(get_db),
):
    """
    将安全测试报告上传到 ERP 协作文档（创建 OnlyOffice 在线协作文档）
    """
    try:
        docx_path = await _generate_and_convert_report(execution_id, db)
    except ValueError as e:
        raise HTTPException(status_code=500, detail=str(e))

    name = request.name or os.path.basename(docx_path)
    erp_service = get_erp_service()
    file_info = erp_service.upload_file_to_cooperation(
        docx_path, request.project_id, name, group_id=request.group_id
    )

    if not file_info:
        return UploadFileToErpResponse(success=False, message="协作文档上传失败，请查看日志")

    return UploadFileToErpResponse(
        success=True,
        file_id=file_info.get("id"),
        message=f"协作文档上传成功，文档ID: {file_info.get('id')}",
    )