import os
import uuid
import json
import subprocess
import shutil
import glob as globmod


SERVER_SIDE_TOOLS = {
    "word-to-pdf", "powerpoint-to-pdf", "excel-to-pdf",
    "pdf-to-word", "pdf-to-powerpoint"
}

TOOL_INPUT_EXTENSIONS = {
    "word-to-pdf": {".doc", ".docx"},
    "powerpoint-to-pdf": {".ppt", ".pptx"},
    "excel-to-pdf": {".xls", ".xlsx"},
    "pdf-to-word": {".pdf"},
    "pdf-to-powerpoint": {".pdf"},
}

TOOL_OUTPUT_FORMAT = {
    "word-to-pdf": "pdf",
    "powerpoint-to-pdf": "pdf",
    "excel-to-pdf": "pdf",
    "pdf-to-word": "docx",
    "pdf-to-powerpoint": "pptx",
}

TOOL_PDF_FILTER = {
    "word-to-pdf": "writer_pdf_Export",
    "powerpoint-to-pdf": "impress_pdf_Export",
    "excel-to-pdf": "calc_pdf_Export",
}


def _parse_url_entry(url_entry):
    if isinstance(url_entry, str):
        try:
            parsed = json.loads(url_entry)
            if isinstance(parsed, dict):
                return parsed
        except (json.JSONDecodeError, TypeError):
            return {"path": url_entry, "name": os.path.splitext(os.path.basename(url_entry))[0], "ext": os.path.splitext(url_entry)[1]}
    elif isinstance(url_entry, dict):
        return url_entry
    return None


def _find_libreoffice():
    """Return the libreoffice executable path, or raise RuntimeError if not found."""
    import shutil as _shutil
    for candidate in ("libreoffice", "soffice"):
        path = _shutil.which(candidate)
        if path:
            return path
    raise RuntimeError("LibreOffice is not installed on this server")


def _libreoffice_convert(input_path, output_dir, target_format, pdf_filter=None):
    user_profile = os.path.join("/tmp", f"lo_profile_{uuid.uuid4().hex}")
    os.makedirs(user_profile, exist_ok=True)

    convert_to_arg = target_format
    if pdf_filter:
        convert_to_arg = f"{target_format}:{pdf_filter}"

    try:
        lo_bin = _find_libreoffice()
    except RuntimeError:
        shutil.rmtree(user_profile, ignore_errors=True)
        raise

    cmd = [
        lo_bin,
        "--headless",
        "--norestore",
        "--nolockcheck",
        f"-env:UserInstallation=file://{user_profile}",
        "--convert-to", convert_to_arg,
        "--outdir", output_dir,
        input_path
    ]

    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=180,
            env={**os.environ, "HOME": "/tmp"}
        )

        converted_files = globmod.glob(os.path.join(output_dir, f"*.{target_format}"))
        if converted_files:
            return converted_files[0]
        # LibreOffice ran but produced nothing — surface stderr for debugging
        err_snippet = (result.stderr or "").strip()[-300:]
        raise RuntimeError(f"LibreOffice conversion produced no output. stderr: {err_snippet}")
    except subprocess.TimeoutExpired:
        raise RuntimeError("LibreOffice conversion timed out after 180 seconds.")
    finally:
        shutil.rmtree(user_profile, ignore_errors=True)


def _pdf_to_docx_via_pymupdf(input_path, output_dir):
    """Extract text from a PDF with PyMuPDF and write a DOCX with python-docx.
    This is a pure-Python fallback — plain text only, no images or complex layout."""
    try:
        import fitz  # PyMuPDF
    except ImportError:
        raise RuntimeError("PyMuPDF is not installed. Run: pip install PyMuPDF")
    try:
        from docx import Document
    except ImportError:
        raise RuntimeError("python-docx is not installed. Run: pip install python-docx")

    base_name = os.path.splitext(os.path.basename(input_path))[0]
    docx_out = os.path.join(output_dir, f"{base_name}.docx")

    doc_pdf = fitz.open(input_path)
    word_doc = Document()
    for page_num, page in enumerate(doc_pdf):
        if page_num > 0:
            word_doc.add_page_break()
        text = page.get_text("text")
        for line in text.splitlines():
            word_doc.add_paragraph(line)
    doc_pdf.close()
    word_doc.save(docx_out)

    if os.path.exists(docx_out) and os.path.getsize(docx_out) > 0:
        return docx_out
    raise RuntimeError("DOCX output was not created by PyMuPDF fallback.")


