#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：functional_report.py
模块描述：功能测试报告 API 路由，提供上传Excel、生成报告、下载预览、ERP上传等接口

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

import json
import logging
import os
from pathlib import Path
from typing import Optional

from fastapi import APIRouter, HTTPException, Query, UploadFile, File, Form, Depends
from fastapi.responses import FileResponse, HTMLResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select

from app.database import get_db
from app.schemas.functional_report import (
    UploadResponse,
    GenerateRequest,
    GenerateResponse,
    UploadToErpRequest,
    UploadToErpResponse,
    UploadToProjectRequest,
    UploadToCooperationRequest,
    UploadFileToErpResponse,
    ErpConfigResponse,
    ErpConfigUpdateRequest,
    ReportInfoResponse,
)
from app.services.functional_report_service import FunctionalReportService
from app.services.erp_service import ErpService
from app.models.erp_config import ErpConfig
from app.config import settings

logger = logging.getLogger(__name__)

router = APIRouter()

# 报告存储基础目录
REPORT_BASE_DIR = str(Path(settings.REPORT_DIR) / "functional")


def get_report_service() -> FunctionalReportService:
    """获取功能测试报告服务实例"""
    return FunctionalReportService(REPORT_BASE_DIR)


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,
        temp_dir=REPORT_BASE_DIR,
    )


# ==================== 上传Excel ====================


@router.post("/upload", response_model=UploadResponse, summary="上传测试用例和BUG列表Excel")
async def upload_files(
    testcase_file: UploadFile = File(..., description="测试用例Excel文件"),
    buglist_file: UploadFile = File(..., description="BUG列表Excel文件"),
    session_id: str = Form("", description="会话ID（可选，为空则自动生成）"),
):
    """
    上传测试用例Excel和BUG列表Excel文件

    - 支持 .xlsx 格式
    - 文件大小限制 10MB
    - 返回 session_id 用于后续生成报告
    """
    # 检查文件扩展名
    for f in [testcase_file, buglist_file]:
        if not f.filename.endswith((".xlsx", ".xls")):
            raise HTTPException(status_code=400, detail=f"不支持的文件格式: {f.filename}，请上传 .xlsx 文件")

    # 检查文件大小（10MB）
    MAX_SIZE = 10 * 1024 * 1024
    for f in [testcase_file, buglist_file]:
        content = await f.read()
        if len(content) > MAX_SIZE:
            raise HTTPException(status_code=400, detail=f"文件过大: {f.filename}，最大支持 10MB")
        await f.seek(0)

    service = get_report_service()
    if not session_id:
        import uuid
        from datetime import datetime
        session_id = f"func_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}"

    # 保存文件
    testcase_content = await testcase_file.read()
    testcase_path = service.save_uploaded_file(testcase_content, testcase_file.filename, session_id)

    buglist_content = await buglist_file.read()
    buglist_path = service.save_uploaded_file(buglist_content, buglist_file.filename, session_id)

    logger.info(f"文件上传成功: session_id={session_id}")

    return UploadResponse(
        session_id=session_id,
        testcase_path=testcase_path,
        buglist_path=buglist_path,
    )


# ==================== 生成报告 ====================


