"""异步邮件发送服务（aiosmtplib）"""
import re
import asyncio
import html
import aiosmtplib
from email.message import EmailMessage
from typing import Optional
from decimal import Decimal


# ── 错误类型 ──────────────────────────────────────────────────────────────

class SmtpSendError(Exception):
    """SMTP 发送失败，携带前端需要的结构化信息"""
    def __init__(self, detail: str, error_type: str, smtp_code: Optional[int] = None):
        super().__init__(detail)
        self.detail     = detail
        self.error_type = error_type
        self.smtp_code  = smtp_code


def _classify_smtp_error(exc: Exception) -> tuple[str, Optional[int]]:
    msg = str(exc).lower()
    code: Optional[int] = None
    m = re.search(r'\b([2-5]\d{2})\b', str(exc))
    if m:
        code = int(m.group(1))
    if isinstance(exc, aiosmtplib.SMTPAuthenticationError) or code in (535, 534, 530):
        return "auth_failed", code
    if isinstance(exc, (aiosmtplib.SMTPConnectError, ConnectionRefusedError, OSError)):
        if "timed out" in msg or "timeout" in msg:
            return "timeout", None
        return "connection_failed", None
    if isinstance(exc, aiosmtplib.SMTPTimeoutError) or "timeout" in msg or "timed out" in msg:
        return "timeout", None
    if "ssl" in msg or "tls" in msg or "certificate" in msg or "handshake" in msg:
        return "tls_error", None
    if code in (550, 551, 552, 553, 554) or "recipient" in msg or "no such user" in msg:
        return "recipient_invalid", code
    return "unknown", code


# ── 模板元数据 & 默认内容 ─────────────────────────────────────────────────

