"""店铺设置 API — GET/PUT /admin/settings"""
from decimal import Decimal

import httpx
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select

from app.api.deps import get_db, get_admin_user
from app.core.cache import cache_delete
from app.core.models.user import User
from app.core.models.tenant import Tenant
from app.core.models.tenant_settings import TenantSettings
from app.core.ai_utils import AI_PROVIDER_DEFAULTS as _AI_PROVIDER_DEFAULTS, call_ai, resolve_ai_extra
from app.core.services.ai_quota import current_month_key, get_sliding_usage

router = APIRouter(prefix="/admin/settings", tags=["店铺设置"])


class TenantSettingsOut(BaseModel):
    store_name: str
    store_phone: str | None = None
    store_address: str | None = None
    store_description: str | None = None
    store_logo: str | None = None
    store_subtitle: str | None = None
    show_store_name: bool = True
    show_store_subtitle: bool = True
    default_shipping_fee: float = 10.0
    free_shipping_threshold: float = 99.0
    notify_email: str | None = None
    payment_methods: dict | None = None
    # SMTP
    smtp_host: str | None = None
    smtp_port: int = 587
    smtp_user: str | None = None
    smtp_from_name: str | None = "SME Store"
    smtp_from_email: str | None = None
    smtp_use_tls: bool = True
    smtp_enabled: bool = False
    # 额外抄送列表
    email_notify_list: list[str] = []
    # AI
    ai_enabled: bool = False
    ai_provider: str = "deepseek"
    ai_api_key: str | None = None
    ai_base_url: str | None = None
    ai_model: str | None = None
    anysearch_api_key: str | None = None
    # 地址自定义字段定义
    address_custom_fields: list = []
    customer_profile_fields: list = []
    # 全局 SEO
    site_meta_title: str | None = None
    site_meta_description: str | None = None
    site_keywords: str | None = None
    og_image: str | None = None
    favicon_url: str | None = None
    # 配送与售后政策（富文本 HTML）
    shipping_policy: str | None = None
    # AI 客服
    ai_chat_enabled: bool = False
    ai_chat_name: str = "客服"
    ai_chat_welcome: str = "您好！有什么可以帮您？"
    ai_chat_faq: str | None = None
    ai_chat_blacklist: str | None = None
    default_locale: str = "zh"
    # 英文变体（storefront bilingual）
    store_name_en: str | None = None
    store_subtitle_en: str | None = None
    site_meta_title_en: str | None = None
    site_meta_description_en: str | None = None
    site_keywords_en: str | None = None
    ai_chat_name_en: str | None = None
    ai_chat_welcome_en: str | None = None
    shipping_policy_en: str | None = None
    # 商品展示
    show_real_stock: bool = True
    # 分类筛选是否连带子分类商品（false=仅精确匹配本分类）
    category_filter_recursive: bool = False
    # 商品页服务保障展示项 [{icon, text, text_en}]
    service_badges: list = []
    # 库存
    allow_oversell_global: int = 0
    # 库存扣减状态
    inventory_deduct_statuses: list[str] = ["pending"]
    # 租户 store 域名（用于主题编辑器预览 iframe）
    store_domain: str | None = None
    aqua_homepage_layout: dict | list | str | None = None
    # 是否允许租户自行配置 AI Key（由 superadmin 控制，false 时前端隐藏自定义字段）
    ai_allow_custom: bool = True
    # AI 调用额度（由 superadmin 控制，NULL=不限额）
    ai_quota_monthly: int | None = None
    ai_quota_5h: int | None = None
    ai_quota_weekly: int | None = None
    ai_used_this_month: int = 0
    ai_used_5h: int = 0
    ai_used_weekly: int = 0
    # AI 超额自定义提示（store 客服用）
    ai_exhausted_message: str | None = None
    expiry_warning_days: int = 30

    model_config = {"from_attributes": True}


