"""俱乐部支付通道：读配置、加解密、付呗/微信/支付宝字段归一化。"""
import logging
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional

from django.conf import settings
from django.core.cache import cache

from gvsdsdk.payment.models import EncryptField, _decrypt_field

from jituan.constants import (
    CLUB_ID_DEFAULT,
    FUBEI_GATEWAY_PROD,
    FUBEI_GATEWAY_SANDBOX,
    PAY_CHANNEL_ALIPAY_WAP,
    PAY_CHANNEL_FUBEI_WX_MINI,
    PAY_CHANNEL_WECHAT_JSAPI,
    PAY_METHOD_ALIPAY,
    PAY_METHOD_FUBEI,
    PAY_METHOD_LABELS,
    PAY_METHOD_TO_CHANNEL,
    PAY_METHOD_WECHAT,
)
from jituan.models import Club, ClubPaymentChannel

logger = logging.getLogger(__name__)

CACHE_TTL = 60


def encrypt_secret_field(plain: str) -> str:
    if not plain:
        return ''
    return EncryptField(plain)


def decrypt_secret_field(encrypted: str) -> str:
    if not encrypted:
        return ''
    return _decrypt_field(encrypted)


@dataclass
class PaymentChannelConfig:
    """归一化后的通道配置，供支付网关读取。"""
    club_id: str
    channel: str
    config_id: int
    name: str
    enabled: bool
    sandbox: bool
    app_id: str = ''
    mch_id: str = ''
    api_key: str = ''
    private_key: str = ''
    public_key: str = ''
    cert_serial_no: str = ''
    private_key_path: str = ''
    platform_cert_dir: str = ''
    notify_url: str = ''
    return_url: str = ''
    gateway_url: str = ''
    mini_app_id: str = ''
    extra: Dict[str, Any] = field(default_factory=dict)

    @property
    def store_id(self):
        return self.extra.get('store_id') or self.extra.get('fubei_store_id')

    @property
    def fubei_vendor_sn(self):
        return self.extra.get('vendor_sn') or self.extra.get('fubei_vendor_sn') or self.app_id


def _cache_key(club_id: str, channel: str) -> str:
    return f'club_pay_ch:{club_id}:{channel}'


def _default_notify_path(channel: str) -> str:
    if channel == PAY_CHANNEL_FUBEI_WX_MINI:
        return '/hqhd/dingdan/fubei-notify/'
    if channel == PAY_CHANNEL_ALIPAY_WAP:
        return '/hqhd/dingdan/alipay-notify/'
    return '/hqhd/dingdan/pay-notify/'


def _build_base_url(club: Club) -> str:
    domain = (club.h5_domain or '').strip().rstrip('/')
    if domain:
        return domain
    return 'https://www.abas.asia'


def _resolve_notify_url(club: Club, row: ClubPaymentChannel) -> str:
    if row.notify_url:
        return row.notify_url.strip()
    return f'{_build_base_url(club)}{_default_notify_path(row.channel)}'


def _row_to_config(club: Club, row: ClubPaymentChannel) -> PaymentChannelConfig:
    extra = dict(row.extra_json or {})
    gateway = (row.gateway_url or '').strip()
    if not gateway and row.channel == PAY_CHANNEL_FUBEI_WX_MINI:
        gateway = FUBEI_GATEWAY_SANDBOX if row.sandbox else FUBEI_GATEWAY_PROD

    mini_app_id = (row.mini_app_id or club.pay_app_id or club.wx_appid or '').strip()

    return PaymentChannelConfig(
        club_id=row.club_id,
        channel=row.channel,
        config_id=row.id,
        name=row.name,
        enabled=row.enabled == 1,
        sandbox=bool(row.sandbox),
        app_id=(row.app_id or '').strip(),
        mch_id=(row.mch_id or '').strip(),
        api_key=decrypt_secret_field(row.api_key_enc),
        private_key=decrypt_secret_field(row.private_key_enc),
        public_key=decrypt_secret_field(row.public_key_enc) or (row.public_key_enc or '').strip(),
        cert_serial_no=row.cert_serial_no or '',
        private_key_path=row.private_key_path or '',
        platform_cert_dir=row.platform_cert_dir or '',
        notify_url=_resolve_notify_url(club, row),
        return_url=(row.return_url or '').strip(),
        gateway_url=gateway,
        mini_app_id=mini_app_id,
        extra=extra,
    )