TEMPLATE_META: dict[str, dict] = {
    "welcome": {
        "name": "注册欢迎邮件",
        "description": "顾客注册成功后自动发送",
        "variables": [
            {"key": "name",                "label": "顾客姓名"},
            {"key": "email",               "label": "注册邮箱"},
            {"key": "store_name",          "label": "商店名称"},
            {"key": "customer.company",    "label": "公司名称（顾客资料字段，若租户已启用）"},
            {"key": "customer.birthday",   "label": "生日（顾客资料字段，若租户已启用）"},
            {"key": "customer.gender",     "label": "性别（顾客资料字段，若租户已启用）"},
            {"key": "customer.vat_number", "label": "税号（顾客资料字段，若租户已启用）"},
            {"key": "customer.custom_1",   "label": "自定义字段1（若租户已启用）"},
            {"key": "customer.custom_2",   "label": "自定义字段2（若租户已启用）"},
            {"key": "customer.custom_3",   "label": "自定义字段3（若租户已启用）"},
            {"key": "customer.custom_4",   "label": "自定义字段4（若租户已启用）"},
            {"key": "customer.custom_5",   "label": "自定义字段5（若租户已启用）"},
        ],
        "default_subject": "欢迎注册 {{store_name}}",
        "default_body": """\
<div style="font-family:sans-serif;max-width:520px;margin:0 auto;padding:32px;border:1px solid #e5e7eb;border-radius:12px;">
  <h2 style="color:#4f46e5;margin-top:0;">欢迎加入 {{store_name}}！</h2>
  <p>亲爱的 <strong>{{name}}</strong>，您好！</p>
  <p>您已成功注册，登录邮箱为：<strong>{{email}}</strong></p>
  <p>现在您可以浏览商品、下单购买，享受专属会员权益。</p>
  <hr style="border:none;border-top:1px solid #e5e7eb;margin:24px 0;">
  <p style="font-size:12px;color:#6b7280;">如果您没有注册此账号，请忽略此邮件。</p>
</div>""",
        "notify_list_enabled": 0,
    },
    "xero_welcome": {
        "name": "Xero 同步客户欢迎邮件",
        "description": "从 Xero 导入客户时自动发送，包含初始登录密码",
        "variables": [
            {"key": "name",       "label": "顾客姓名"},
            {"key": "email",      "label": "登录邮箱"},
            {"key": "password",   "label": "初始密码"},
            {"key": "store_name", "label": "商店名称"},
        ],
        "default_subject": "您的 {{store_name}} 账号已创建",
        "default_body": """\
<div style="font-family:sans-serif;max-width:520px;margin:0 auto;padding:32px;border:1px solid #e5e7eb;border-radius:12px;">
  <h2 style="color:#4f46e5;margin-top:0;">欢迎加入 {{store_name}}！</h2>
  <p>亲爱的 <strong>{{name}}</strong>，您好！</p>
  <p>我们已为您创建了一个账号，您可以使用以下信息登录：</p>
  <div style="background:#f3f4f6;padding:16px;border-radius:8px;margin:16px 0;">
    <p style="margin:4px 0;"><strong>邮箱：</strong>{{email}}</p>
    <p style="margin:4px 0;"><strong>初始密码：</strong><code style="background:#e5e7eb;padding:2px 8px;border-radius:4px;font-size:15px;">{{password}}</code></p>
  </div>
  <p style="color:#ef4444;font-weight:600;">请登录后尽快修改密码。</p>
  <hr style="border:none;border-top:1px solid #e5e7eb;margin:24px 0;">
  <p style="font-size:12px;color:#6b7280;">此邮件由 {{store_name}} 自动发送，请勿回复。</p>
</div>""",
        "notify_list_enabled": 0,
    },
    "password_changed": {
        "name": "密码修改通知",
        "description": "顾客修改密码后自动发送",
        "variables": [
            {"key": "name",       "label": "顾客姓名"},
            {"key": "time",       "label": "修改时间"},
            {"key": "store_name", "label": "商店名称"},
        ],
        "default_subject": "密码修改通知 - {{store_name}}",
        "default_body": """\
<div style="font-family:sans-serif;max-width:520px;margin:0 auto;padding:32px;border:1px solid #e5e7eb;border-radius:12px;">
  <h2 style="color:#f59e0b;margin-top:0;">密码已修改</h2>
  <p>亲爱的 <strong>{{name}}</strong>，您好！</p>
  <p>您的账号密码已于 <strong>{{time}}</strong> 成功修改。</p>
  <p>如果这不是您本人操作，请立即联系客服。</p>
  <hr style="border:none;border-top:1px solid #e5e7eb;margin:24px 0;">
  <p style="font-size:12px;color:#6b7280;">此邮件由 {{store_name}} 自动发送，请勿回复。</p>
</div>""",
        "notify_list_enabled": 0,
    },
    "order_created": {
        "name": "新订单确认",
        "description": "顾客下单成功后自动发送",
        "attach_invoice": 0,
        "variables": [
            {"key": "name",                  "label": "顾客姓名"},
            {"key": "order_no",              "label": "订单号"},
            {"key": "total",                 "label": "订单金额"},
            {"key": "items_table",           "label": "商品明细（HTML 表格）"},
            {"key": "shipping_address",      "label": "收货地址"},
            {"key": "recv_street",           "label": "收货街道（可按模板自由排序）"},
            {"key": "recv_district",         "label": "收货区/郊区（可按模板自由排序）"},
            {"key": "recv_city",             "label": "收货城市（可按模板自由排序）"},
            {"key": "recv_state",            "label": "收货州/省（可按模板自由排序）"},
            {"key": "recv_postcode",         "label": "收货邮编（可按模板自由排序）"},
            {"key": "recv_country",          "label": "收货国家（可按模板自由排序）"},
            {"key": "recv_email",            "label": "收货邮箱"},
            {"key": "store_name",            "label": "商店名称"},
            {"key": "subtotal",              "label": "商品小计（含货币符号）"},
            {"key": "tax_total",             "label": "税额数值（含货币符号，无税则为空）"},
            {"key": "shipping_total",        "label": "运费数值（含货币符号，无运费则为空）"},
            {"key": "tax_line",              "label": "税额行 HTML（有税才渲染，无税为空，推荐用此替代 tax_total）"},
            {"key": "shipping_line",         "label": "运费行 HTML（有运费才渲染，无运费为空，推荐用此替代 shipping_total）"},
            {"key": "prices_include_tax",    "label": "价格是否含税（含税为 '1'，不含税为空，可用于模板条件显示）"},
            {"key": "note",                  "label": "顾客备注（无则为空）"},
            {"key": "purchase_order_number", "label": "采购单号 PO Number（无则为空）"},
            {"key": "pay_method",            "label": "支付方式"},
            {"key": "coupon_code",           "label": "优惠券代码（无则为空）"},
            {"key": "coupon_label",          "label": "优惠券描述（无则为空）"},
            {"key": "delivery_type",         "label": "配送类型（数字，无则为空）"},
            {"key": "points_used",           "label": "使用积分数量"},
            {"key": "member_discount",       "label": "会员折扣金额"},
            {"key": "customer.company",      "label": "公司名称（顾客资料字段，若租户已启用）"},
            {"key": "customer.birthday",     "label": "生日（顾客资料字段，若租户已启用）"},
            {"key": "customer.gender",       "label": "性别（顾客资料字段，若租户已启用）"},
            {"key": "customer.vat_number",   "label": "税号（顾客资料字段，若租户已启用）"},
            {"key": "customer.custom_1",     "label": "自定义字段1（若租户已启用）"},
            {"key": "customer.custom_2",     "label": "自定义字段2（若租户已启用）"},
            {"key": "customer.custom_3",     "label": "自定义字段3（若租户已启用）"},
            {"key": "customer.custom_4",     "label": "自定义字段4（若租户已启用）"},
            {"key": "customer.custom_5",     "label": "自定义字段5（若租户已启用）"},
            {"key": "billing.company",       "label": "账单公司名称（若租户已启用账单字段）"},
            {"key": "billing.line1",         "label": "账单地址行1（若租户已启用账单字段）"},
            {"key": "billing.line2",         "label": "账单地址行2（若租户已启用账单字段）"},
            {"key": "billing.city",          "label": "账单城市（若租户已启用账单字段）"},
            {"key": "billing.state",         "label": "账单省/州（若租户已启用账单字段）"},
            {"key": "billing.postal_code",   "label": "账单邮编（若租户已启用账单字段）"},
            {"key": "billing.country",       "label": "账单国家（若租户已启用账单字段）"},
        ],
        "default_subject": "订单确认 {{order_no}} - {{store_name}}",
        "default_body": """\
<div style="font-family:sans-serif;max-width:560px;margin:0 auto;padding:32px;border:1px solid #e5e7eb;border-radius:12px;">
  <h2 style="color:#4f46e5;margin-top:0;">订单确认</h2>
  <p>亲爱的 <strong>{{name}}</strong>，感谢您的订购！</p>
  <p>订单号：<strong>{{order_no}}</strong></p>
  {{items_table}}
  <p style="font-size:15px;"><strong>合计：{{total}}</strong></p>
  <p style="color:#4b5563;">收货地址：{{recv_street}}<br>{{recv_district}}<br>{{recv_city}}, {{recv_state}} {{recv_postcode}}<br>{{recv_country}}</p>
  <hr style="border:none;border-top:1px solid #e5e7eb;margin:24px 0;">
  <p style="font-size:12px;color:#6b7280;">此邮件由 {{store_name}} 自动发送，请勿回复。</p>
</div>""",
        "notify_list_enabled": 0,
    },
    "order_status_changed": {
        "name": "订单状态变更",
        "description": "订单状态改变时自动发送",
        "attach_invoice": 0,
        "variables": [
            {"key": "name",         "label": "顾客姓名"},
            {"key": "order_no",     "label": "订单号"},
            {"key": "status_label", "label": "新状态（中文）"},
            {"key": "items_table",  "label": "商品明细（HTML 表格）"},
            {"key": "store_name",   "label": "商店名称"},
        ],
        "default_subject": "订单状态更新 {{order_no}}",
        "default_body": """\
<div style="font-family:sans-serif;max-width:560px;margin:0 auto;padding:32px;border:1px solid #e5e7eb;border-radius:12px;">
  <h2 style="color:#3b82f6;margin-top:0;">订单状态更新</h2>
  <p>亲爱的 <strong>{{name}}</strong>，您好！</p>
  <p>您的订单 <strong>{{order_no}}</strong> 状态已更新为：<strong style="color:#3b82f6;">{{status_label}}</strong></p>
  {{items_table}}
  <hr style="border:none;border-top:1px solid #e5e7eb;margin:24px 0;">
  <p style="font-size:12px;color:#6b7280;">此邮件由 {{store_name}} 自动发送，请勿回复。</p>
</div>""",
        "notify_list_enabled": 0,
    },
    "refund_approved": {
        "name": "退款申请通过",
        "description": "Admin 审核通过退款申请后发送给顾客",
        "variables": [
            {"key": "name",          "label": "顾客姓名"},
            {"key": "order_no",      "label": "订单号"},
            {"key": "refund_amount", "label": "退款金额"},
            {"key": "refund_type",   "label": "退款类型"},
            {"key": "store_name",    "label": "商店名称"},
        ],
        "default_subject": "退款申请已通过 {{order_no}} - {{store_name}}",
        "default_body": """\
<div style="font-family:sans-serif;max-width:520px;margin:0 auto;padding:32px;border:1px solid #e5e7eb;border-radius:12px;">
  <h2 style="color:#10b981;margin-top:0;">退款申请已通过</h2>
  <p>亲爱的 <strong>{{name}}</strong>，您好！</p>
  <p>您对订单 <strong>{{order_no}}</strong> 的{{refund_type}}申请已通过审核。</p>
  <p>退款金额：<strong style="color:#10b981;">¥{{refund_amount}}</strong></p>
  <p>我们将尽快为您处理，请耐心等待。</p>
  <hr style="border:none;border-top:1px solid #e5e7eb;margin:24px 0;">
  <p style="font-size:12px;color:#6b7280;">此邮件由 {{store_name}} 自动发送，请勿回复。</p>
</div>""",
        "notify_list_enabled": 0,
    },
    "refund_rejected": {
        "name": "退款申请被拒绝",
        "description": "Admin 拒绝退款申请后发送给顾客",
        "variables": [
            {"key": "name",          "label": "顾客姓名"},
            {"key": "order_no",      "label": "订单号"},
            {"key": "refund_amount", "label": "退款金额"},
            {"key": "admin_note",    "label": "拒绝原因"},
            {"key": "store_name",    "label": "商店名称"},
        ],
        "default_subject": "退款申请未通过 {{order_no}} - {{store_name}}",
        "default_body": """\
<div style="font-family:sans-serif;max-width:520px;margin:0 auto;padding:32px;border:1px solid #e5e7eb;border-radius:12px;">
  <h2 style="color:#ef4444;margin-top:0;">退款申请未通过</h2>
  <p>亲爱的 <strong>{{name}}</strong>，您好！</p>
  <p>很抱歉，您对订单 <strong>{{order_no}}</strong> 的退款申请未通过审核。</p>
  <p>退款金额：¥{{refund_amount}}</p>
  <p>原因：<em>{{admin_note}}</em></p>
  <p>如有疑问，请联系客服。</p>
  <hr style="border:none;border-top:1px solid #e5e7eb;margin:24px 0;">
  <p style="font-size:12px;color:#6b7280;">此邮件由 {{store_name}} 自动发送，请勿回复。</p>
</div>""",
        "notify_list_enabled": 0,
    },
    "refund_completed": {
        "name": "退款已完成",
        "description": "退款处理完成后发送给顾客",
        "variables": [
            {"key": "name",          "label": "顾客姓名"},
            {"key": "order_no",      "label": "订单号"},
            {"key": "refund_amount", "label": "退款金额"},
            {"key": "store_name",    "label": "商店名称"},
        ],
        "default_subject": "退款已完成 {{order_no}} - {{store_name}}",
        "default_body": """\
<div style="font-family:sans-serif;max-width:520px;margin:0 auto;padding:32px;border:1px solid #e5e7eb;border-radius:12px;">
  <h2 style="color:#4f46e5;margin-top:0;">退款已完成</h2>
  <p>亲爱的 <strong>{{name}}</strong>，您好！</p>
  <p>您对订单 <strong>{{order_no}}</strong> 的退款已处理完成。</p>
  <p>退款金额：<strong style="color:#4f46e5;">¥{{refund_amount}}</strong></p>
  <p>退款将按原支付方式退回，请注意查收。</p>
  <hr style="border:none;border-top:1px solid #e5e7eb;margin:24px 0;">
  <p style="font-size:12px;color:#6b7280;">此邮件由 {{store_name}} 自动发送，请勿回复。</p>
</div>""",
        "notify_list_enabled": 0,
    },
    "contact_form_notification": {
        "name": "表单提交管理员通知",
        "description": "有访客提交联系表单时，通知管理员",
        "variables": [
            {"key": "form_title",      "label": "表单标题"},
            {"key": "submission_id",   "label": "提交编号"},
            {"key": "submitter_name",  "label": "提交者姓名"},
            {"key": "submitter_email", "label": "提交者邮箱"},
            {"key": "submitted_at",    "label": "提交时间"},
            {"key": "site_name",       "label": "商店名称"},
        ],
        "default_subject": "【{{site_name}}】新表单提交 #{{submission_id}} - {{form_title}}",
        "default_body": """\
<div style="font-family:sans-serif;max-width:560px;margin:0 auto;padding:32px;border:1px solid #e5e7eb;border-radius:12px;">
  <h2 style="color:#4f46e5;margin-top:0;">新表单提交</h2>
  <p>表单「<strong>{{form_title}}</strong>」收到新提交（#{{submission_id}}）。</p>
  <p>提交者：<strong>{{submitter_name}}</strong>（{{submitter_email}}）</p>
  <p>提交时间：{{submitted_at}}</p>
  <hr style="border:none;border-top:1px solid #e5e7eb;margin:24px 0;">
  <p style="font-size:12px;color:#6b7280;">请登录后台查看详情并回复。</p>
</div>""",
        "notify_list_enabled": 0,
    },
    "contact_form_confirm": {
        "name": "表单提交访客确认",
        "description": "访客提交表单后，自动发送确认邮件给访客",
        "variables": [
            {"key": "form_title",    "label": "表单标题"},
            {"key": "submission_id", "label": "提交编号"},
            {"key": "submitted_at",  "label": "提交时间"},
            {"key": "site_name",     "label": "商店名称"},
        ],
        "default_subject": "感谢您的提交 - {{site_name}}",
        "default_body": """\
<div style="font-family:sans-serif;max-width:520px;margin:0 auto;padding:32px;border:1px solid #e5e7eb;border-radius:12px;">
  <h2 style="color:#4f46e5;margin-top:0;">感谢您的提交</h2>
  <p>我们已收到您通过「{{form_title}}」提交的内容（编号：#{{submission_id}}）。</p>
  <p>我们将尽快与您联系，请耐心等候。</p>
  <hr style="border:none;border-top:1px solid #e5e7eb;margin:24px 0;">
  <p style="font-size:12px;color:#6b7280;">此邮件由 {{site_name}} 自动发送，请勿回复。</p>
</div>""",
        "notify_list_enabled": 0,
    },
    "contact_form_reply": {
        "name": "管理员回复通知",
        "description": "管理员在后台回复表单提交后，发送邮件给访客",
        "variables": [
            {"key": "form_title",    "label": "表单标题"},
            {"key": "submission_id", "label": "提交编号"},
            {"key": "reply_body",    "label": "回复内容"},
            {"key": "site_name",     "label": "商店名称"},
        ],
        "default_subject": "您的咨询已获回复 - {{site_name}}",
        "default_body": """\
<div style="font-family:sans-serif;max-width:520px;margin:0 auto;padding:32px;border:1px solid #e5e7eb;border-radius:12px;">
  <h2 style="color:#4f46e5;margin-top:0;">您的咨询已获回复</h2>
  <p>您在「{{form_title}}」中的提交（#{{submission_id}}）已获得回复：</p>
  <div style="background:#f3f4f6;padding:16px;border-radius:8px;margin:16px 0;white-space:pre-wrap;">{{reply_body}}</div>
  <hr style="border:none;border-top:1px solid #e5e7eb;margin:24px 0;">
  <p style="font-size:12px;color:#6b7280;">此邮件由 {{site_name}} 自动发送，请勿回复。</p>
</div>""",
        "notify_list_enabled": 0,
    },
}


