"""将 Office 文件转换为 PDF（MS Office COM 或 LibreOffice headless 后端）。"""
from __future__ import annotations

import shutil
import subprocess
import tempfile
from pathlib import Path

from app.core.config import settings
from app.core.logger import get_logger
from app.core.response import AppError

logger = get_logger(__name__)

_WORD_FORMAT_PDF = 17  # wdFormatPDF
_WORD_FORMAT_DOC = 0   # wdFormatDocument (.doc)
_EXCEL_TYPE_PDF = 0     # xlTypePDF


def _word_to_pdf(src: Path, dst: Path) -> None:
    import pythoncom
    import win32com.client

    pythoncom.CoInitialize()
    word = None
    doc = None
    try:
        word = win32com.client.DispatchEx("Word.Application")
        word.Visible = False
        word.DisplayAlerts = 0
        doc = word.Documents.Open(str(src.resolve()), ReadOnly=True)
        doc.SaveAs(str(dst.resolve()), FileFormat=_WORD_FORMAT_PDF)
    except Exception as exc:  # noqa: BLE001
        raise AppError(f"Word 转 PDF 失败: {exc}") from exc
    finally:
        if doc is not None:
            try:
                doc.Close(False)
            except Exception:
                pass
        if word is not None:
            try:
                word.Quit()
            except Exception:
                pass
        pythoncom.CoUninitialize()


def _excel_to_pdf(src: Path, dst: Path) -> None:
    import pythoncom
    import win32com.client

    pythoncom.CoInitialize()
    excel = None
    wb = None
    try:
        excel = win32com.client.DispatchEx("Excel.Application")
        excel.Visible = False
        excel.DisplayAlerts = False
        wb = excel.Workbooks.Open(str(src.resolve()), ReadOnly=True)
        wb.ExportAsFixedFormat(_EXCEL_TYPE_PDF, str(dst.resolve()))
    except Exception as exc:  # noqa: BLE001
        raise AppError(f"Excel 转 PDF 失败: {exc}") from exc
    finally:
        if wb is not None:
            try:
                wb.Close(False)
            except Exception:
                pass
        if excel is not None:
            try:
                excel.Quit()
            except Exception:
                pass
        pythoncom.CoUninitialize()


_LIBREOFFICE_TIMEOUT = 120  # 单次转换超时秒数


def _office_to_pdf(src: Path, dst: Path, ext: str) -> None:
    """MS Office COM 后端：按扩展名分发到 Word / Excel。"""
    if ext in (".docx", ".doc"):
        _word_to_pdf(src, dst)
    elif ext in (".xlsx", ".xlsm"):
        _excel_to_pdf(src, dst)
    else:
        raise AppError(f"暂不支持转换为 PDF 的文件类型: {ext}")


def _libreoffice_to_pdf(src: Path, dst: Path) -> None:
    """LibreOffice headless 命令行转 PDF（无 MS Office 环境）。"""
    outdir = dst.parent
    profile_dir = Path(tempfile.mkdtemp(prefix="soffice_profile_"))
    env_profile = "file:///" + profile_dir.as_posix()
    cmd = [
        settings.soffice_path or "soffice",
        "--headless",
        "--convert-to", "pdf",
        "--outdir", str(outdir),
        f"-env:UserInstallation={env_profile}",
        str(src),
    ]
    try:
        subprocess.run(cmd, check=True, capture_output=True, text=True, timeout=_LIBREOFFICE_TIMEOUT)
    except subprocess.TimeoutExpired as exc:
        raise AppError(f"LibreOffice 转 PDF 超时: {exc}") from exc
    except Exception as exc:  # noqa: BLE001
        raise AppError(f"LibreOffice 转 PDF 失败: {exc}") from exc
    finally:
        shutil.rmtree(profile_dir, ignore_errors=True)

    produced = outdir / f"{src.stem}.pdf"  # soffice 固定按源文件名输出
    if not produced.exists():
        raise AppError(f"LibreOffice 转 PDF 失败，未生成文件: {produced}")
    if produced != dst:
        produced.replace(dst)


