#!/usr/bin/env python3
from __future__ import annotations

import argparse
import shutil
import tempfile
import zipfile
from pathlib import Path
from urllib.parse import unquote, urlparse
from xml.etree import ElementTree as ET


PKG_NS = "http://schemas.openxmlformats.org/package/2006/relationships"
R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main"
CT_NS = "http://schemas.openxmlformats.org/package/2006/content-types"

ET.register_namespace("", PKG_NS)
ET.register_namespace("a", A_NS)
ET.register_namespace("r", R_NS)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Embed externally linked local images into a DOCX file."
    )
    parser.add_argument("input_docx", type=Path)
    parser.add_argument("output_docx", type=Path)
    return parser.parse_args()


def local_path_from_target(target: str) -> Path | None:
    parsed = urlparse(target)
    if parsed.scheme != "file":
        return None
    return Path(unquote(parsed.path))


def ensure_content_type(defaults_root: ET.Element, extension: str) -> None:
    ext = extension.lower().lstrip(".")
    existing = defaults_root.find(f"{{{CT_NS}}}Default[@Extension='{ext}']")
    if existing is not None:
        return

    content_types = {
        "png": "image/png",
        "jpg": "image/jpeg",
        "jpeg": "image/jpeg",
        "gif": "image/gif",
        "bmp": "image/bmp",
        "svg": "image/svg+xml",
    }
    content_type = content_types.get(ext)
    if not content_type:
        return

    ET.SubElement(
        defaults_root,
        f"{{{CT_NS}}}Default",
        {"Extension": ext, "ContentType": content_type},
    )


def main() -> int:
    args = parse_args()
    input_docx = args.input_docx.resolve()
    output_docx = args.output_docx.resolve()

    with tempfile.TemporaryDirectory() as tmpdir:
        workdir = Path(tmpdir)
        with zipfile.ZipFile(input_docx) as zin:
            zin.extractall(workdir)

        rels_path = workdir / "word" / "_rels" / "document.xml.rels"
        doc_path = workdir / "word" / "document.xml"
        types_path = workdir / "[Content_Types].xml"
        media_dir = workdir / "word" / "media"
        media_dir.mkdir(parents=True, exist_ok=True)

        rels_tree = ET.parse(rels_path)
        rels_root = rels_tree.getroot()
        doc_tree = ET.parse(doc_path)
        doc_root = doc_tree.getroot()
        types_tree = ET.parse(types_path)
        types_root = types_tree.getroot()

        rel_to_target: dict[str, str] = {}
        next_image_num = 1

        existing_media = sorted(media_dir.glob("image*.*"))
        if existing_media:
            next_image_num = len(existing_media) + 1

        for rel in rels_root.findall(f"{{{PKG_NS}}}Relationship"):
            rel_type = rel.attrib.get("Type", "")
            target_mode = rel.attrib.get("TargetMode")
            if not rel_type.endswith("/image") or target_mode != "External":
                continue

            target = rel.attrib.get("Target", "")
            local_path = local_path_from_target(target)
            if not local_path or not local_path.exists():
                continue

            image_name = f"image{next_image_num}{local_path.suffix.lower()}"
            next_image_num += 1
            embedded_target = f"media/{image_name}"
            shutil.copy2(local_path, media_dir / image_name)

            rel.attrib["Target"] = embedded_target
            rel.attrib.pop("TargetMode", None)
            rel_to_target[rel.attrib["Id"]] = embedded_target
            ensure_content_type(types_root, local_path.suffix)

        if not rel_to_target:
            raise SystemExit("No external local images found in DOCX relationships.")

        for blip in doc_root.findall(f".//{{{A_NS}}}blip"):
            link_key = f"{{{R_NS}}}link"
            embed_key = f"{{{R_NS}}}embed"
            rel_id = blip.attrib.get(link_key)
            if rel_id in rel_to_target:
                blip.attrib[embed_key] = rel_id
                blip.attrib.pop(link_key, None)

        rels_tree.write(rels_path, encoding="UTF-8", xml_declaration=True)
        doc_tree.write(doc_path, encoding="UTF-8", xml_declaration=True)
        types_tree.write(types_path, encoding="UTF-8", xml_declaration=True)

        if output_docx.exists():
            output_docx.unlink()

        with zipfile.ZipFile(output_docx, "w", zipfile.ZIP_DEFLATED) as zout:
            for path in sorted(workdir.rglob("*")):
                if path.is_dir():
                    continue
                zout.write(path, path.relative_to(workdir).as_posix())

    return 0


if __name__ == "__main__":
    raise SystemExit(main())
