"""俱乐部微信商户证书落盘（收款 / 提现分目录）。"""
import logging
import os
import re

from django.conf import settings

logger = logging.getLogger(__name__)

# pay = 点单收款商户；withdraw = 提现转账商户
CERT_KINDS = {
    'pay_key': ('pay', 'apiclient_key.pem', 'pay_key_path'),
    'pay_cert': ('pay', 'apiclient_cert.pem', 'pay_cert_path'),
    'withdraw_key': ('withdraw', 'apiclient_key.pem', 'private_key_path'),
    'withdraw_cert': ('withdraw', 'apiclient_cert.pem', None),  # 与私钥同目录，不单独存字段
    'withdraw_platform': ('withdraw', 'pub_key.pem', 'platform_cert_dir'),  # 目录字段指向所在目录
}

_SAFE_CLUB = re.compile(r'^[a-zA-Z0-9_-]{1,16}$')


def club_wx_cert_root():
    """
    证书根目录。优先 settings.CLUB_WX_CERT_ROOT；
    否则尝试沿用现网 secrets 父目录下的 wx_certs；最后 BASE_DIR/wx_certs。
    """
    configured = (getattr(settings, 'CLUB_WX_CERT_ROOT', None) or '').strip()
    if configured:
        return configured
    # 兼容现网：.../sites/xxx/tuikuanzhengshu → .../sites/xxx/wx_certs
    for attr in ('WEIXIN_CERT_PATH', 'WEIXIN_KEY_PATH'):
        p = (getattr(settings, attr, None) or '').strip()
        if p:
            parent = os.path.dirname(os.path.dirname(os.path.abspath(p)))
            if parent and parent not in ('.', os.sep):
                return os.path.join(parent, 'wx_certs')
    v3 = getattr(settings, 'WECHAT_PAY_V3_CONFIG', None) or {}
    pk = (v3.get('PRIVATE_KEY_PATH') or '').strip()
    if pk:
        parent = os.path.dirname(os.path.dirname(os.path.abspath(pk)))
        if parent and parent not in ('.', os.sep):
            return os.path.join(parent, 'wx_certs')
    return os.path.join(str(settings.BASE_DIR), 'wx_certs')


def club_cert_dir(club_id, bucket):
    cid = (club_id or '').strip()
    if not _SAFE_CLUB.match(cid):
        raise ValueError('非法 club_id')
    if bucket not in ('pay', 'withdraw'):
        raise ValueError('非法证书分类')
    path = os.path.join(club_wx_cert_root(), cid, bucket)
    os.makedirs(path, mode=0o750, exist_ok=True)
    return path


def _validate_pem(raw: bytes, expect_private: bool):
    if not raw or len(raw) > 64 * 1024:
        raise ValueError('文件过大或为空')
    text = raw.decode('utf-8', errors='ignore')
    if 'BEGIN' not in text or 'END' not in text:
        raise ValueError('不是有效的 PEM 文件')
    has_private = 'PRIVATE KEY' in text
    if expect_private and not has_private:
        raise ValueError('需要私钥 PEM（含 PRIVATE KEY）')
    if not expect_private and has_private:
        raise ValueError('请上传证书/公钥 PEM，不要上传私钥')
    return raw


def save_club_cert_file(club, kind, uploaded_file):
    """
    保存上传的证书文件并回写 Club 字段。
    kind: pay_key | pay_cert | withdraw_key | withdraw_cert | withdraw_platform
    返回 {'path': ..., 'field': ..., 'kind': ...}
    """
    if kind not in CERT_KINDS:
        raise ValueError(f'不支持的文件类型: {kind}')
    bucket, filename, field_name = CERT_KINDS[kind]
    expect_private = kind in ('pay_key', 'withdraw_key')
    raw = uploaded_file.read()
    raw = _validate_pem(raw, expect_private=expect_private)

    directory = club_cert_dir(club.club_id, bucket)
    dest = os.path.join(directory, filename)
    # 原子写入
    tmp = dest + '.tmp'
    with open(tmp, 'wb') as f:
        f.write(raw)
    os.chmod(tmp, 0o640)
    os.replace(tmp, dest)

    update_fields = []
    if field_name == 'platform_cert_dir':
        # 平台证书：字段存目录
        setattr(club, field_name, directory)
        update_fields.append(field_name)
        saved_path = directory
    elif field_name:
        setattr(club, field_name, dest)
        update_fields.append(field_name)
        saved_path = dest
    else:
        # withdraw_cert：只落盘，序列号由私钥配对自动解析
        saved_path = dest

    if update_fields:
        club.save(update_fields=update_fields)

    logger.warning(
        '[CLUB_CERT_UPLOAD] club=%s kind=%s path=%s field=%s',
        club.club_id, kind, saved_path, field_name or '(none)',
    )
    return {
        'kind': kind,
        'path': saved_path,
        'field': field_name,
        'bucket': bucket,
        'filename': filename,
    }
