"""
Pure-Python EML exporter for the QBR Flask application.

No pywin32, no Outlook installation required.
Uses only Python standard library.

Features:
  - X-Unsent: 1 → Outlook opens as fresh email with Send enabled
  - From is OPTIONAL — if omitted, Outlook uses default account
  - To is OPTIONAL — if omitted, user fills manually in Outlook
  - HTML minifier → strips redundant whitespace/mso styles
"""

import base64
import binascii
import os
import re
import shutil
import tempfile
from pathlib import Path
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email import encoders

from flask import jsonify, request, send_file


def _minify_html(html):
    if not html:
        return html
    html = re.sub(r'<!--(?!\[if).*?-->', '', html, flags=re.DOTALL)
    html = re.sub(r'mso-[a-z\-]+\s*:\s*[^;"]+;?\s*', '', html)
    html = re.sub(r'\s*style\s*=\s*"\s*"', '', html)
    html = re.sub(r'>\s+<', '><', html)
    html = re.sub(r'\n\s*\n', '\n', html)
    html = re.sub(r'[ \t]+', ' ', html)
    lines = [line.strip() for line in html.split('\n') if line.strip()]
    return '\n'.join(lines)


def _extract_inline_data_images(html):
    """
    Convert data:image/* base64 URLs in HTML into CID references.

    Outlook/Word is much more reliable with MIME inline images than with
    data: URLs. The browser export creates PNG data URLs for report slides;
    this function turns those images into proper MIME related parts.

    Returns:
        (updated_html, inline_images)
        inline_images = [(content_id, mime_type, bytes), ...]
    """
    if not html:
        return html, []

    pattern = re.compile(
        r'data:(image/(?:png|jpe?g|gif|webp));base64,([A-Za-z0-9+/=\s]+)',
        flags=re.IGNORECASE
    )

    inline_images = []
    cache = {}
    counter = 0

    def replace(match):
        nonlocal counter

        mime_type = match.group(1).lower()
        encoded = re.sub(r'\s+', '', match.group(2))

        try:
            image_bytes = base64.b64decode(encoded, validate=True)
        except (binascii.Error, ValueError):
            return match.group(0)

        if not image_bytes:
            return match.group(0)

        # Reuse the same CID for identical images to keep the EML smaller.
        import hashlib
        cache_key = (mime_type, hashlib.sha256(image_bytes).hexdigest())

        if cache_key in cache:
            content_id = cache[cache_key]
        else:
            counter += 1
            content_id = f'qbr-inline-{counter}@prosupport-plus'
            cache[cache_key] = content_id
            inline_images.append((content_id, mime_type, image_bytes))

        return f'cid:{content_id}'

    updated_html = pattern.sub(replace, html)
    return updated_html, inline_images


def _safe_filename(name):
    name = str(name or "ProSupport-Plus-MBR")
    name = os.path.basename(name)
    for ext in (".msg", ".eml"):
        if name.lower().endswith(ext):
            name = name[: -len(ext)]
    name = re.sub(r'[<>:"/\\|?*\x00-\x1f]+', " ", name)
    name = re.sub(r"\s+", " ", name).strip()
    if not name:
        name = "ProSupport-Plus-MBR"
    return name + ".eml"


def _decode_attachment(item):
    if not isinstance(item, dict):
        raise ValueError("Invalid attachment data.")
    original_name = str(item.get("name") or "attachment")
    original_name = os.path.basename(original_name)
    original_name = re.sub(r'[<>:"/\\|?*\x00-\x1f]+', " ", original_name).strip()
    if not original_name:
        original_name = "attachment"
    encoded = item.get("base64", "")
    if not encoded:
        raise ValueError(f"Attachment '{original_name}' has no data.")
    if isinstance(encoded, str) and encoded.startswith("data:"):
        encoded = encoded.split(",", 1)[1]
    try:
        data = base64.b64decode(encoded, validate=True)
    except (binascii.Error, ValueError, TypeError) as exc:
        raise ValueError(f"Invalid base64 data for '{original_name}'.") from exc
    return original_name, data