async def _get_tenant_default_locale(db, tenant_id: int) -> str:
    """从多语言插件配置读取租户默认语言，未配置则返回 'zh'"""
    from sqlalchemy import text as sa_text
    try:
        r = await db.execute(
            sa_text(
                "SELECT config FROM plugin_configs "
                "WHERE tenant_id=:tid AND plugin_name='i18n_multilang' LIMIT 1"
            ),
            {"tid": tenant_id},
        )
        row = r.fetchone()
        if row and row[0]:
            import json
            cfg = row[0] if isinstance(row[0], dict) else json.loads(row[0])
            return cfg.get("default_locale", "zh")
    except Exception:
        pass
    return "zh"


async def get_email_template(
    db,
    tenant_id: int,
    template_key: str,
    customer_language: str | None,
    default_locale: str = "zh",
):
    """按优先级查找邮件模板：客户语言 → 租户默认语言 → 另一种语言 → None（使用内置默认）"""
    from sqlalchemy import select as sa_select
    from app.core.models.email_template import EmailTemplate

    languages_to_try = []
    if customer_language and customer_language in ("zh", "en"):
        languages_to_try.append(customer_language)
    if default_locale not in languages_to_try:
        languages_to_try.append(default_locale)
    # 兜底：尝试另一种语言，避免因 default_locale 配置与实际模板语言不一致而回落到内置默认
    for fallback in ("en", "zh"):
        if fallback not in languages_to_try:
            languages_to_try.append(fallback)

    for lang in languages_to_try:
        result = await db.execute(
            sa_select(EmailTemplate).where(
                EmailTemplate.tenant_id == tenant_id,
                EmailTemplate.template_key == template_key,
                EmailTemplate.language == lang,
                EmailTemplate.is_enabled == 1,
            )
        )
        tpl = result.scalar_one_or_none()
        if tpl:
            return tpl
    return None


