from __future__ import annotations

import base64
import re
import zipfile
from io import BytesIO
from typing import Any

from backend.constants import PPTX_MIME_TYPE


def build_pptx_inline_part(
    *,
    title: str,
    slides: list[dict[str, Any]],
    filename: str | None = None,
) -> dict[str, Any]:
    normalized_title = (title or "PPTX").strip()
    normalized_slides = normalize_slides(slides)
    normalized_filename = build_pptx_filename(filename, normalized_title)
    payload = build_pptx_bytes(normalized_title, normalized_slides)

    return {
        "type": "DATA_INLINE",
        "mimeType": PPTX_MIME_TYPE,
        "dataBase64": base64.b64encode(payload).decode("ascii"),
        "filename": normalized_filename,
        "textSnippet": build_plain_preview(normalized_title, normalized_slides),
        "markdownSnippet": build_markdown_preview(normalized_title, normalized_slides),
    }


def normalize_slides(slides: list[dict[str, Any]] | None) -> list[dict[str, Any]]:
    normalized: list[dict[str, Any]] = []
    for index, slide in enumerate(slides or []):
        if not isinstance(slide, dict):
            continue

        title = slide.get("title")
        if not isinstance(title, str) or not title.strip():
            title = f"Slide {index + 1}"

        bullets = slide.get("bullets") or []
        normalized_bullets = [str(bullet).strip() for bullet in bullets if str(bullet).strip()]
        normalized.append({"title": title.strip(), "bullets": normalized_bullets})

    if not normalized:
        normalized.append({"title": "Summary", "bullets": []})

    return normalized


def build_pptx_filename(filename: str | None, title: str) -> str:
    if isinstance(filename, str) and filename.strip():
        raw_name = filename.strip()
        if not raw_name.lower().endswith(".pptx"):
            raw_name = f"{raw_name}.pptx"
        return safe_filename(raw_name)

    return f"{safe_filename(title, fallback='presentation')}.pptx"


def safe_filename(value: str, fallback: str = "filename") -> str:
    value = (value or "").strip()
    value = re.sub(r"[\s/]+", "_", value)
    value = re.sub(r"[^A-Za-z0-9._-]+", "", value)
    return value or fallback


def build_plain_preview(title: str, slides: list[dict[str, Any]]) -> str:
    lines = [title.strip() or "PPTX"]
    for slide in slides:
        lines.extend(["", slide["title"]])
        for bullet in slide.get("bullets") or []:
            lines.append(f"- {bullet}")
    return "\n".join(lines).strip()


def build_markdown_preview(title: str, slides: list[dict[str, Any]]) -> str:
    lines = [f"# {title.strip() or 'PPTX'}"]
    for slide in slides:
        lines.extend(["", f"## {slide['title']}"])
        for bullet in slide.get("bullets") or []:
            lines.append(f"- {bullet}")
    return "\n".join(lines).strip()


def build_pptx_bytes(title: str, slides: list[dict[str, Any]]) -> bytes:
    normalized_slides = normalize_slides(slides)
    slide_files: dict[str, str] = {}
    slide_rel_files: dict[str, str] = {}
    presentation_slide_ids = []
    presentation_rels = []
    content_type_overrides = [
        '<Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>',
        '<Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/>',
        '<Override PartName="/ppt/slideMasters/slideMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/>',
        '<Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>',
    ]

    for index, slide in enumerate(normalized_slides, start=1):
        slide_files[f"ppt/slides/slide{index}.xml"] = _build_slide_xml(slide["title"], slide.get("bullets") or [])
        slide_rel_files[f"ppt/slides/_rels/slide{index}.xml.rels"] = _slide_rels()
        slide_id = 255 + index
        rel_id = f"rId{index + 1}"
        presentation_slide_ids.append(f'<p:sldId id="{slide_id}" r:id="{rel_id}"/>')
        presentation_rels.append(
            f'<Relationship Id="{rel_id}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide{index}.xml"/>'
        )
        content_type_overrides.append(
            f'<Override PartName="/ppt/slides/slide{index}.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>'
        )

    files = {
        "[Content_Types].xml": _content_types(content_type_overrides),
        "_rels/.rels": _root_rels(),
        "ppt/presentation.xml": _presentation_xml(presentation_slide_ids),
        "ppt/_rels/presentation.xml.rels": _presentation_rels(presentation_rels),
        "ppt/slideMasters/slideMaster1.xml": _slide_master_xml(),
        "ppt/slideMasters/_rels/slideMaster1.xml.rels": _slide_master_rels(),
        "ppt/slideLayouts/slideLayout1.xml": _slide_layout_xml(),
        "ppt/theme/theme1.xml": _theme_xml(title),
    }
    files.update(slide_files)
    files.update(slide_rel_files)
    return _zip_bytes(files)


