#!/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
from fastapi.responses import FileResponse, HTMLResponse

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.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（两者）
    """
    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 = 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,
        )
        # 缓存分析结果到会话目录
        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")

        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"],
        )
    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():
    """
    获取当前ERP配置
    """
    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):
    """
    更新ERP配置（仅更新当前会话，不会持久化到环境变量）

    如需持久化，请设置环境变量或在系统配置页面修改。
    """
    if request.api_key is not None:
        settings.ERP_API_KEY = request.api_key
    if request.base_url is not None:
        settings.ERP_BASE_URL = request.base_url
    if request.developtesting_id is not None:
        settings.ERP_DEVELOPTESTING_ID = request.developtesting_id
    if request.type_id is not None:
        settings.ERP_TYPE_ID = request.type_id
    if request.image_url_prefix is not None:
        settings.ERP_IMAGE_URL_PREFIX = request.image_url_prefix

    logger.info("ERP配置已更新（当前会话）")

    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 "",
    )