"""IPTC IIM + XMP 메타데이터 자동 삽입 유틸리티 모듈.

PNG: PngInfo를 이용해 XMP 메타데이터 삽입
JPEG: APP13(IPTC IIM) + APP1(XMP) 마커를 JPEG 바이트에 직접 삽입
"""

from __future__ import annotations

import io
import struct
from pathlib import Path

from PIL import Image
from PIL.PngImagePlugin import PngInfo

# ─────────────────────────────────────────────────────────────────────────────
# XMP 패킷 생성
# ─────────────────────────────────────────────────────────────────────────────


def make_xmp_packet(title: str = "", keywords: list[str] | None = None) -> str:
    """XMP 패킷 XML 문자열 생성. digitalsourcetype=trainedAlgorithmicMedia 포함.

    Args:
        title: 이미지 제목
        keywords: 키워드 목록 (None이면 ["AI-generated"] 사용)

    Returns:
        UTF-8 인코딩 가능한 XMP XML 문자열
    """
    if keywords is None:
        keywords = ["AI-generated"]

    kw_items = "".join(f"      <rdf:li>{_xml_escape(kw)}</rdf:li>\n" for kw in keywords)

    return (
        '<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>\n'
        '<x:xmpmeta xmlns:x="adobe:ns:meta/">\n'
        '  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n'
        '    <rdf:Description rdf:about=""\n'
        '      xmlns:Iptc4xmpExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/"\n'
        '      xmlns:dc="http://purl.org/dc/elements/1.1/"\n'
        '      Iptc4xmpExt:DigitalSourceType="http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"\n'
        "    >\n"
        "      <dc:title>"
        "<rdf:Alt>"
        f'<rdf:li xml:lang="x-default">{_xml_escape(title)}</rdf:li>'
        "</rdf:Alt>"
        "</dc:title>\n"
        "      <dc:subject>\n"
        "        <rdf:Bag>\n"
        f"{kw_items}"
        "        </rdf:Bag>\n"
        "      </dc:subject>\n"
        "    </rdf:Description>\n"
        "  </rdf:RDF>\n"
        "</x:xmpmeta>\n"
        '<?xpacket end="w"?>'
    )


# ─────────────────────────────────────────────────────────────────────────────
# IPTC IIM 인코딩
# ─────────────────────────────────────────────────────────────────────────────


def _encode_iim_record(record: int, dataset: int, data: bytes) -> bytes:
    """단일 IPTC IIM 레코드 인코딩."""
    return b"\x1c" + struct.pack("BB", record, dataset) + struct.pack(">H", len(data)) + data


def encode_iptc_iim(title: str = "", keywords: list[str] | None = None) -> bytes:
    """IPTC IIM (Application2) 바이트 인코딩.

    - Record 2, Dataset 5: ObjectName (title)
    - Record 2, Dataset 25: Keywords (각 keyword마다 별도 레코드)

    Args:
        title: 이미지 제목 (ObjectName)
        keywords: 키워드 목록 (None이면 ["AI-generated"] 사용)

    Returns:
        IPTC IIM 바이트열
    """
    if keywords is None:
        keywords = ["AI-generated"]

    result = b""

    # Dataset 5: ObjectName (title)
    if title:
        result += _encode_iim_record(2, 5, title.encode("utf-8"))

    # Dataset 25: Keywords (각각 별도 레코드)
    for kw in keywords:
        result += _encode_iim_record(2, 25, kw.encode("utf-8"))

    return result


# ─────────────────────────────────────────────────────────────────────────────
# 태깅 메인 함수
# ─────────────────────────────────────────────────────────────────────────────


def tag_image(
    image_path: Path,
    title: str = "",
    keywords: list[str] | None = None,
) -> Path:
    """이미지에 IPTC + XMP 메타데이터 삽입. 동일 경로에 덮어씀. image_path 반환.

    PNG: Pillow PngInfo.add_itxt("XML:com.adobe.xmp", xmp_packet) 사용
    JPEG: APP13 (IPTC IIM) + APP1 (XMP) 마커를 JPEG 바이트에 직접 삽입

    Args:
        image_path: 태깅할 이미지 파일 경로
        title: 이미지 제목
        keywords: 키워드 목록 (None이면 ["AI-generated"] 사용)

    Returns:
        image_path (동일 경로)
    """
    if keywords is None:
        keywords = ["AI-generated"]

    suffix = image_path.suffix.lower()

    if suffix == ".png":
        _tag_png(image_path, title, keywords)
    elif suffix in (".jpg", ".jpeg"):
        _tag_jpeg(image_path, title, keywords)
    else:
        # 지원하지 않는 형식은 XMP만 삽입 시도 (PNG 방식)
        _tag_png(image_path, title, keywords)

    return image_path