def _convert_pdf_to_docx(input_path, output_dir):
    import shutil as _shutil

    # 1) Try ebook-convert (Calibre)
    ebook_bin = _shutil.which("ebook-convert")
    if ebook_bin is None:
        # ebook-convert not installed — skip silently, move to next strategy
        pass
    else:
        try:
            base_name = os.path.splitext(os.path.basename(input_path))[0]
            docx_out = os.path.join(output_dir, f"{base_name}.docx")
            result = subprocess.run(
                [ebook_bin, input_path, docx_out],
                capture_output=True, text=True, timeout=300,
                env={**os.environ, "HOME": "/tmp"}
            )
            if result.returncode == 0 and os.path.exists(docx_out):
                return docx_out
        except subprocess.TimeoutExpired:
            pass  # ebook-convert timed out — fall through to next strategy

    # 2) Try LibreOffice
    try:
        return _libreoffice_convert(input_path, output_dir, "docx")
    except RuntimeError:
        pass  # LibreOffice unavailable or failed — fall through to PyMuPDF

    # 3) Pure-Python fallback via PyMuPDF + python-docx (text only, no images)
    return _pdf_to_docx_via_pymupdf(input_path, output_dir)


def _convert_single(file_object, tool_name, options, config):
    upload_dir = config.get("UPLOAD_DIR", "static/uploads")
    tool_lower = tool_name.lower()

    file_path = file_object.get("path", "")
    file_name = file_object.get("name", "")

    if not file_path:
        raise ValueError("No file path provided")

    input_path = os.path.join(upload_dir, file_path)
    if not os.path.exists(input_path):
        input_path = file_path
    if not os.path.exists(input_path):
        raise FileNotFoundError(f"File not found: {os.path.basename(file_path)}")

    output_folder = uuid.uuid4().hex
    output_dir = os.path.join(upload_dir, output_folder)
    os.makedirs(output_dir, exist_ok=True)

    target_format = TOOL_OUTPUT_FORMAT.get(tool_lower)
    if not target_format:
        raise ValueError(f"Unknown tool: {tool_name}")

    output_path = None

    if tool_lower == "pdf-to-word":
        output_path = _convert_pdf_to_docx(input_path, output_dir)

    elif tool_lower == "pdf-to-powerpoint":
        output_path = _libreoffice_convert(input_path, output_dir, "pptx")

    elif tool_lower in TOOL_PDF_FILTER:
        pdf_filter = TOOL_PDF_FILTER[tool_lower]
        output_path = _libreoffice_convert(input_path, output_dir, "pdf", pdf_filter)

    if not output_path or not os.path.exists(output_path):
        raise RuntimeError(f"Conversion failed for {os.path.basename(file_path)}")

    final_name = f"{file_name}.{target_format}"
    final_path = os.path.join(output_dir, final_name)
    if output_path != final_path:
        if os.path.exists(final_path):
            os.remove(final_path)
        os.rename(output_path, final_path)

    return final_path


def convert(urls, target_format, options, config):
    try:
        tool_lower = target_format.lower()

        if tool_lower not in SERVER_SIDE_TOOLS:
            return {"error": True, "message": f"Unsupported tool: {target_format}"}

        results = []
        errors = []

        for url_entry in urls:
            file_object = _parse_url_entry(url_entry)
            if not file_object:
                continue

            file_path = file_object.get("path", "")
            if not file_path or file_path == "empty":
                continue

            try:
                output_path = _convert_single(file_object, tool_lower, options, config)
                results.append(output_path)
            except Exception as e:
                fname = os.path.basename(file_path) if file_path else "unknown"
                errors.append(f"Failed to convert {fname}: {str(e)}")

        if not results and errors:
            return {"error": True, "message": "; ".join(errors)}

        if not results and not errors:
            return {"error": True, "message": "No files were provided for conversion."}

        return {
            "error": False,
            "results": results,
            "output_path": results[0] if results else "",
            "errors": errors if errors else None
        }

    except Exception as e:
        return {"error": True, "message": f"Conversion failed: {str(e)}"}