def _build_slide_xml(title: str, bullets: list[str]) -> str:
    bullet_runs = []
    for bullet in bullets:
        bullet_runs.append(
            '<a:p><a:pPr marL="342900" indent="-171450"><a:buFont typeface="Arial"/><a:buChar char="•"/></a:pPr>'
            f'<a:r><a:rPr lang="en-US" sz="2400"/><a:t>{_xml_escape(bullet)}</a:t></a:r></a:p>'
        )

    if not bullet_runs:
        bullet_runs.append('<a:p><a:r><a:rPr lang="en-US" sz="2400"/><a:t></a:t></a:r></a:p>')

    return f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
       xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
       xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
  <p:cSld>
    <p:bg><p:bgPr><a:solidFill><a:srgbClr val="FFFFFF"/></a:solidFill></p:bgPr></p:bg>
    <p:spTree>
      <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
      <p:grpSpPr/>
      <p:sp>
        <p:nvSpPr><p:cNvPr id="2" name="Title"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
        <p:spPr><a:xfrm><a:off x="685800" y="457200"/><a:ext cx="7772400" cy="914400"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr>
        <p:txBody><a:bodyPr wrap="square"/><a:lstStyle/><a:p><a:r><a:rPr lang="en-US" sz="3600" b="1"/><a:t>{_xml_escape(title)}</a:t></a:r></a:p></p:txBody>
      </p:sp>
      <p:sp>
        <p:nvSpPr><p:cNvPr id="3" name="Bullets"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
        <p:spPr><a:xfrm><a:off x="914400" y="1600200"/><a:ext cx="7315200" cy="4343400"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr>
        <p:txBody><a:bodyPr wrap="square"/><a:lstStyle/>{"".join(bullet_runs)}</p:txBody>
      </p:sp>
    </p:spTree>
  </p:cSld>
  <p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr>
</p:sld>
"""


def _presentation_xml(slide_ids: list[str]) -> str:
    return f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<p:presentation xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
                 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
                 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
  <p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="rId1"/></p:sldMasterIdLst>
  <p:sldIdLst>{"".join(slide_ids)}</p:sldIdLst>
  <p:sldSz cx="9144000" cy="6858000"/>
  <p:notesSz cx="6858000" cy="9144000"/>
</p:presentation>
"""


def _presentation_rels(slide_rels: list[str]) -> str:
    return f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Target="slideMasters/slideMaster1.xml"/>
  {"".join(slide_rels)}
</Relationships>
"""


def _slide_rels() -> str:
    return """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>
</Relationships>
"""


def _slide_master_rels() -> str:
    return """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="../theme/theme1.xml"/>
</Relationships>
"""


def _slide_layout_xml() -> str:
    return """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<p:sldLayout xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
             xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
             xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" type="blank" preserve="1">
  <p:cSld name="Blank Layout"><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/></p:spTree></p:cSld>
  <p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr>
</p:sldLayout>
"""


def _slide_master_xml() -> str:
    return """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<p:sldMaster xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
             xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
             xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
  <p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/></p:spTree></p:cSld>
  <p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="rId1"/></p:sldLayoutIdLst>
  <p:txStyles/>
  <p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/>
</p:sldMaster>
"""


def _theme_xml(name: str) -> str:
    return f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="{_xml_escape(name) or 'Agent Hub'}">
  <a:themeElements>
    <a:clrScheme name="Office">
      <a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>
      <a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>
      <a:dk2><a:srgbClr val="1F1F1F"/></a:dk2>
      <a:lt2><a:srgbClr val="E7E6E6"/></a:lt2>
      <a:accent1><a:srgbClr val="5B9BD5"/></a:accent1>
      <a:accent2><a:srgbClr val="ED7D31"/></a:accent2>
      <a:accent3><a:srgbClr val="A5A5A5"/></a:accent3>
      <a:accent4><a:srgbClr val="FFC000"/></a:accent4>
      <a:accent5><a:srgbClr val="4472C4"/></a:accent5>
      <a:accent6><a:srgbClr val="70AD47"/></a:accent6>
      <a:hlink><a:srgbClr val="0563C1"/></a:hlink>
      <a:folHlink><a:srgbClr val="954F72"/></a:folHlink>
    </a:clrScheme>
    <a:fontScheme name="Office">
      <a:majorFont><a:latin typeface="Arial"/><a:ea typeface="Arial"/><a:cs typeface="Arial"/></a:majorFont>
      <a:minorFont><a:latin typeface="Arial"/><a:ea typeface="Arial"/><a:cs typeface="Arial"/></a:minorFont>
    </a:fontScheme>
    <a:fmtScheme name="Office">
      <a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:fillStyleLst>
      <a:lnStyleLst><a:ln w="9525"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:ln></a:lnStyleLst>
      <a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle></a:effectStyleLst>
      <a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:bgFillStyleLst>
    </a:fmtScheme>
  </a:themeElements>
</a:theme>
"""


def _content_types(overrides: list[str]) -> str:
    return f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
  <Default Extension="xml" ContentType="application/xml"/>
  {''.join(overrides)}
</Types>
"""


def _root_rels() -> str:
    return """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/>
</Relationships>
"""


def _xml_escape(value: Any) -> str:
    text = "" if value is None else str(value)
    return (
        text.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace('"', "&quot;")
        .replace("'", "&apos;")
    )


def _zip_bytes(files: dict[str, str]) -> bytes:
    buffer = BytesIO()
    with zipfile.ZipFile(buffer, "w", compression=zipfile.ZIP_DEFLATED) as zf:
        for path, content in files.items():
            zf.writestr(path, content)
    return buffer.getvalue()
