"""高级运费规则：规则集缓存。

只缓存**租户的已校验、已启用规则集**（advanced-shipping:{tenant}:{cache_version}，TTL 300s）。
绝不缓存与客户相关的报价：报价依赖地址、优惠券、客户分组、下单时刻，缓存它等于把 A 客户的
运费发给 B 客户。Nuxt/SWR 层同理。

Redis 不可用或未命中时**直连 MySQL 求值**——强制模式下没有回落到 legacy 定价这条路。
"""
import logging

from pydantic import ValidationError
from sqlalchemy import select, update

from app.core.cache import cache_delete, cache_get, cache_set

from .quote import RuleSet, RuleSpec
from .schemas import AdvancedShippingRuleIn

logger = logging.getLogger(__name__)

CACHE_TTL = 300

# 模型一律在函数体内、且从 app.core.models 聚合模块懒加载：
# app.core.models 会反向 import 本插件的 models，直接 `from .models import` 会在
# "先导入本模块"的场景下触发循环导入（懒加载写法沿用 plugins/tax/calculator.py）。


def rule_set_cache_key(tenant_id: int, cache_version: int) -> str:
    return f"advanced-shipping:{tenant_id}:{cache_version}"


async def load_settings(db, tenant_id: int):
    from app.core.models import AdvancedShippingSettings
    result = await db.execute(
        select(AdvancedShippingSettings).where(AdvancedShippingSettings.tenant_id == tenant_id)
    )
    return result.scalar_one_or_none()


async def settings_or_default(db, tenant_id: int):
    """**只读**路径用：没有配置行就返回内存默认值，不 INSERT。

    ensure_settings 会 add()+flush()，而 get_db 从不 commit——读路径上那条 INSERT 白写；
    更糟的是 advanced_shipping_settings 有 (tenant_id) 唯一约束，两个并发的首次请求
    都 flush 会有一个撞重复键 500。

    放在 cache.py（而不是 admin_service.py）：Store 结账也要读配置，
    而 admin_service 满是 HTTPException 与 Admin 语义，结账不该 import 它。
    """
    from app.core.models import AdvancedShippingSettings

    settings = await load_settings(db, tenant_id)
    return settings or AdvancedShippingSettings(
        tenant_id=tenant_id, mode="shadow",
        operating_timezone="Pacific/Auckland", cache_version=1,
    )


def _validated(row) -> RuleSpec | None:
    """写库时校验过一次，读出来再校验一次：老数据/手工 SQL 改过的行不该把结账带崩。"""
    try:
        AdvancedShippingRuleIn(
            shipping_method_id=row.shipping_method_id,
            quote_group=row.quote_group,
            aggregate_strategy=row.aggregate_strategy,
            delivery_mode=row.delivery_mode,
            presentation=row.presentation or {},
            conditions=row.conditions or {},
            pricing=row.pricing or {},
            priority=row.priority,
            enabled=bool(row.enabled),
        )
    except ValidationError as exc:
        logger.warning("跳过不合法的高级运费规则 tenant=%s rule=%s: %s", row.tenant_id, row.id, exc)
        return None
    return RuleSpec.from_row(row)


async def load_rule_set(db, tenant_id: int) -> RuleSet:
    """从 MySQL 读取本租户已启用规则 + 隐藏边，逐条过 Pydantic 契约。"""
    from app.core.models import AdvancedShippingRule, AdvancedShippingRuleHide

    rows = (await db.execute(
        select(AdvancedShippingRule)
        .where(AdvancedShippingRule.tenant_id == tenant_id, AdvancedShippingRule.enabled == 1)
        .order_by(AdvancedShippingRule.priority, AdvancedShippingRule.id)
    )).scalars().all()
    rules = tuple(spec for spec in (_validated(r) for r in rows) if spec is not None)

    valid_ids = {spec.id for spec in rules}
    hide_rows = (await db.execute(
        select(AdvancedShippingRuleHide)
        .where(AdvancedShippingRuleHide.tenant_id == tenant_id)
        .order_by(AdvancedShippingRuleHide.rule_id, AdvancedShippingRuleHide.hidden_rule_id)
    )).scalars().all()
    hides = tuple((h.rule_id, h.hidden_rule_id) for h in hide_rows if h.rule_id in valid_ids)
    return RuleSet(rules=rules, hides=hides)


async def get_rule_set(db, tenant_id: int, *, cache_version: int) -> RuleSet:
    """缓存优先，但缓存只是加速：Redis 挂了/未命中就直连 MySQL，绝不回落 legacy 定价。"""
    key = rule_set_cache_key(tenant_id, cache_version)
    try:
        raw = await cache_get(key)
    except Exception as exc:                     # noqa: BLE001 — Redis 故障不该影响结账
        logger.warning("高级运费规则集缓存读取失败，直连数据库: %s", exc)
        raw = None
    if raw:
        try:
            return RuleSet.from_json(raw)
        except (KeyError, TypeError, ValueError) as exc:
            logger.warning("高级运费规则集缓存内容损坏，直连数据库: %s", exc)

    rule_set = await load_rule_set(db, tenant_id)
    try:
        await cache_set(key, rule_set.to_json(), ttl=CACHE_TTL)
    except Exception as exc:                     # noqa: BLE001
        logger.warning("高级运费规则集缓存写入失败: %s", exc)
    return rule_set


async def invalidate_rule_set(db, tenant_id: int, *, cache_version: int | None = None) -> None:
    """任何规则/隐藏边/配置/模板导入的变更之后调用。

    版本号由数据库自增（一条 UPDATE 语句），不是"读出来 +1 再写回"——后者在并发写规则时
    会丢失一次失效，租户会看到最长 300 秒的旧运费。commit 交给调用方所在的事务。
    """
    from app.core.models import AdvancedShippingSettings

    await db.execute(
        update(AdvancedShippingSettings)
        .where(AdvancedShippingSettings.tenant_id == tenant_id)
        .values(cache_version=AdvancedShippingSettings.cache_version + 1)
    )
    if cache_version is not None:
        await cache_delete(rule_set_cache_key(tenant_id, cache_version))


__all__ = [
    "CACHE_TTL", "get_rule_set", "invalidate_rule_set", "load_rule_set", "load_settings",
    "rule_set_cache_key", "settings_or_default",
]