_ORDER_STATUS_LABELS = {
    "pending":   "待支付",
    "paid":      "已支付",
    "shipped":   "已发货",
    "completed": "已完成",
    "cancelled": "已取消",
}


# ── 渲染工具 ──────────────────────────────────────────────────────────────

def render_tpl(text: str, variables: dict) -> str:
    """将 {{var}} 占位符替换为实际值"""
    for key, val in variables.items():
        text = text.replace("{{" + key + "}}", str(val) if val is not None else "")
    return text


_ITEMS_TABLE_HEADERS = {
    "zh": ("商品", "SKU", "数量", "单价"),
    "en": ("Product", "SKU", "Qty", "Unit Price"),
}


def build_items_table(
    items: list[dict],
    locale: str = "zh",
    adjustments: list[dict] | None = None,
    currency_symbol: str = "",
) -> str:
    """生成商品明细 HTML 表格（用于 order_created 模板中的 {{items_table}}）

    ``adjustments``（可选）：高级订单编辑器写入 ``Order.extra_attributes['adjustments']``
    的条目列表，每项含 ``kind``/``label``/``amount``。渲染为表格中额外的行，
    让收件人看到为何最终金额与商品小计不同。

    ``currency_symbol``：高级编辑的调整金额前缀（项目多币种：¥ / $ / NZ$ 等）。
    默认空串——不再硬编码 ``$``；调用方不传则不显示符号。
    ``adjustments_table`` 调用方从租户默认货币读取后传入。
    """
    h_product, h_sku, h_qty, h_price = _ITEMS_TABLE_HEADERS.get(locale, _ITEMS_TABLE_HEADERS["zh"])
    rows = "".join(
        f"<tr>"
        f"<td style='padding:8px 6px;border-bottom:1px solid #f0f0f0;'>{item.get('name','')}</td>"
        f"<td style='padding:8px 6px;border-bottom:1px solid #f0f0f0;color:#888;'>{item.get('variant','')}</td>"
        f"<td style='padding:8px 6px;border-bottom:1px solid #f0f0f0;text-align:center;'>×{item.get('quantity',1)}</td>"
        f"<td style='padding:8px 6px;border-bottom:1px solid #f0f0f0;text-align:right;'>{item.get('price','')}</td>"
        f"</tr>"
        for item in items
    )
    adj_rows = ""
    if adjustments:
        adj_rows = "".join(
            f"<tr>"
            f"<td style='padding:8px 6px;border-bottom:1px solid #f0f0f0;color:#555;' colspan='3'>"
            f"{html.escape(adj.get('label',''))}</td>"
            f"<td style='padding:8px 6px;border-bottom:1px solid #f0f0f0;text-align:right;color:#555;'>"
            f"{currency_symbol}{adj.get('amount','')}</td>"
            f"</tr>"
            for adj in adjustments
        )
    return (
        "<table style='width:100%;border-collapse:collapse;margin:16px 0;font-size:13px;'>"
        f"<thead><tr style='background:#f8f8fc;'>"
        f"<th style='padding:8px 6px;text-align:left;font-size:12px;color:#555;font-weight:600;'>{h_product}</th>"
        f"<th style='padding:8px 6px;text-align:left;font-size:12px;color:#555;font-weight:600;'>{h_sku}</th>"
        f"<th style='padding:8px 6px;text-align:center;font-size:12px;color:#555;font-weight:600;'>{h_qty}</th>"
        f"<th style='padding:8px 6px;text-align:right;font-size:12px;color:#555;font-weight:600;'>{h_price}</th>"
        "</tr></thead>"
        f"<tbody>{rows}{adj_rows}</tbody>"
        "</table>"
    )