@router.post("/generate", response_model=GenerateResponse, summary="生成功能测试报告")
async def generate_report(request: GenerateRequest):
    """
    基于已上传的Excel文件生成功能测试报告

    - 自动分析数据、生成图表、生成Word/Markdown报告
    - 支持格式: docx（Word）、md（Markdown）、both（两者）
    - 可选勾选 ERP 自动上传（测试单/项目资料/协作文档）
    """
    session_dir = Path(REPORT_BASE_DIR) / request.session_id
    if not session_dir.exists():
        raise HTTPException(status_code=404, detail=f"会话不存在: {request.session_id}")

    # 查找会话目录中的Excel文件
    xlsx_files = list(session_dir.glob("*.xlsx")) + list(session_dir.glob("*.xls"))
    if len(xlsx_files) < 2:
        raise HTTPException(status_code=400, detail="会话目录中缺少Excel文件，请先上传")

    # 区分测试用例文件和BUG列表文件（按文件名关键词）
    testcase_path = None
    buglist_path = None
    for f in xlsx_files:
        name_lower = f.name.lower()
        if "bug" in name_lower or "缺陷" in name_lower:
            buglist_path = str(f)
        elif "用例" in name_lower or "testcase" in name_lower or "case" in name_lower:
            testcase_path = str(f)

    # 如果没区分出来，按文件大小（BUG列表通常较小）
    if not testcase_path or not buglist_path:
        sorted_files = sorted(xlsx_files, key=lambda f: f.stat().st_size)
        buglist_path = str(sorted_files[0])
        testcase_path = str(sorted_files[1]) if len(sorted_files) > 1 else str(sorted_files[0])

    service = get_report_service()
    try:
        result = await service.analyze_and_generate(
            testcase_path=testcase_path,
            buglist_path=buglist_path,
            project_name=request.project_name,
            report_format=request.report_format,
            session_id=request.session_id,
            template_id=request.template_id,
        )
        # 缓存分析结果到会话目录
        analysis_cache = {
            "case_analysis": result["case_analysis"],
            "bug_analysis": result["bug_analysis"],
            "case_bug_link_count": result["case_bug_link_count"],
            "project_name": result["project_name"],
        }
        cache_path = session_dir / "analysis_result.json"
        cache_path.write_text(json.dumps(analysis_cache, ensure_ascii=False, indent=2), encoding="utf-8")

        # 处理 ERP 自动上传
        erp_results = []
        reports_dir = session_dir / "reports"

        if reports_dir.exists() and (request.upload_to_erp or request.upload_to_project or request.upload_to_cooperation):
            # 查找第一个 docx 报告（ERP上传需要 docx 格式）
            docx_files = list(reports_dir.glob("*.docx"))
            if docx_files:
                docx_path = str(docx_files[0])
                docx_name = docx_files[0].name

                erp_service = get_erp_service()

                # 上传到 ERP 测试单
                if request.upload_to_erp:
                    try:
                        # 处理抄送人
                        copyuser_ids = []
                        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)

                        # 处理创建人
                        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]

                        success = erp_service.upload_report_to_erp(
                            file_path=docx_path,
                            developtesting_id=request.developtesting_id,
                            copyuser_list=copyuser_ids,
                            createuser_id=createuser_id,
                        )
                        erp_results.append({
                            "type": "erp",
                            "label": "ERP测试单",
                            "success": success,
                            "message": "上传成功" if success else "上传失败，请查看日志"
                        })
                    except Exception as e:
                        logger.error(f"ERP测试单上传失败: {str(e)}")
                        erp_results.append({
                            "type": "erp", "label": "ERP测试单",
                            "success": False, "message": str(e)
                        })

                # 上传到项目资料
                if request.upload_to_project:
                    try:
                        if not request.project_id:
                            erp_results.append({
                                "type": "project", "label": "项目资料",
                                "success": False, "message": "项目ID不能为空"
                            })
                        else:
                            name = request.project_file_name or docx_name
                            file_info = erp_service.upload_file_to_project(
                                docx_path, request.project_id, name
                            )
                            if file_info:
                                erp_results.append({
                                    "type": "project", "label": "项目资料",
                                    "success": True,
                                    "message": f"上传成功（文件ID: {file_info.get('id')}）"
                                })
                            else:
                                erp_results.append({
                                    "type": "project", "label": "项目资料",
                                    "success": False, "message": "上传失败，请查看日志"
                                })
                    except Exception as e:
                        logger.error(f"项目资料上传失败: {str(e)}")
                        erp_results.append({
                            "type": "project", "label": "项目资料",
                            "success": False, "message": str(e)
                        })

                # 上传到协作文档
                if request.upload_to_cooperation:
                    try:
                        if not request.cooperation_project_id:
                            erp_results.append({
                                "type": "cooperation", "label": "协作文档",
                                "success": False, "message": "项目ID不能为空"
                            })
                        else:
                            name = request.cooperation_file_name or docx_name
                            file_info = erp_service.upload_file_to_cooperation(
                                docx_path, request.cooperation_project_id, name,
                                group_id=request.cooperation_group_id
                            )
                            if file_info:
                                erp_results.append({
                                    "type": "cooperation", "label": "协作文档",
                                    "success": True,
                                    "message": f"上传成功（文档ID: {file_info.get('id')}）"
                                })
                            else:
                                erp_results.append({
                                    "type": "cooperation", "label": "协作文档",
                                    "success": False, "message": "上传失败，请查看日志"
                                })
                    except Exception as e:
                        logger.error(f"协作文档上传失败: {str(e)}")
                        erp_results.append({
                            "type": "cooperation", "label": "协作文档",
                            "success": False, "message": str(e)
                        })

        return GenerateResponse(
            session_id=result["session_id"],
            project_name=result["project_name"],
            case_analysis=result["case_analysis"],
            bug_analysis=result["bug_analysis"],
            case_bug_link_count=result["case_bug_link_count"],
            charts=result["charts"],
            reports=result["reports"],
            erp_results=erp_results,
        )
    except Exception as e:
        logger.error(f"生成报告失败: {str(e)}")
        import traceback
        logger.error(traceback.format_exc())
        raise HTTPException(status_code=500, detail=f"生成报告失败: {str(e)}")