class TenantSettingsUpdate(BaseModel):
    store_name: str | None = None
    store_phone: str | None = None
    store_address: str | None = None
    store_description: str | None = None
    store_logo: str | None = None
    store_subtitle: str | None = None
    show_store_name: bool | None = None
    show_store_subtitle: bool | None = None
    default_shipping_fee: float | None = None
    free_shipping_threshold: float | None = None
    notify_email: str | None = None
    payment_methods: dict | None = None
    # SMTP
    smtp_host: str | None = None
    smtp_port: int | None = None
    smtp_user: str | None = None
    smtp_password: str | None = None
    smtp_from_name: str | None = None
    smtp_from_email: str | None = None
    smtp_use_tls: bool | None = None
    smtp_enabled: bool | None = None
    # 额外抄送列表
    email_notify_list: list[str] | None = None
    # AI
    ai_enabled: bool | None = None
    ai_provider: str | None = None
    ai_api_key: str | None = None
    ai_base_url: str | None = None
    ai_model: str | None = None
    anysearch_api_key: str | None = None
    # 地址自定义字段定义
    address_custom_fields: list | None = None
    customer_profile_fields: list | None = None
    # 全局 SEO
    site_meta_title: str | None = None
    site_meta_description: str | None = None
    site_keywords: str | None = None
    og_image: str | None = None
    favicon_url: str | None = None
    # 配送与售后政策
    shipping_policy: str | None = None
    # AI 客服
    ai_chat_enabled: bool | None = None
    ai_chat_name: str | None = None
    ai_chat_welcome: str | None = None
    ai_chat_faq: str | None = None
    ai_chat_blacklist: str | None = None
    ai_exhausted_message: str | None = None
    expiry_warning_days: int | None = None
    # CDN 存储
    cdn_provider: str | None = None
    cdn_public_url: str | None = None
    cdn_s3_bucket: str | None = None
    cdn_s3_region: str | None = None
    cdn_s3_access_key: str | None = None
    cdn_s3_secret_key: str | None = None
    cdn_s3_use_acl: str | None = None   # "true" / "false"
    cdn_r2_endpoint: str | None = None
    cdn_oss_bucket: str | None = None
    cdn_oss_endpoint: str | None = None
    cdn_oss_access_key: str | None = None
    cdn_oss_secret_key: str | None = None
    default_locale: str | None = None
    # 英文变体（storefront bilingual）
    store_name_en: str | None = None
    store_subtitle_en: str | None = None
    site_meta_title_en: str | None = None
    site_meta_description_en: str | None = None
    site_keywords_en: str | None = None
    ai_chat_name_en: str | None = None
    ai_chat_welcome_en: str | None = None
    shipping_policy_en: str | None = None
    # 商品展示
    show_real_stock: bool | None = None
    # 分类筛选是否连带子分类商品
    category_filter_recursive: bool | None = None
    # 商品页服务保障展示项 [{icon, text, text_en}]
    service_badges: list | None = None
    # 库存
    allow_oversell_global: int | None = None
    # 库存扣减状态
    inventory_deduct_statuses: list[str] | None = None
    aqua_homepage_layout: dict | list | str | None = None


class AiTestRequest(BaseModel):
    prompt: str = "你好，请用一句话介绍你自己。"
    # 前端当前表单值，若传入则覆盖 DB 里的配置，实现"保存前测试"
    ai_provider:       str | None = None
    ai_api_key:        str | None = None
    ai_base_url:       str | None = None
    ai_model:          str | None = None
    anysearch_api_key: str | None = None


def _normalize_service_badges(v) -> list:
    """校验并清洗商品页服务保障展示项。最多 6 项，字段限长，跳过空项。"""
    if not isinstance(v, list):
        raise HTTPException(status_code=422, detail="service_badges 必须是数组")
    if len(v) > 6:
        raise HTTPException(status_code=422, detail="服务保障最多 6 项")
    out: list[dict] = []
    for item in v:
        if not isinstance(item, dict):
            raise HTTPException(status_code=422, detail="service_badges 每项必须是对象")
        icon = str(item.get("icon") or "").strip()
        text = str(item.get("text") or "").strip()
        text_en = str(item.get("text_en") or "").strip()
        url = str(item.get("url") or "").strip()
        if len(icon) > 200 or len(text) > 50 or len(text_en) > 50 or len(url) > 500:
            raise HTTPException(status_code=422, detail="服务保障字段过长（图标≤200，文案≤50，链接≤500）")
        if not icon and not text and not text_en:
            continue  # 跳过完全空的项
        out.append({"icon": icon, "text": text, "text_en": text_en, "url": url})
    return out


def _display_ai_used(tenant_row) -> int:
    """只读展示用：跨月了就当 0（真正的重置落库发生在 consume_ai_quota 里，这里不写库）"""
    if not tenant_row:
        return 0
    if tenant_row.ai_quota_month != current_month_key():
        return 0
    return tenant_row.ai_used_this_month