# ── 核心发送函数 ──────────────────────────────────────────────────────────

async def send_email(
    to: str,
    subject: str,
    html_body: str,
    text_body: Optional[str] = None,
    smtp_config: Optional[dict] = None,
    attachments: Optional[list] = None,
) -> bool:
    """
    异步发送邮件，失败返回 False 不抛异常。
    attachments: list of (filename, content_bytes, mime_type)
    SMTP disabled 或配置不完整时静默跳过。
    """
    if not smtp_config or not smtp_config.get("enabled"):
        print(f"[Email] SMTP disabled or not configured, skipping: {to} / {subject}")
        return False

    host       = smtp_config.get("host", "")
    port       = smtp_config.get("port", 587)
    user       = smtp_config.get("user", "")
    password   = smtp_config.get("password", "")
    from_name  = smtp_config.get("from_name", "SME Store")
    from_email = smtp_config.get("from_email", user)
    use_tls    = smtp_config.get("use_tls", True)

    if not host or not from_email:
        print(f"[Email] SMTP not fully configured, skipping: {to} / {subject}")
        return False

    msg = EmailMessage()
    msg["From"]    = f"{from_name} <{from_email}>"
    msg["To"]      = to
    msg["Subject"] = subject
    msg.set_content(text_body or html_body, subtype="plain")
    msg.add_alternative(html_body, subtype="html")

    for filename, content_bytes, mime_type in (attachments or []):
        maintype, subtype = (mime_type or "application/octet-stream").split("/", 1)
        msg.add_attachment(content_bytes, maintype=maintype, subtype=subtype, filename=filename)

    try:
        await aiosmtplib.send(
            msg,
            hostname=host,
            port=port,
            username=user,
            password=password,
            start_tls=use_tls,
        )
        print(f"[Email] Sent to {to}: {subject}")
        return True
    except Exception as e:
        print(f"[Email] Failed to send to {to}: {e}")
        return False


def _format_extra_fields_html(extra_fields: dict, field_defs: list) -> str:
    """按租户 address_custom_fields 定义把 extra_fields 格式化为 HTML 块。SaaS 通用：不hardcode字段名。"""
    if not field_defs or not extra_fields:
        return ""
    parts = []
    for f in field_defs:
        val = extra_fields.get(f.get("key", ""))
        if val is not None and str(val).strip():
            label = f.get("label") or f.get("key", "")
            parts.append(f'<div style="margin-bottom:3px"><span style="color:#666;margin-right:8px">{label}:</span>{val}</div>')
    return "".join(parts)