# ==================== 下载报告 ====================


@router.get("/download/{session_id}", summary="下载报告文件")
async def download_report(
    session_id: str,
    filename: str = Query("", description="报告文件名（为空则返回第一个报告）"),
):
    """
    下载生成的报告文件

    - 指定 filename 下载特定文件
    - 不指定 filename 则返回目录中的第一个报告文件
    """
    service = get_report_service()
    info = service.get_report_info(session_id)

    if not info["reports"]:
        raise HTTPException(status_code=404, detail="未找到报告文件")

    if filename:
        target = next((r for r in info["reports"] if r["name"] == filename), None)
        if not target:
            raise HTTPException(status_code=404, detail=f"报告文件不存在: {filename}")
        report_path = target["path"]
        report_name = filename
    else:
        report_path = info["reports"][0]["path"]
        report_name = info["reports"][0]["name"]

    # 判断媒体类型
    media_type = "application/octet-stream"
    if report_name.endswith(".docx"):
        media_type = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
    elif report_name.endswith(".md"):
        media_type = "text/markdown; charset=utf-8"

    return FileResponse(
        report_path,
        media_type=media_type,
        filename=report_name,
    )


# ==================== 预览报告 ====================


@router.get("/preview/{session_id}", summary="预览Markdown报告")
async def preview_report(session_id: str):
    """
    预览Markdown格式报告

    - 返回渲染后的HTML内容
    - 仅支持 .md 格式报告的预览
    """
    service = get_report_service()
    info = service.get_report_info(session_id)

    # 查找Markdown报告
    md_report = next((r for r in info["reports"] if r["name"].endswith(".md")), None)
    if not md_report:
        raise HTTPException(status_code=404, detail="未找到Markdown报告，请先生成Markdown格式报告")

    try:
        content = Path(md_report["path"]).read_text(encoding="utf-8")
        return HTMLResponse(content=content)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"读取报告失败: {str(e)}")


# ==================== 报告信息 ====================


@router.get("/info/{session_id}", response_model=ReportInfoResponse, summary="获取报告信息")
async def get_report_info(session_id: str):
    """
    获取会话的报告信息

    - 返回报告文件列表、图表文件列表、分析结果缓存
    """
    service = get_report_service()
    info = service.get_report_info(session_id)
    return ReportInfoResponse(
        session_id=info["session_id"],
        reports=info["reports"],
        charts=info["charts"],
        analysis=info["analysis"],
    )


# ==================== ERP上传 ====================