def convert_to_pdf(input_path: str | Path, output_path: str | Path | None = None) -> Path:
    """把 .docx / .xlsx 转换为 PDF，返回 PDF 路径。"""
    src = Path(input_path)
    if not src.exists():
        raise AppError(f"待转换文件不存在: {src}")

    ext = src.suffix.lower()
    if ext not in (".docx", ".doc", ".xlsx", ".xlsm"):
        raise AppError(f"暂不支持转换为 PDF 的文件类型: {ext}")

    dst = Path(output_path) if output_path else src.with_suffix(".pdf")
    dst.parent.mkdir(parents=True, exist_ok=True)

    logger.debug("转换 PDF: %s", src.name)
    mode = (settings.pdf_converter or "auto").strip().lower()
    if mode == "libreoffice":
        _libreoffice_to_pdf(src, dst)
    elif mode == "msoffice":
        _office_to_pdf(src, dst, ext)
    else:  # auto
        try:
            _office_to_pdf(src, dst, ext)
        except AppError as office_err:
            logger.warning("MS Office 转 PDF 失败，回退 LibreOffice: %s", office_err)
            _libreoffice_to_pdf(src, dst)

    if not dst.exists():
        raise AppError(f"PDF 转换失败，未生成文件: {dst}")
    logger.info("转换 PDF 完成: %s", dst.name)
    return dst


def _word_to_doc(src: Path, dst: Path) -> None:
    """Word COM 把 docx/doc 存为 .doc。"""
    import pythoncom
    import win32com.client

    pythoncom.CoInitialize()
    word = None
    doc = None
    try:
        word = win32com.client.DispatchEx("Word.Application")
        word.Visible = False
        word.DisplayAlerts = 0
        doc = word.Documents.Open(str(src.resolve()), ReadOnly=True)
        doc.SaveAs(str(dst.resolve()), FileFormat=_WORD_FORMAT_DOC)
    except Exception as exc:  # noqa: BLE001
        raise AppError(f"Word 转 doc 失败: {exc}") from exc
    finally:
        if doc is not None:
            try:
                doc.Close(False)
            except Exception:
                pass
        if word is not None:
            try:
                word.Quit()
            except Exception:
                pass
        pythoncom.CoUninitialize()


def pdf_to_docx(src: Path, dst: Path) -> Path:
    """pdf 转 docx（pdf2docx）。"""
    from pdf2docx import Converter

    dst.parent.mkdir(parents=True, exist_ok=True)
    try:
        cv = Converter(str(src))
        cv.convert(str(dst))
        cv.close()
    except Exception as exc:  # noqa: BLE001
        raise AppError(f"pdf 转 docx 失败: {exc}") from exc
    if not dst.exists():
        raise AppError(f"pdf 转 docx 失败，未生成文件: {dst}")
    return dst


def pdf_to_doc(src: Path, dst: Path) -> Path:
    """pdf 转 doc：先 pdf2docx 转 docx，再 Word COM 存为 .doc。"""
    tmp_docx = dst.with_suffix(".docx")
    try:
        pdf_to_docx(src, tmp_docx)
        _word_to_doc(tmp_docx, dst)
    finally:
        if tmp_docx.exists():
            try:
                tmp_docx.unlink()
            except OSError:
                pass
    if not dst.exists():
        raise AppError(f"pdf 转 doc 失败，未生成文件: {dst}")
    return dst


def convert_file(src: str | Path, dst: str | Path, target: str) -> Path:
    """按目标格式分发转换：target ∈ {pdf, docx, doc}。"""
    src = Path(src)
    dst = Path(dst)
    ext = src.suffix.lower()
    target = target.lower().lstrip(".")

    if target == "pdf":
        return convert_to_pdf(src, dst)
    if target == "docx" and ext == ".pdf":
        return pdf_to_docx(src, dst)
    if target == "doc" and ext == ".pdf":
        return pdf_to_doc(src, dst)
    raise AppError(f"不支持 {ext} -> {target} 的转换")