def _build_pdf_items_table(items: list, currency_symbol: str = "") -> str:
    """用英文表头构建 PDF 友好的商品明细表（避免 xhtml2pdf 中文字体问题）"""
    rows = ""
    for i, item in enumerate(items, 1):
        snap = item.product_snapshot or {}
        name = snap.get("name") or f"Item #{item.product_id}"
        variant = snap.get("variant_text") or snap.get("sku") or ""
        sku = snap.get("sku") or ""
        qty = float(item.quantity or 0)
        price = float(item.unit_price or 0)
        total = float(item.total_price or price * qty)
        rows += f"""<tr>
          <td style="border:1px solid #ddd;padding:5px 8px;">{name}{(' / ' + variant) if variant else ''}</td>
          <td style="border:1px solid #ddd;padding:5px 8px;">{sku}</td>
          <td style="border:1px solid #ddd;padding:5px 8px;text-align:right;">{currency_symbol}{price:.2f}</td>
          <td style="border:1px solid #ddd;padding:5px 8px;text-align:center;">{int(qty) if qty == int(qty) else qty}</td>
          <td style="border:1px solid #ddd;padding:5px 8px;text-align:right;">{currency_symbol}{total:.2f}</td>
        </tr>"""
    return f"""<table style="width:100%;border-collapse:collapse;font-size:12px;">
      <thead><tr style="background:#f5f5f5;">
        <th style="border:1px solid #ddd;padding:5px 8px;text-align:left;">Product Name</th>
        <th style="border:1px solid #ddd;padding:5px 8px;text-align:left;">SKU</th>
        <th style="border:1px solid #ddd;padding:5px 8px;text-align:right;">Price</th>
        <th style="border:1px solid #ddd;padding:5px 8px;text-align:center;">Qty</th>
        <th style="border:1px solid #ddd;padding:5px 8px;text-align:right;">Total</th>
      </tr></thead>
      <tbody>{rows}</tbody>
    </table>"""