def register_msg_export(app):
    """Register GET and POST /download-msg endpoints."""

    @app.route("/download-msg", methods=["GET"])
    def download_msg_check():
        return jsonify(status="ok", format="eml"), 200

    @app.route("/download-msg", methods=["POST"])
    def download_msg():
        data = request.get_json(silent=True) or {}

        from_address = str(data.get("from") or "").strip()
        to_address   = str(data.get("to") or "").strip()
        subject      = str(data.get("subject") or "").strip()
        html_body    = str(data.get("html") or "")
        text_body    = str(data.get("text") or "")
        filename     = _safe_filename(data.get("filename"))

        # FROM IS OPTIONAL — NO VALIDATION!
        # If empty, Outlook uses default account

        # TO IS NOW OPTIONAL — NO VALIDATION!
        # If empty, user fills it manually in Outlook before sending

        if not subject:
            return jsonify(error="Please enter the Subject."), 400
        if not html_body:
            html_body = text_body or "ProSupport Plus Business Review"

        attachments_payload = data.get("attachments") or []
        if not isinstance(attachments_payload, list):
            return jsonify(error="Invalid attachments payload."), 400

        temp_dir = None

        try:
            html_body = _minify_html(html_body)

            # Browser-side report slides are exported as data:image/png URLs.
            # Convert them to CID inline MIME images because Outlook/Word does
            # not reliably render data: URLs inside an EML HTML body.
            html_body, inline_images = _extract_inline_data_images(html_body)

            msg = MIMEMultipart("mixed")

            if from_address:
                msg["From"] = from_address

            if to_address:
                msg["To"] = to_address

            msg["Subject"]  = subject
            msg["X-Unsent"] = "1"

            # related = HTML + its inline images
            related = MIMEMultipart("related")

            alt = MIMEMultipart("alternative")
            if text_body:
                alt.attach(MIMEText(text_body, "plain", "utf-8"))
            alt.attach(MIMEText(html_body, "html", "utf-8"))
            related.attach(alt)

            for content_id, mime_type, image_bytes in inline_images:
                main_type, _, sub_type = mime_type.partition("/")
                image_part = MIMEImage(image_bytes, _subtype=sub_type)
                image_part.add_header("Content-ID", f"<{content_id}>")
                image_part.add_header("Content-Disposition", "inline")
                related.attach(image_part)

            msg.attach(related)

            for idx, item in enumerate(attachments_payload):
                att_name, att_bytes = _decode_attachment(item)
                mime_type = str(item.get("type") or "application/octet-stream")
                main_type, _, sub_type = mime_type.partition("/")
                if not sub_type:
                    main_type, sub_type = "application", "octet-stream"
                part = MIMEBase(main_type, sub_type)
                part.set_payload(att_bytes)
                encoders.encode_base64(part)
                part.add_header("Content-Disposition", "attachment", filename=att_name)
                msg.attach(part)

            temp_dir = Path(tempfile.mkdtemp(prefix="qbr_eml_"))
            eml_path = temp_dir / filename

            with open(eml_path, "wb") as fp:
                fp.write(msg.as_bytes())

            response = send_file(
                str(eml_path),
                mimetype="message/rfc822",
                as_attachment=True,
                download_name=filename,
                max_age=0,
            )

            @response.call_on_close
            def _cleanup():
                try:
                    shutil.rmtree(temp_dir, ignore_errors=True)
                except Exception:
                    pass

            return response

        except ValueError as ve:
            return jsonify(error=str(ve)), 400

        except Exception as exc:
            if temp_dir:
                try:
                    shutil.rmtree(temp_dir, ignore_errors=True)
                except Exception:
                    pass
            return jsonify(error=f"Email export failed: {exc}"), 500