@router.post("/upload-to-erp", response_model=UploadToErpResponse, summary="上传报告到ERP")
async def upload_report_to_erp(request: UploadToErpRequest):
    """
    将生成的报告上传到ERP系统

    - 报告文件会先转为HTML（含图片上传）
    - 然后在ERP中创建测试报告
    - 可选：匹配抄送人姓名、创建人姓名
    """
    # 查找报告文件
    session_dir = Path(REPORT_BASE_DIR) / request.session_id
    reports_dir = session_dir / "reports"
    if not reports_dir.exists():
        raise HTTPException(status_code=404, detail="报告目录不存在")

    report_path = reports_dir / request.report_filename
    if not report_path.exists():
        raise HTTPException(status_code=404, detail=f"报告文件不存在: {request.report_filename}")

    if not report_path.suffix.lower() == ".docx":
        raise HTTPException(status_code=400, detail="ERP上传仅支持 .docx 格式报告")

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

    # 处理创建人
    createuser_id = None
    if request.createuser_name:
        erp_service = get_erp_service()
        if not stuff_list:
            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]

    # 上传到ERP
    erp_service = get_erp_service()
    success = erp_service.upload_report_to_erp(
        file_path=str(report_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("/upload-to-project", response_model=UploadFileToErpResponse, summary="上传文件到ERP项目资料")
async def upload_to_project(request: UploadToProjectRequest):
    """
    将报告文件上传到ERP项目资料（仅归档，不创建协作文档）
    """
    report_path = _resolve_report_path(request.session_id, request.report_filename)
    if report_path is None:
        raise HTTPException(status_code=404, detail=f"报告文件不存在: {request.report_filename}")

    name = request.name or os.path.basename(report_path)
    erp_service = get_erp_service()
    file_info = erp_service.upload_file_to_project(report_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("/upload-to-cooperation", response_model=UploadFileToErpResponse, summary="上传文件到ERP协作文档")
async def upload_to_cooperation(request: UploadToCooperationRequest):
    """
    将报告文件上传到ERP协作文档（创建OnlyOffice在线协作文档）
    """
    report_path = _resolve_report_path(request.session_id, request.report_filename)
    if report_path is None:
        raise HTTPException(status_code=404, detail=f"报告文件不存在: {request.report_filename}")

    name = request.name or os.path.basename(report_path)
    erp_service = get_erp_service()
    file_info = erp_service.upload_file_to_cooperation(
        report_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')}",
    )


def _resolve_report_path(session_id: str, report_filename: str) -> Optional[str]:
    """解析会话目录下的报告文件路径，不存在返回None"""
    session_dir = Path(REPORT_BASE_DIR) / session_id
    report_path = session_dir / "reports" / report_filename
    if report_path.exists() and report_path.is_file():
        return str(report_path)
    return None


# ==================== ERP配置 ====================


@router.get("/config", response_model=ErpConfigResponse, summary="获取ERP配置")
async def get_erp_config(db: AsyncSession = Depends(get_db)):
    """
    获取当前ERP配置

    优先从数据库读取，若数据库无记录则回退到环境变量默认值。
    """
    # 从数据库读取
    result = await db.execute(select(ErpConfig).where(ErpConfig.id == 1))
    config = result.scalar_one_or_none()

    if config:
        # 数据库有记录，但空字段回退到环境变量默认值（保证配置自动填充）
        return ErpConfigResponse(
            api_key=config.api_key or settings.ERP_API_KEY or "",
            base_url=config.base_url or settings.ERP_BASE_URL or "",
            developtesting_id=config.developtesting_id or settings.ERP_DEVELOPTESTING_ID or 0,
            type_id=config.type_id or settings.ERP_TYPE_ID or 2,
            image_url_prefix=config.image_url_prefix or settings.ERP_IMAGE_URL_PREFIX or "",
        )

    # 数据库无记录，回退到环境变量
    return ErpConfigResponse(
        api_key=settings.ERP_API_KEY or "",
        base_url=settings.ERP_BASE_URL or "",
        developtesting_id=settings.ERP_DEVELOPTESTING_ID or 0,
        type_id=settings.ERP_TYPE_ID or 2,
        image_url_prefix=settings.ERP_IMAGE_URL_PREFIX or "",
    )


@router.put("/config", response_model=ErpConfigResponse, summary="更新ERP配置")
async def update_erp_config(request: ErpConfigUpdateRequest, db: AsyncSession = Depends(get_db)):
    """
    更新ERP配置（持久化到数据库）

    更新后的配置会持久化到数据库，服务重启后依然有效。
    """
    # 从数据库读取或创建新记录
    result = await db.execute(select(ErpConfig).where(ErpConfig.id == 1))
    config = result.scalar_one_or_none()

    if not config:
        # 新记录：先用环境变量默认值填充，再用请求值覆盖
        config = ErpConfig(
            id=1,
            api_key=settings.ERP_API_KEY or "",
            base_url=settings.ERP_BASE_URL or "",
            developtesting_id=settings.ERP_DEVELOPTESTING_ID or 0,
            type_id=settings.ERP_TYPE_ID or 2,
            image_url_prefix=settings.ERP_IMAGE_URL_PREFIX or "",
        )
        db.add(config)

    # 更新字段
    if request.api_key is not None:
        config.api_key = request.api_key
        settings.ERP_API_KEY = request.api_key
    if request.base_url is not None:
        config.base_url = request.base_url
        settings.ERP_BASE_URL = request.base_url
    if request.developtesting_id is not None:
        config.developtesting_id = request.developtesting_id
        settings.ERP_DEVELOPTESTING_ID = request.developtesting_id
    if request.type_id is not None:
        config.type_id = request.type_id
        settings.ERP_TYPE_ID = request.type_id
    if request.image_url_prefix is not None:
        config.image_url_prefix = request.image_url_prefix
        settings.ERP_IMAGE_URL_PREFIX = request.image_url_prefix

    await db.commit()
    await db.refresh(config)

    logger.info("ERP配置已更新（持久化到数据库）")

    return ErpConfigResponse(
        api_key=config.api_key or settings.ERP_API_KEY or "",
        base_url=config.base_url or settings.ERP_BASE_URL or "",
        developtesting_id=config.developtesting_id or settings.ERP_DEVELOPTESTING_ID or 0,
        type_id=config.type_id or settings.ERP_TYPE_ID or 2,
        image_url_prefix=config.image_url_prefix or settings.ERP_IMAGE_URL_PREFIX or "",
    )