async def _generate_invoice_pdf(db, tenant_id: int, variables: dict) -> bytes | None:
    """从数据库读发票模板，用 variables 渲染后转 PDF。依赖 xhtml2pdf。"""
    try:
        import io
        from weasyprint import HTML as WeasyHTML
        from sqlalchemy import select
        from app.core.models.print_template import PrintTemplate
        from app.core.models.tenant_settings import TenantSettings
        from app.core.models.order import Order, OrderItem
        from app.core.models.currency import Currency

        # 并发加载：打印模板、店铺设置、货币符号
        tpl_r, ts_r, sym_r = await asyncio.gather(
            db.execute(select(PrintTemplate).where(
                PrintTemplate.tenant_id == tenant_id,
                PrintTemplate.template_key == "invoice",
            )),
            db.execute(select(TenantSettings).where(TenantSettings.tenant_id == tenant_id)),
            db.execute(select(Currency.symbol).where(
                Currency.tenant_id == tenant_id, Currency.is_default == 1
            ).limit(1)),
        )
        tpl = tpl_r.scalar_one_or_none()
        ts  = ts_r.scalar_one_or_none()
        currency_symbol = sym_r.scalar_one_or_none() or ""

        # 从 DB 加载订单明细，构建英文表头 items_table（避免 xhtml2pdf 中文字体问题）
        order_no = variables.get("order_no", "")
        items_table_html = ""
        order_created_at = ""
        recv_phone = ""
        recv_email = ""
        recv_street = ""
        recv_district = ""
        recv_city = ""
        recv_state = ""
        recv_postcode = ""
        recv_country = ""
        recv_name = variables.get("name", "")
        _extra_fields: dict = {}
        purchase_order_number = variables.get("purchase_order_number", "")

        if order_no:
            ord_r = await db.execute(
                select(Order).where(Order.tenant_id == tenant_id, Order.order_no == order_no)
            )
            order = ord_r.scalar_one_or_none()
            if order:
                order_created_at = order.created_at.strftime("%Y-%m-%d") if order.created_at else ""
                addr = order.shipping_address or {}
                if isinstance(addr, dict):
                    recv_name = addr.get("name", "") or recv_name
                    recv_phone = addr.get("phone", "")
                    recv_email = addr.get("email", "")
                    recv_street = addr.get("address", "")
                    recv_district = addr.get("district", "")
                    recv_city = addr.get("city", "")
                    recv_state = addr.get("province", "")
                    recv_postcode = addr.get("zip_code", "")
                    recv_country = addr.get("country", "")
                    _extra_fields = addr.get("extra_fields") or {}
                else:
                    _extra_fields = {}
                if not purchase_order_number:
                    purchase_order_number = (order.extra_attributes or {}).get("purchase_order_number", "") if order.extra_attributes else ""
                items_r = await db.execute(
                    select(OrderItem).where(OrderItem.order_id == order.id)
                )
                items = items_r.scalars().all()
                items_table_html = _build_pdf_items_table(items, currency_symbol)

        # Logo：转 base64 内嵌（本地文件优先，CDN URL 走 HTTP 下载）
        import os, base64 as _b64, mimetypes
        logo_url = ((ts.extra or {}).get("logo_url") if ts else "") or ""
        shop_logo_html = ""
        if logo_url:
            b64data = mime = None
            if logo_url.startswith("http://") or logo_url.startswith("https://"):
                # CDN / 外部 URL
                try:
                    import httpx
                    resp = httpx.get(logo_url, timeout=10, follow_redirects=True)
                    if resp.status_code == 200:
                        mime = resp.headers.get("content-type", "image/webp").split(";")[0]
                        b64data = _b64.b64encode(resp.content).decode()
                except Exception:
                    pass
            else:
                _upload_prefix = "/api/static/uploads/"
                _upload_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../static/uploads"))
                rel = logo_url[len(_upload_prefix):] if logo_url.startswith(_upload_prefix) else logo_url.lstrip("/")
                file_path = os.path.join(_upload_dir, rel)
                if os.path.exists(file_path):
                    mime = mimetypes.guess_type(file_path)[0] or "image/webp"
                    with open(file_path, "rb") as _f:
                        b64data = _b64.b64encode(_f.read()).decode()
            if b64data:
                shop_logo_html = f'<img src="data:{mime};base64,{b64data}" style="height:60px;object-fit:contain;display:block;margin:0 auto;" />'

        # email variables 里的金额已含货币符号，直接用，不再额外拼接
        tpl_vars: dict[str, str] = {
            "shop_name":             (ts.store_name    if ts else "") or "",
            "shop_address":          (ts.store_address if ts else "") or "",
            "shop_phone":            (ts.store_phone   if ts else "") or "",
            "shop_logo":             shop_logo_html,
            "recv_name":             recv_name,
            "recv_address":          recv_street,
            "recv_phone":            recv_phone,
            "recv_email":            recv_email,
            "recv_street":           recv_street,
            "recv_district":         recv_district,
            "recv_city":             recv_city,
            "recv_state":            recv_state,
            "recv_postcode":         recv_postcode,
            "recv_country":          recv_country,
            "delivery_type":         variables.get("delivery_type", ""),
            "recv_extra_fields":     _format_extra_fields_html(
                                         _extra_fields,
                                         (ts.extra or {}).get("address_custom_fields", []) if ts else [],
                                     ),
            "order_no":              order_no,
            "created_at":            order_created_at,
            "items_table":           items_table_html,
            "items_table_with_tax":  items_table_html,
            "grand_total":           variables.get("total", ""),
            "subtotal":              variables.get("subtotal", ""),
            "tax_total":             variables.get("tax_total", ""),
            "shipping_fee":          variables.get("shipping_total", ""),
            "member_discount":       variables.get("member_discount", ""),
            "pay_method":            variables.get("pay_method", ""),
            "paid_at":               "",
            "carrier":               "",
            "tracking_no":           "",
            "estimated_delivery":    "",
            "remark":                variables.get("note", ""),
            "purchase_order_number": purchase_order_number,
        }

        # 注入单独的自定义字段变量 recv_field_{key}，供模板精确取值
        _field_defs = (ts.extra or {}).get("address_custom_fields", []) if ts else []
        for _f in _field_defs:
            _fk = _f.get("key", "")
            if _fk:
                tpl_vars[f"recv_field_{_fk}"] = str(_extra_fields.get(_fk, "") or "")

        if tpl and tpl.content.strip():
            html_body = tpl.content
            for k, v in tpl_vars.items():
                html_body = html_body.replace(f"{{{{{k}}}}}", str(v) if v else "")
        else:
            # 无自定义模板时用简洁备用布局
            html_body = f"""
<div style="font-family:Arial,sans-serif;max-width:700px;margin:0 auto;padding:24px;">
  <h2 style="text-align:center;margin-bottom:4px;">INVOICE</h2>
  <hr/>
  <table style="width:100%;border-collapse:collapse;margin-bottom:16px;">
    <tr>
      <td style="width:50%;vertical-align:top;padding:8px 0;">
        <strong>{tpl_vars['shop_name']}</strong><br/>
        {tpl_vars['shop_address']}<br/>
        {tpl_vars['shop_phone']}
      </td>
      <td style="width:50%;vertical-align:top;text-align:right;padding:8px 0;">
        <strong>Invoice No:</strong> {tpl_vars['order_no']}<br/>
        <strong>Date:</strong> {tpl_vars['created_at']}<br/>
        <strong>Bill To:</strong> {tpl_vars['recv_name']}<br/>
        {tpl_vars['recv_address']}
      </td>
    </tr>
  </table>
  {items_table_html}
  <table style="width:100%;margin-top:12px;border-collapse:collapse;">
    {f"<tr><td style='text-align:right;padding:4px;border:none;'>Subtotal: {tpl_vars['subtotal']}</td></tr>" if tpl_vars['subtotal'] else ''}
    {f"<tr><td style='text-align:right;padding:4px;border:none;'>Tax: {tpl_vars['tax_total']}</td></tr>" if tpl_vars['tax_total'] else ''}
    {f"<tr><td style='text-align:right;padding:4px;border:none;'>Shipping: {tpl_vars['shipping_fee']}</td></tr>" if tpl_vars['shipping_fee'] else ''}
    <tr><td style="text-align:right;padding:6px;border-top:1px solid #ccc;font-weight:bold;">Total: {tpl_vars['grand_total']}</td></tr>
  </table>
  {f'<p style="font-style:italic;margin-top:12px;">PO No. {purchase_order_number}</p>' if purchase_order_number else ''}
</div>"""

        full_html = f"""<!DOCTYPE html>
<html><head><meta charset="utf-8">
<style>
  body {{ font-family: Arial, sans-serif; font-size: 12px; }}
  table {{ border-collapse: collapse; width: 100%; }}
  td, th {{ border: 1px solid #ccc; padding: 4px 6px; }}
  thead tr {{ background: #f5f5f5; }}
</style>
</head><body>{html_body}</body></html>"""

        pdf_bytes = WeasyHTML(string=full_html, base_url="http://localhost:8000").write_pdf()
        return pdf_bytes
    except ImportError:
        print("[Invoice PDF] weasyprint not installed; skipping PDF attachment")
        return None
    except Exception as e:
        print(f"[Invoice PDF] Error: {e}")
        return None