def _settings_to_out(
    s: TenantSettings, store_domain: str | None = None, ai_allow_custom: bool = True,
    ai_quota_monthly: int | None = None, ai_used_this_month: int = 0,
    ai_quota_5h: int | None = None, ai_quota_weekly: int | None = None,
    ai_used_5h: int = 0, ai_used_weekly: int = 0,
) -> TenantSettingsOut:
    extra = s.extra or {}
    return TenantSettingsOut(
        store_name=s.store_name,
        store_phone=s.store_phone,
        store_address=s.store_address,
        store_description=s.store_description,
        store_logo=extra.get("logo_url") or None,
        store_subtitle=extra.get("store_subtitle") or None,
        show_store_name=bool(extra.get("show_store_name", True)),
        show_store_subtitle=bool(extra.get("show_store_subtitle", True)),
        default_shipping_fee=float(s.default_shipping_fee),
        free_shipping_threshold=float(s.free_shipping_threshold),
        notify_email=s.notify_email,
        payment_methods=s.payment_methods,
        smtp_host=s.smtp_host or None,
        smtp_port=s.smtp_port,
        smtp_user=s.smtp_user or None,
        smtp_from_name=s.smtp_from_name or "SME Store",
        smtp_from_email=s.smtp_from_email or None,
        smtp_use_tls=bool(s.smtp_use_tls),
        smtp_enabled=bool(s.smtp_enabled),
        ai_enabled=bool(extra.get("ai_enabled", False)),
        ai_provider=extra.get("ai_provider", "deepseek"),
        ai_api_key=extra.get("ai_api_key") or None,
        ai_base_url=extra.get("ai_base_url") or None,
        ai_model=extra.get("ai_model") or None,
        anysearch_api_key=extra.get("anysearch_api_key") or None,
        address_custom_fields=extra.get("address_custom_fields") or [],
        customer_profile_fields=extra.get("customer_profile_fields") or [],
        site_meta_title=extra.get("site_meta_title") or None,
        site_meta_description=extra.get("site_meta_description") or None,
        site_keywords=extra.get("site_keywords") or None,
        og_image=extra.get("og_image") or None,
        favicon_url=extra.get("favicon_url") or None,
        shipping_policy=extra.get("shipping_policy") or None,
        ai_chat_enabled=bool(extra.get("ai_chat_enabled", False)),
        ai_chat_name=extra.get("ai_chat_name") or "客服",
        ai_chat_welcome=extra.get("ai_chat_welcome") or "您好！有什么可以帮您？",
        ai_chat_faq=extra.get("ai_chat_faq") or None,
        ai_chat_blacklist=extra.get("ai_chat_blacklist") or None,
        default_locale=extra.get("default_locale") or "zh",
        store_name_en=extra.get("store_name_en") or None,
        store_subtitle_en=extra.get("store_subtitle_en") or None,
        site_meta_title_en=extra.get("site_meta_title_en") or None,
        site_meta_description_en=extra.get("site_meta_description_en") or None,
        site_keywords_en=extra.get("site_keywords_en") or None,
        ai_chat_name_en=extra.get("ai_chat_name_en") or None,
        ai_chat_welcome_en=extra.get("ai_chat_welcome_en") or None,
        shipping_policy_en=extra.get("shipping_policy_en") or None,
        show_real_stock=bool(extra.get("show_real_stock", True)),
        category_filter_recursive=bool(extra.get("category_filter_recursive", False)),
        service_badges=extra.get("service_badges") or [],
        allow_oversell_global=s.allow_oversell_global if s.allow_oversell_global is not None else 0,
        inventory_deduct_statuses=extra.get("inventory_deduct_statuses") or ["pending"],
        store_domain=store_domain,
        aqua_homepage_layout=extra.get("aqua_homepage_layout"),
        email_notify_list=extra.get("email_notify_list") or [],
        ai_allow_custom=ai_allow_custom,
        ai_quota_monthly=ai_quota_monthly,
        ai_quota_5h=ai_quota_5h,
        ai_quota_weekly=ai_quota_weekly,
        ai_used_this_month=ai_used_this_month,
        ai_used_5h=ai_used_5h,
        ai_used_weekly=ai_used_weekly,
        ai_exhausted_message=extra.get("ai_exhausted_message") or None,
        expiry_warning_days=int(extra.get("expiry_warning_days") or 30),
    )