# ─────────────────────────────────────────────────────────────────────────────
# PNG 태깅
# ─────────────────────────────────────────────────────────────────────────────


def _tag_png(image_path: Path, title: str, keywords: list[str]) -> None:
    """PNG 이미지에 XMP 메타데이터 삽입."""
    xmp_str = make_xmp_packet(title, keywords)

    img = Image.open(str(image_path))
    pnginfo = PngInfo()
    pnginfo.add_itxt("XML:com.adobe.xmp", xmp_str, "")
    if keywords:
        pnginfo.add_text("Keywords", ", ".join(keywords))
    img.save(str(image_path), pnginfo=pnginfo)


# ─────────────────────────────────────────────────────────────────────────────
# JPEG 태깅
# ─────────────────────────────────────────────────────────────────────────────


def _build_app13_block(iptc_data: bytes) -> bytes:
    """Photoshop APP13 블록 구성.

    구조:
      APP13 marker (2) + length (2) + header + 8BIM block
    """
    header = b"Photoshop 3.0\x00"
    resource_id = b"\x04\x04"
    pascal_string = b"\x00\x00"
    iptc_size = struct.pack(">I", len(iptc_data))

    bim_block = b"8BIM" + resource_id + pascal_string + iptc_size + iptc_data
    # 홀수 길이면 패딩
    if len(iptc_data) % 2 != 0:
        bim_block += b"\x00"

    payload = header + bim_block
    # APP13 marker + length (length에는 length 필드 자체의 2바이트 포함)
    length = struct.pack(">H", len(payload) + 2)
    return b"\xff\xed" + length + payload


def _build_app1_xmp_block(xmp_str: str) -> bytes:
    """XMP APP1 블록 구성.

    구조:
      APP1 marker (2) + length (2) + XMP namespace header + xmp data
    """
    ns_header = b"http://ns.adobe.com/xap/1.0/\x00"
    xmp_bytes = xmp_str.encode("utf-8")

    payload = ns_header + xmp_bytes
    length = struct.pack(">H", len(payload) + 2)
    return b"\xff\xe1" + length + payload


def _tag_jpeg(image_path: Path, title: str, keywords: list[str]) -> None:
    """JPEG 이미지에 APP13(IPTC) + APP1(XMP) 마커 삽입.

    최종 구조: SOI → APP13(IPTC) → APP1(XMP) → 원본 SOI 이후 나머지 내용
    """
    xmp_str = make_xmp_packet(title, keywords)
    iptc_data = encode_iptc_iim(title, keywords)

    # 원본 JPEG 읽기
    jpeg_bytes = image_path.read_bytes()

    # SOI 마커 확인
    if not jpeg_bytes.startswith(b"\xff\xd8"):
        raise ValueError(f"유효하지 않은 JPEG 파일: {image_path}")

    soi = jpeg_bytes[:2]
    rest = jpeg_bytes[2:]

    # APP13 (IPTC) 블록 생성
    app13_block = _build_app13_block(iptc_data)

    # APP1 (XMP) 블록 생성
    app1_xmp_block = _build_app1_xmp_block(xmp_str)

    # 최종 JPEG 구성: SOI + APP13 + APP1_XMP + 나머지
    new_jpeg = soi + app13_block + app1_xmp_block + rest

    image_path.write_bytes(new_jpeg)


# ─────────────────────────────────────────────────────────────────────────────
# 헬퍼
# ─────────────────────────────────────────────────────────────────────────────


def _xml_escape(text: str) -> str:
    """XML 특수문자 이스케이프."""
    return (
        text.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace('"', "&quot;")
        .replace("'", "&apos;")
    )