async def send_notification(
    db,
    tenant_id: int,
    template_key: str,
    to_email: str,
    variables: dict,
    smtp_config: dict,
    customer_language: str | None = None,
) -> bool:
    """
    从数据库加载自定义模板（不存在则用内置默认），渲染变量后发送邮件。
    语言优先级：customer_language → 租户 i18n 插件默认语言 → 内置默认模板
    若模板开启了 notify_list_enabled，则同时发送到租户的额外抄送列表。
    """
    from app.core.models.tenant_settings import TenantSettings

    default_locale = await _get_tenant_default_locale(db, tenant_id)

    # ponytail: render adjustments_table once here so callers don't have to.
    # ``adjustments`` is the raw list (kind/label/amount dicts) written to
    # Order.extra_attributes by the advanced order editor.
    adjustments = variables.get("adjustments")
    if isinstance(adjustments, list) and "adjustments_table" not in variables:
        # 货币符号从租户默认货币取（项目多币种：¥/$/NZ$/€）。空租户给空串，
        # 渲染出 ``{empty}{amount}`` —— 比硬编 ``$`` 安全，但调用方也可以显式
        # 传自己的 adjustments_table 跳过本默认渲染。
        from app.core.models.currency import Currency
        sym_r = await db.execute(
            select(Currency.symbol).where(
                Currency.tenant_id == tenant_id, Currency.is_default == 1
            ).limit(1)
        )
        currency_symbol = sym_r.scalar_one_or_none() or ""
        variables = {
            **variables,
            "adjustments_table": build_items_table(
                [], adjustments=adjustments, locale=default_locale,
                currency_symbol=currency_symbol,
            ),
        }

    tpl = await get_email_template(db, tenant_id, template_key, customer_language, default_locale)

    # 模板存在且被明确禁用 → 跳过
    if tpl is not None and not tpl.is_enabled:
        return False

    meta = TEMPLATE_META.get(template_key, {})

    if tpl is not None:
        subject = render_tpl(tpl.subject, variables)
        html    = render_tpl(tpl.body, variables)
        notify_list_enabled = bool(tpl.notify_list_enabled)
        attach_invoice      = bool(tpl.attach_invoice)
    else:
        subject = render_tpl(meta.get("default_subject", template_key), variables)
        html    = render_tpl(meta.get("default_body", ""), variables)
        notify_list_enabled = bool(meta.get("notify_list_enabled", 0))
        attach_invoice      = bool(meta.get("attach_invoice", 0))

    # 附加发票 PDF
    attachments = None
    if attach_invoice and template_key in ("order_created", "order_status_changed"):
        pdf_bytes = await _generate_invoice_pdf(db, tenant_id, variables)
        if pdf_bytes:
            order_no = variables.get("order_no", "invoice")
            attachments = [(f"invoice_{order_no}.pdf", pdf_bytes, "application/pdf")]

    result = await send_email(to=to_email, subject=subject, html_body=html, smtp_config=smtp_config, attachments=attachments)

    # 额外抄送列表
    if notify_list_enabled:
        try:
            s_result = await db.execute(
                select(TenantSettings).where(TenantSettings.tenant_id == tenant_id)
            )
            settings = s_result.scalar_one_or_none()
            if settings:
                extra = settings.extra or {}
                notify_list = extra.get("email_notify_list") or []
                for addr in notify_list:
                    if addr and addr != to_email:
                        await send_email(to=addr, subject=subject, html_body=html, smtp_config=smtp_config, attachments=attachments)
        except Exception as e:
            print(f"[Email] notify_list send error: {e}")

    return result


# ── 测试邮件（带异常抛出）────────────────────────────────────────────────

async def send_test_email(to: str, smtp_config: dict) -> str:
    """
    发送测试邮件。成功返回 smtp message-id，失败抛出 SmtpSendError。
    """
    if not smtp_config.get("enabled"):
        raise SmtpSendError(
            detail="SMTP 未启用，请在邮件设置中开启「启用邮件发送」并保存",
            error_type="smtp_not_configured",
        )

    host       = smtp_config.get("host", "")
    port       = smtp_config.get("port", 587)
    user       = smtp_config.get("user", "")
    password   = smtp_config.get("password", "")
    from_name  = smtp_config.get("from_name", "SME Store")
    from_email = smtp_config.get("from_email", "") or user
    use_tls    = smtp_config.get("use_tls", True)

    if not host or not from_email:
        raise SmtpSendError(
            detail="SMTP 配置不完整，请检查服务器地址和发件人邮箱是否已填写",
            error_type="smtp_not_configured",
        )

    msg = EmailMessage()
    msg["From"]    = f"{from_name} <{from_email}>"
    msg["To"]      = to
    msg["Subject"] = "【测试邮件】LS SMTP 配置验证"
    html = f"""
    <div style="font-family:sans-serif;max-width:480px;margin:0 auto;padding:24px;border:1px solid #e5e7eb;border-radius:8px;">
      <h2 style="color:#4f46e5;margin-top:0;">✅ SMTP 配置验证成功</h2>
      <p>这是一封来自 <strong>LS</strong> 的测试邮件。</p>
      <p>收到此邮件说明您的 SMTP 设置正确，系统将能正常发送订单确认、发货通知等邮件。</p>
      <hr style="border:none;border-top:1px solid #e5e7eb;margin:20px 0;">
      <p style="font-size:12px;color:#6b7280;">
        发件服务器：{host}:{port}<br>
        发件账号：{user}
      </p>
    </div>
    """
    msg.set_content(f"SMTP 配置验证成功。发件服务器：{host}:{port}", subtype="plain")
    msg.add_alternative(html, subtype="html")

    try:
        await aiosmtplib.send(
            msg,
            hostname=host,
            port=port,
            username=user,
            password=password,
            start_tls=use_tls,
            timeout=15,
        )
        return msg.get("Message-ID", "")
    except Exception as exc:
        error_type, smtp_code = _classify_smtp_error(exc)
        raise SmtpSendError(
            detail=str(exc),
            error_type=error_type,
            smtp_code=smtp_code,
        ) from exc


# ── SMTP 配置构建 ──────────────────────────────────────────────────────────

def build_smtp_config(settings_obj) -> dict:
    """把 TenantSettings ORM 对象转成 send_email 需要的 dict 格式"""
    if not settings_obj:
        return {}
    return {
        "host":       settings_obj.smtp_host or "",
        "port":       settings_obj.smtp_port or 587,
        "user":       settings_obj.smtp_user or "",
        "password":   settings_obj.smtp_password or "",
        "from_name":  settings_obj.smtp_from_name or "SME Store",
        "from_email": settings_obj.smtp_from_email or "",
        "use_tls":    bool(settings_obj.smtp_use_tls),
        "enabled":    bool(settings_obj.smtp_enabled),
    }