@router.get("/storage-usage", summary="获取当前租户存储用量")
async def get_storage_usage(
    current_user: User = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
):
    from app.core.models.tenant import Tenant
    r = await db.execute(select(Tenant).where(Tenant.id == current_user.tenant_id))
    tenant = r.scalar_one_or_none()
    if not tenant:
        raise HTTPException(status_code=404, detail="租户不存在")
    return {"storage_used_mb": tenant.storage_used_mb, "storage_quota_mb": tenant.storage_quota_mb}


@router.get("", response_model=TenantSettingsOut, summary="获取店铺设置")
async def get_settings(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_admin_user),
):
    """读取当前店铺设置，无记录则返回默认空配置"""
    result = await db.execute(
        select(TenantSettings).where(TenantSettings.tenant_id == current_user.tenant_id)
    )
    s = result.scalar_one_or_none()

    tenant_result = await db.execute(
        select(
            Tenant.domain, Tenant.ai_allow_custom,
            Tenant.ai_quota_monthly, Tenant.ai_used_this_month, Tenant.ai_quota_month,
            Tenant.ai_quota_5h, Tenant.ai_quota_weekly,
        ).where(Tenant.id == current_user.tenant_id)
    )
    tenant_row = tenant_result.first()
    store_domain: str | None = tenant_row.domain if tenant_row else None
    ai_allow_custom: bool = tenant_row.ai_allow_custom if tenant_row else True
    ai_quota_monthly: int | None = tenant_row.ai_quota_monthly if tenant_row else None
    ai_used_this_month: int = _display_ai_used(tenant_row)
    ai_quota_5h: int | None = tenant_row.ai_quota_5h if tenant_row else None
    ai_quota_weekly: int | None = tenant_row.ai_quota_weekly if tenant_row else None

    sliding = await get_sliding_usage(db, current_user.tenant_id)

    _quota_kwargs = dict(
        ai_allow_custom=ai_allow_custom,
        ai_quota_monthly=ai_quota_monthly, ai_used_this_month=ai_used_this_month,
        ai_quota_5h=ai_quota_5h, ai_quota_weekly=ai_quota_weekly,
        ai_used_5h=sliding["used_5h"], ai_used_weekly=sliding["used_weekly"],
    )

    if not s:
        return TenantSettingsOut(
            store_name="My Store",
            default_shipping_fee=10.0,
            free_shipping_threshold=99.0,
            store_domain=store_domain,
            **_quota_kwargs,
        )
    return _settings_to_out(s, store_domain=store_domain, **_quota_kwargs)


