import logging
import subprocess
from collections.abc import Iterable, Mapping
from pathlib import Path
from tempfile import NamedTemporaryFile

import nh3
import pypandoc
from django.conf import settings
from flexible_reports.adapters import django_tables2 as flexible_tables

LOGGER = logging.getLogger(__name__)


DEFAULT_ALLOWED_TAGS: Iterable[str] = (
    "table",
    "tr",
    "td",
    "th",
    "b",
    "i",
    "u",
    "sup",
    "sub",
    "h1",
    "h2",
    "h3",
    "h4",
    "em",
    "strong",
    "strike",
    "font",
)

DEFAULT_ALLOWED_ATTRIBUTES: Mapping[str, Iterable[str]] = {"td": ("colspan",)}

_DOCKER_IMAGE = getattr(settings, "HTML2DOCX_DOCKER_IMAGE", "iplweb/html2docx:latest")
_DOCKER_COMMAND: Iterable[str] = getattr(
    settings, "HTML2DOCX_DOCKER_COMMAND", ("docker",)
)

PAGEBREAK_MARKER = "---PAGEBREAK---"


class DocxConversionError(RuntimeError):
    """Raised when DOCX export fails using both pandoc and html2docx."""


def as_docx(  # noqa: PLR0913
    report,
    parent_context,
    allowed_tags: Iterable[str] | None = None,
    allowed_attributes: Mapping[str, Iterable[str]] | None = None,
):
    html = flexible_tables.as_html(report, parent_context)

    attrs = allowed_attributes or DEFAULT_ALLOWED_ATTRIBUTES
    cleaned_html = nh3.clean(
        html,
        tags=set(allowed_tags or DEFAULT_ALLOWED_TAGS),
        attributes={k: set(v) for k, v in attrs.items()},
        clean_content_tags=set(),
        link_rel=None,
    )

    output_file = NamedTemporaryFile(delete=False)

    try:
        pypandoc.convert_text(
            cleaned_html,
            "docx",
            format="html",
            outputfile=output_file.name,
        )
        output_file.seek(0)
        return output_file
    except (OSError, RuntimeError) as exc:
        LOGGER.warning(
            "Pandoc conversion failed, retrying with html2docx", exc_info=exc
        )
        try:
            _convert_using_docker_image(cleaned_html, output_file.name)
        except Exception as docker_exc:  # noqa: BLE001
            output_file.close()
            raise DocxConversionError(
                "DOCX conversion failed using both pandoc and html2docx"
            ) from docker_exc

        output_file.seek(0)
        return output_file


def _convert_using_docker_image(html: str, output_path: str) -> None:
    # Use docker to run html2docx container with stdin/stdout piping
    cmd = list(_DOCKER_COMMAND) + [
        "run",
        "--rm",
        "-i",
        _DOCKER_IMAGE,
        "-",  # Read from stdin
    ]

    try:
        process = subprocess.run(  # noqa: S603
            cmd,
            input=html.encode("utf-8"),
            check=True,
            capture_output=True,
            text=False,  # Binary mode for DOCX output
        )

        # Write the captured stdout to the output file
        with open(output_path, "wb") as output_file:
            output_file.write(process.stdout)

        if process.stderr:
            LOGGER.debug(
                "html2docx docker stderr: %s",
                process.stderr.decode("utf-8", errors="replace"),
            )

    except subprocess.CalledProcessError as exc:
        LOGGER.error(
            "html2docx docker conversion failed: %s",
            (
                exc.stderr.decode("utf-8", errors="replace")
                if exc.stderr
                else "No error output"
            ),
        )
        raise
    except FileNotFoundError:
        LOGGER.error("docker executable not found")
        raise

    # Validate output
    output_file_path = Path(output_path)
    if not output_file_path.exists() or output_file_path.stat().st_size == 0:
        raise RuntimeError("html2docx docker produced no output")


def _replace_pagebreak_in_paragraph(paragraph, marker: str) -> bool:
    """Replace marker with page break in a single paragraph.

    Returns True if replacement was made.
    """
    from docx.oxml import OxmlElement
    from docx.oxml.ns import qn

    if marker in paragraph.text:
        # Clear paragraph content
        paragraph.clear()
        # Add page break
        run = paragraph.add_run()
        br = OxmlElement("w:br")
        br.set(qn("w:type"), "page")
        run._r.append(br)
        return True
    return False


def _replace_pagebreak_markers(docx_path: str, marker: str = PAGEBREAK_MARKER) -> int:
    """Replace text markers with actual page breaks in DOCX file.

    This function post-processes a DOCX file to replace text markers with
    real Word page breaks. Works with files generated by any converter
    (pypandoc, html2docx, etc.).

    Args:
        docx_path: Path to the DOCX file to modify (in-place)
        marker: Text marker to replace with page breaks

    Returns:
        Number of replacements made
    """
    from docx import Document

    doc = Document(docx_path)
    replacements = 0

    # Check main document paragraphs
    for paragraph in doc.paragraphs:
        if _replace_pagebreak_in_paragraph(paragraph, marker):
            replacements += 1

    # Check paragraphs inside tables
    for table in doc.tables:
        for row in table.rows:
            for cell in row.cells:
                for paragraph in cell.paragraphs:
                    if _replace_pagebreak_in_paragraph(paragraph, marker):
                        replacements += 1

    if replacements > 0:
        doc.save(docx_path)
        LOGGER.info("Replaced %d pagebreak markers in DOCX", replacements)
    else:
        LOGGER.warning("No pagebreak markers found in DOCX (marker: %s)", marker)

    return replacements


def html_to_docx(html: str, process_pagebreaks: bool = True) -> bytes:
    """Convert HTML string to DOCX bytes.

    Uses pypandoc as primary converter with Docker html2docx as fallback.
    Optionally processes page break markers (---PAGEBREAK---) and replaces
    them with actual Word page breaks.

    Args:
        html: HTML content to convert
        process_pagebreaks: If True, replace PAGEBREAK_MARKER with real page breaks

    Returns:
        DOCX file content as bytes

    Raises:
        DocxConversionError: If both pypandoc and docker conversion fail
    """
    output_file = NamedTemporaryFile(delete=False, suffix=".docx")
    output_path = output_file.name
    output_file.close()

    try:
        pypandoc.convert_text(html, "docx", format="html", outputfile=output_path)
    except (OSError, RuntimeError) as exc:
        LOGGER.warning(
            "Pandoc conversion failed, retrying with html2docx", exc_info=exc
        )
        try:
            _convert_using_docker_image(html, output_path)
        except Exception as docker_exc:  # noqa: BLE001
            Path(output_path).unlink(missing_ok=True)
            raise DocxConversionError(
                "DOCX conversion failed using both pandoc and html2docx"
            ) from docker_exc

    # Post-process to replace pagebreak markers with real page breaks
    if process_pagebreaks and PAGEBREAK_MARKER in html:
        try:
            _replace_pagebreak_markers(output_path)
        except Exception as exc:  # noqa: BLE001
            LOGGER.warning("Failed to process pagebreak markers", exc_info=exc)

    try:
        return Path(output_path).read_bytes()
    finally:
        Path(output_path).unlink(missing_ok=True)