def get_club_payment_channel(
    club_id: Optional[str],
    channel: str,
    *,
    config_id: Optional[int] = None,
    require_enabled: bool = True,
) -> Optional[PaymentChannelConfig]:
    """读取俱乐部某通道配置；优先 is_default，可指定 config_id。"""
    cid = (club_id or '').strip() or CLUB_ID_DEFAULT
    cache_k = _cache_key(cid, channel)
    if config_id is None and require_enabled:
        cached = cache.get(cache_k)
        if cached is not None:
            return cached if cached != '__none__' else None

    club = Club.query.filter(club_id=cid).first()
    if not club:
        logger.warning('club %s 不存在，无法读取支付通道 %s', cid, channel)
        return None

    qs = ClubPaymentChannel.query.filter(club_id=cid, channel=channel)
    if require_enabled:
        qs = qs.filter(enabled=1)
    if config_id is not None:
        row = qs.filter(id=config_id).first()
    else:
        row = qs.filter(is_default=True).order_by('sort_order', 'id').first()
        if not row:
            row = qs.order_by('sort_order', 'id').first()

    if not row:
        if config_id is None and require_enabled:
            cache.set(cache_k, '__none__', CACHE_TTL)
        return None

    cfg = _row_to_config(club, row)
    if config_id is None and require_enabled:
        cache.set(cache_k, cfg, CACHE_TTL)
    return cfg


def invalidate_club_payment_cache(club_id: str, channel: Optional[str] = None):
    cid = (club_id or '').strip() or CLUB_ID_DEFAULT
    if channel:
        cache.delete(_cache_key(cid, channel))
        return
    for ch in (
        PAY_CHANNEL_WECHAT_JSAPI,
        PAY_CHANNEL_ALIPAY_WAP,
        PAY_CHANNEL_FUBEI_WX_MINI,
    ):
        cache.delete(_cache_key(cid, ch))


def get_fubei_config(club_id: Optional[str] = None) -> Optional[PaymentChannelConfig]:
    """付呗微信小程序通道配置。"""
    return get_club_payment_channel(club_id, PAY_CHANNEL_FUBEI_WX_MINI)


def list_payment_methods_for_miniapp(club_id: Optional[str] = None) -> List[Dict[str, Any]]:
    """小程序下单页：返回用户可见的支付方式（enabled 且配置完整）。"""
    cid = (club_id or '').strip() or CLUB_ID_DEFAULT
    methods = []

    wechat_direct = get_club_payment_channel(cid, PAY_CHANNEL_WECHAT_JSAPI)
    fubei = get_fubei_config(cid)
    alipay = get_club_payment_channel(cid, PAY_CHANNEL_ALIPAY_WAP)

    if fubei and fubei.app_id and fubei.api_key:
        methods.append({
            'code': PAY_METHOD_FUBEI,
            'label': PAY_METHOD_LABELS[PAY_METHOD_FUBEI],
            'channel': PAY_CHANNEL_FUBEI_WX_MINI,
            'sort_order': _sort_for(cid, PAY_CHANNEL_FUBEI_WX_MINI),
        })
    elif wechat_direct and wechat_direct.mch_id and wechat_direct.api_key:
        methods.append({
            'code': PAY_METHOD_WECHAT,
            'label': PAY_METHOD_LABELS[PAY_METHOD_WECHAT],
            'channel': PAY_CHANNEL_WECHAT_JSAPI,
            'sort_order': _sort_for(cid, PAY_CHANNEL_WECHAT_JSAPI),
        })

    if alipay and alipay.app_id and alipay.private_key and alipay.public_key:
        from jituan.services.alipay_pay import is_alipay_enabled_for_club
        if is_alipay_enabled_for_club(cid):
            methods.append({
                'code': PAY_METHOD_ALIPAY,
                'label': PAY_METHOD_LABELS[PAY_METHOD_ALIPAY],
                'channel': PAY_CHANNEL_ALIPAY_WAP,
                'sort_order': _sort_for(cid, PAY_CHANNEL_ALIPAY_WAP),
            })

    methods.sort(key=lambda x: x.get('sort_order', 0))
    return methods


def _sort_for(club_id: str, channel: str) -> int:
    row = ClubPaymentChannel.query.filter(
        club_id=club_id, channel=channel, enabled=1,
    ).order_by('-is_default', 'sort_order', 'id').first()
    return row.sort_order if row else 0


def channel_for_pay_method(pay_method: str) -> Optional[str]:
    return PAY_METHOD_TO_CHANNEL.get((pay_method or '').strip().lower())


# 付呗通道后台必填字段说明（文档/校验用）
FUBEI_REQUIRED_FIELDS = {
    'app_id': '付呗开放平台 vendor_sn / 付呗ID',
    'api_key_enc': '付呗 Secret（入库前加密）',
    'mini_app_id': '微信小程序 AppID（可与 club.wx_appid 相同；须在付呗后台关联）',
    'notify_url': '异步回调，默认 /hqhd/dingdan/fubei-notify/',
    'extra_json.store_id': '付呗门店 store_id（服务商开户后提供，统一下单必填）',
}