@router.put("", response_model=TenantSettingsOut, summary="保存店铺设置")
async def put_settings(
    body: TenantSettingsUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_admin_user),
):
    """保存（整体替换）店铺设置。"""
    data = body.model_dump(exclude_unset=True)

    tenant_result = await db.execute(
        select(
            Tenant.domain, Tenant.ai_allow_custom,
            Tenant.ai_quota_monthly, Tenant.ai_used_this_month, Tenant.ai_quota_month,
            Tenant.ai_quota_5h, Tenant.ai_quota_weekly,
        ).where(Tenant.id == current_user.tenant_id)
    )
    tenant_row = tenant_result.first()
    store_domain: str | None = tenant_row.domain if tenant_row else None
    ai_allow_custom: bool = tenant_row.ai_allow_custom if tenant_row else True
    ai_quota_monthly: int | None = tenant_row.ai_quota_monthly if tenant_row else None
    ai_used_this_month: int = _display_ai_used(tenant_row)
    ai_quota_5h: int | None = tenant_row.ai_quota_5h if tenant_row else None
    ai_quota_weekly: int | None = tenant_row.ai_quota_weekly if tenant_row else None

    sliding = await get_sliding_usage(db, current_user.tenant_id)
    _quota_kwargs = dict(
        ai_allow_custom=ai_allow_custom,
        ai_quota_monthly=ai_quota_monthly, ai_used_this_month=ai_used_this_month,
        ai_quota_5h=ai_quota_5h, ai_quota_weekly=ai_quota_weekly,
        ai_used_5h=sliding["used_5h"], ai_used_weekly=sliding["used_weekly"],
    )

    if not ai_allow_custom:
        # superadmin 已禁止该租户自定义 AI 提供商/Key/地址/模型：静默忽略这些字段，不落库
        # （ai_enabled 开关和 anysearch_api_key 不受限制，租户始终可编辑）
        for k in ("ai_provider", "ai_api_key", "ai_base_url", "ai_model"):
            data.pop(k, None)

    # 服务保障展示项：入口校验，防止 extra JSON 被塞入超大内容
    if data.get("service_badges") is not None:
        data["service_badges"] = _normalize_service_badges(data["service_badges"])

    if not data:
        result = await db.execute(
            select(TenantSettings).where(TenantSettings.tenant_id == current_user.tenant_id)
        )
        s = result.scalar_one_or_none()
        if s:
            return _settings_to_out(s, store_domain=store_domain, **_quota_kwargs)
        return TenantSettingsOut(
            store_name="My Store", default_shipping_fee=10.0, free_shipping_threshold=99.0,
            store_domain=store_domain, **_quota_kwargs,
        )

    result = await db.execute(
        select(TenantSettings).where(TenantSettings.tenant_id == current_user.tenant_id)
    )
    existing = result.scalar_one_or_none()

    _AI_EXTRA_KEYS = {"ai_enabled", "ai_provider", "ai_api_key", "ai_base_url", "ai_model", "anysearch_api_key"}
    _CDN_EXTRA_KEYS = {"cdn_provider", "cdn_public_url", "cdn_s3_bucket", "cdn_s3_region",
                       "cdn_s3_access_key", "cdn_s3_secret_key", "cdn_s3_use_acl", "cdn_r2_endpoint",
                       "cdn_oss_bucket", "cdn_oss_endpoint", "cdn_oss_access_key", "cdn_oss_secret_key"}
    _EXTRA_KEYS = _AI_EXTRA_KEYS | _CDN_EXTRA_KEYS | {"store_subtitle", "show_store_name", "show_store_subtitle", "show_real_stock", "address_custom_fields",
                                     "site_meta_title", "site_meta_description", "site_keywords", "og_image", "favicon_url",
                                     "shipping_policy",
                                     "ai_chat_enabled", "ai_chat_name", "ai_chat_welcome", "ai_chat_faq", "ai_chat_blacklist", "ai_exhausted_message",
                                     "default_locale",
                                     "store_name_en", "store_subtitle_en", "site_meta_title_en", "site_meta_description_en",
                                     "site_keywords_en", "ai_chat_name_en", "ai_chat_welcome_en", "shipping_policy_en",
                                     "inventory_deduct_statuses", "aqua_homepage_layout", "email_notify_list",
                                     "customer_profile_fields", "expiry_warning_days", "service_badges",
                                     "category_filter_recursive"}

    if existing:
        extra = dict(existing.extra or {})
        for k, v in data.items():
            if k == "store_logo":
                extra["logo_url"] = v or ""
            elif k in _EXTRA_KEYS:
                if k == "customer_profile_fields" and v is not None:
                    from app.core.services.customer_fields import normalize_fields
                    try:
                        v = normalize_fields(v, strict=True)
                    except (ValueError, TypeError) as e:
                        raise HTTPException(status_code=422, detail=f"字段配置格式错误: {e}")
                extra[k] = v
            elif k in ("default_shipping_fee", "free_shipping_threshold") and v is not None:
                setattr(existing, k, Decimal(str(v)))
            elif k == "smtp_port" and v is not None:
                setattr(existing, k, int(v))
            elif k in ("smtp_use_tls", "smtp_enabled"):
                setattr(existing, k, int(bool(v)))
            elif k == "smtp_password" and v is not None:
                setattr(existing, k, v)
            elif v is not None:
                setattr(existing, k, v)
        existing.extra = extra
        await db.commit()
        await db.refresh(existing)
        await cache_delete(f"shop_info:{current_user.tenant_id}")
        return _settings_to_out(existing, store_domain=store_domain, **_quota_kwargs)
    else:
        extra = {}
        s = TenantSettings(tenant_id=current_user.tenant_id)
        for k, v in data.items():
            if k == "store_logo":
                extra["logo_url"] = v or ""
            elif k in _EXTRA_KEYS:
                if k == "customer_profile_fields" and v is not None:
                    from app.core.services.customer_fields import normalize_fields
                    try:
                        v = normalize_fields(v, strict=True)
                    except (ValueError, TypeError) as e:
                        raise HTTPException(status_code=422, detail=f"字段配置格式错误: {e}")
                extra[k] = v
            elif k in ("default_shipping_fee", "free_shipping_threshold") and v is not None:
                setattr(s, k, Decimal(str(v)))
            elif k == "smtp_port" and v is not None:
                setattr(s, k, int(v))
            elif k in ("smtp_use_tls", "smtp_enabled"):
                setattr(s, k, int(bool(v)))
            elif k != "smtp_password":
                setattr(s, k, v)
        s.extra = extra
        db.add(s)
        await db.commit()
        await db.refresh(s)
        await cache_delete(f"shop_info:{current_user.tenant_id}")
        return _settings_to_out(s, store_domain=store_domain, **_quota_kwargs)


@router.get("/ai/ollama-models", summary="列出本地 Ollama 可用模型")
async def list_ollama_models(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_admin_user),
):
    """从已保存的 Ollama 地址读取已安装模型列表"""
    result = await db.execute(
        select(TenantSettings).where(TenantSettings.tenant_id == current_user.tenant_id)
    )
    s = result.scalar_one_or_none()
    extra = s.extra or {} if s else {}
    extra = await resolve_ai_extra(db, current_user.tenant_id, extra)
    base_url = extra.get("ai_base_url") or _AI_PROVIDER_DEFAULTS["ollama"]["base_url"]

    try:
        async with httpx.AsyncClient(timeout=10) as client:
            resp = await client.get(base_url.rstrip("/") + "/api/tags")
            resp.raise_for_status()
            data = resp.json()
            models = [m["name"] for m in data.get("models", [])]
            return {"models": models, "base_url": base_url}
    except httpx.ConnectError:
        raise HTTPException(status_code=502, detail=f"无法连接 Ollama（{base_url}），请确认 Ollama 正在运行")
    except httpx.TimeoutException:
        raise HTTPException(status_code=504, detail=f"连接 Ollama 超时（{base_url}），请检查地址和端口")
    except httpx.HTTPStatusError as e:
        raise HTTPException(status_code=502, detail=f"Ollama 返回错误 {e.response.status_code}")
    except Exception as e:
        raise HTTPException(status_code=502, detail=f"检测失败：{str(e)}")


@router.post("/ai/test", summary="测试 AI 连接")
async def test_ai(
    body: AiTestRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_admin_user),
):
    """使用当前保存的 AI 配置发送测试请求，返回模型响应。"""
    result = await db.execute(
        select(TenantSettings).where(TenantSettings.tenant_id == current_user.tenant_id)
    )
    s = result.scalar_one_or_none()
    extra = dict(s.extra or {}) if s else {}

    tenant_row = (await db.execute(
        select(Tenant.ai_allow_custom).where(Tenant.id == current_user.tenant_id)
    )).first()
    ai_allow_custom: bool = tenant_row.ai_allow_custom if tenant_row else True

    if body.anysearch_api_key:
        extra["anysearch_api_key"] = body.anysearch_api_key

    if ai_allow_custom:
        # 前端传入的表单值优先于 DB 值（实现"保存前测试"）；禁止自定义时忽略表单覆盖
        if body.ai_provider is not None:
            extra["ai_provider"] = body.ai_provider
            # 切换提供商时清除上一个提供商的 base_url/model，让新提供商走默认值
            extra.pop("ai_base_url", None)
            extra.pop("ai_model", None)
        if body.ai_api_key:
            extra["ai_api_key"] = body.ai_api_key
        if body.ai_base_url:  # 非空才覆盖
            extra["ai_base_url"] = body.ai_base_url
        if body.ai_model:     # 非空才覆盖
            extra["ai_model"] = body.ai_model

    # 统一过一次 resolver：
    #   - 允许自定义且上面已经有可用配置（Key，或 Ollama 填了地址/模型）→ 原样返回，测的就是草稿值
    #   - 允许自定义但没有可用配置 → 自动回退到 AiProfile，跟真实业务调用行为一致
    #   - 禁止自定义 → 忽略上面的表单覆盖，直接用分配的 AiProfile
    extra = await resolve_ai_extra(db, current_user.tenant_id, extra)

    provider = extra.get("ai_provider", "deepseek")
    model    = extra.get("ai_model") or _AI_PROVIDER_DEFAULTS.get(provider, {}).get("model", "")

    reply = await call_ai(body.prompt, extra, timeout=120, max_tokens=512)
    return {"reply": reply, "model": model, "provider": provider}
