"""角色协议：后台配置 + 小程序勾选签署（按俱乐部隔离）。"""
from __future__ import annotations

import uuid
from typing import Any, Dict, List, Optional, Tuple

from django.utils import timezone

from jituan.constants import CLUB_ID_DEFAULT
from jituan.models import ClubRoleAgreement, ClubRoleAgreementSign
from jituan.services.club_context import resolve_club_id_from_request

ROLES = (
    ('dashou', '打手/接单员'),
    ('guanshi', '管事'),
    ('zuzhang', '组长'),
)

ROLE_SET = {r[0] for r in ROLES}

# 卡点：可后台逐项开关；默认贴近产品约定
GATE_DEFS = (
    ('qiangdan', '抢单/接单时'),
    ('register', '身份注册时'),
    ('tixian', '提现时'),
    ('invite', '邀请下级时'),
)
GATE_SET = {g[0] for g in GATE_DEFS}

DEFAULT_GATES = {
    'dashou': {'qiangdan': True, 'register': False, 'tixian': False, 'invite': False},
    'guanshi': {'qiangdan': False, 'register': True, 'tixian': True, 'invite': False},
    'zuzhang': {'qiangdan': False, 'register': False, 'tixian': True, 'invite': False},
}


def _club(request) -> str:
    return (resolve_club_id_from_request(request) or CLUB_ID_DEFAULT).strip() or CLUB_ID_DEFAULT


def _images(val) -> List[str]:
    if not val:
        return []
    if isinstance(val, list):
        return [str(x).strip() for x in val if str(x).strip()][:20]
    return []


def _clamp_read_seconds(val) -> int:
    try:
        n = int(val)
    except (TypeError, ValueError):
        n = 2
    if n < 0:
        n = 0
    if n > 300:
        n = 300
    return n


def normalize_gates(role: str, raw) -> Dict[str, bool]:
    base = dict(DEFAULT_GATES.get(role) or {k: False for k in GATE_SET})
    if isinstance(raw, dict):
        for k in GATE_SET:
            if k in raw:
                base[k] = bool(raw.get(k))
    return base


def _row_to_dict(row: Optional[ClubRoleAgreement], club_id: str, role: str) -> Dict[str, Any]:
    if not row:
        return {
            'club_id': club_id,
            'role': role,
            'enabled': False,
            'title': '',
            'content_text': '',
            'images': [],
            'read_seconds': 2,
            'gate_config': normalize_gates(role, None),
            'gate_defs': [{'key': k, 'label': lab} for k, lab in GATE_DEFS],
            'version': 0,
            'published_at': '',
            'exists': False,
        }
    return {
        'club_id': row.club_id,
        'role': row.role,
        'enabled': bool(row.enabled),
        'title': row.title or '',
        'content_text': row.content_text or '',
        'images': _images(row.images_json),
        'read_seconds': _clamp_read_seconds(getattr(row, 'read_seconds', 2)),
        'gate_config': normalize_gates(role, getattr(row, 'gate_config', None)),
        'gate_defs': [{'key': k, 'label': lab} for k, lab in GATE_DEFS],
        'version': int(row.version or 0),
        'published_at': row.published_at.isoformat() if row.published_at else '',
        'updated_by': row.updated_by or '',
        'exists': True,
        'id': row.id,
    }


def build_admin_payload(request) -> Dict[str, Any]:
    club_id = _club(request)
    rows = {
        r.role: r
        for r in ClubRoleAgreement.query.filter(club_id=club_id)
    }
    items = []
    for role, name in ROLES:
        d = _row_to_dict(rows.get(role), club_id, role)
        d['role_name'] = name
        items.append(d)
    return {
        'club_id': club_id,
        'roles': items,
        'gate_defs': [{'key': k, 'label': lab} for k, lab in GATE_DEFS],
    }


def save_agreement(request, data: dict, operator: str = '') -> Tuple[Optional[dict], Optional[str]]:
    club_id = _club(request)
    role = (data.get('role') or '').strip().lower()
    if role not in ROLE_SET:
        return None, '无效角色'

    title = (data.get('title') or '')[:128]
    content_text = data.get('content_text') or ''
    if len(content_text) > 50000:
        return None, '正文过长'
    images = _images(data.get('images') or data.get('images_json'))
    enabled = bool(data.get('enabled'))
    read_seconds = _clamp_read_seconds(data.get('read_seconds', 2))
    gate_config = normalize_gates(role, data.get('gate_config'))

    row = ClubRoleAgreement.query.filter(club_id=club_id, role=role).first()
    if not row:
        row = ClubRoleAgreement(club_id=club_id, role=role, version=0)
    row.enabled = enabled
    row.title = title
    row.content_text = content_text
    row.images_json = images
    row.read_seconds = read_seconds
    row.gate_config = gate_config
    row.updated_by = (operator or '')[:32]
    row.save()
    out = _row_to_dict(row, club_id, role)
    out['role_name'] = dict(ROLES).get(role, role)
    return out, None


def publish_agreement(request, data: dict, operator: str = '') -> Tuple[Optional[dict], Optional[str]]:
    """保存内容并 version+1，强制用户重签新版本。"""
    club_id = _club(request)
    role = (data.get('role') or '').strip().lower()
    if role not in ROLE_SET:
        return None, '无效角色'

    saved, err = save_agreement(request, data, operator=operator)
    if err:
        return None, err

    row = ClubRoleAgreement.query.filter(club_id=club_id, role=role).first()
    if not row:
        return None, '协议不存在'
    if not (row.title or '').strip() and not (row.content_text or '').strip() and not _images(row.images_json):
        return None, '请先填写标题、正文或上传图片再发布'

    row.version = int(row.version or 0) + 1
    row.published_at = timezone.now()
    row.enabled = True if data.get('enabled') is None else bool(data.get('enabled'))
    row.updated_by = (operator or '')[:32]
    row.save()
    out = _row_to_dict(row, club_id, role)
    out['role_name'] = dict(ROLES).get(role, role)
    return out, None


def get_current_for_user(
    club_id: str,
    user_id: str,
    role: str,
    *,
    gate: str = '',
    view_only: bool = False,
) -> Dict[str, Any]:
    """
    取某角色当前协议。
    - gate 非空：仅当该卡点开启且未签当前版时 need_sign=True
    - view_only：只读查看，不强制签署
    """
    role = (role or '').strip().lower()
    if role not in ROLE_SET:
        return {'need_sign': False, 'enabled': False, 'msg': '无效角色'}

    cid = (club_id or '').strip() or CLUB_ID_DEFAULT
    row = ClubRoleAgreement.query.filter(club_id=cid, role=role).first()
    empty = {
        'need_sign': False,
        'enabled': False,
        'club_id': cid,
        'role': role,
        'version': 0,
        'title': '',
        'content_text': '',
        'images': [],
        'read_seconds': 0,
        'gate_config': normalize_gates(role, None),
        'gate_active': False,
        'already_signed': False,
    }
    if not row or int(row.version or 0) <= 0:
        return empty

    version = int(row.version)
    gates = normalize_gates(role, getattr(row, 'gate_config', None))
    signed = ClubRoleAgreementSign.query.filter(
        club_id=cid, user_id=str(user_id), role=role, version=version,
    ).exists()
    enabled = bool(row.enabled)
    read_seconds = _clamp_read_seconds(getattr(row, 'read_seconds', 2))

    gate_key = (gate or '').strip().lower()
    gate_active = True
    if gate_key:
        if gate_key not in GATE_SET:
            return {**empty, 'msg': '无效卡点', 'enabled': enabled}
        gate_active = bool(gates.get(gate_key))

    need_sign = False
    if enabled and not view_only and not signed and gate_active:
        need_sign = True
    # 无 gate 且非 view：兼容旧调用，只要启用且未签就 need_sign
    if enabled and not view_only and not signed and not gate_key:
        need_sign = True

    return {
        'need_sign': need_sign,
        'enabled': enabled,
        'club_id': cid,
        'role': role,
        'role_name': dict(ROLES).get(role, role),
        'version': version,
        'title': row.title or '角色服务协议',
        'content_text': row.content_text or '',
        'images': _images(row.images_json),
        'read_seconds': read_seconds,
        'gate_config': gates,
        'gate': gate_key,
        'gate_active': gate_active if gate_key else enabled,
        'agreement_id': row.id,
        'published_at': row.published_at.isoformat() if row.published_at else '',
        'already_signed': signed,
    }


def list_for_user(club_id: str, user_id: str, roles: Optional[List[str]] = None) -> Dict[str, Any]:
    """小程序「我的协议」列表：按俱乐部返回已发布协议摘要。"""
    cid = (club_id or '').strip() or CLUB_ID_DEFAULT
    want = [r for r in (roles or list(ROLE_SET)) if r in ROLE_SET]
    if not want:
        want = list(ROLE_SET)
    items = []
    for role in want:
        d = get_current_for_user(cid, user_id, role, view_only=True)
        if int(d.get('version') or 0) <= 0:
            continue
        items.append({
            'role': d['role'],
            'role_name': d.get('role_name') or role,
            'title': d.get('title') or '',
            'version': d.get('version') or 0,
            'already_signed': bool(d.get('already_signed')),
            'enabled': bool(d.get('enabled')),
            'published_at': d.get('published_at') or '',
        })
    return {'club_id': cid, 'items': items}


def sign_agreement(
    club_id: str,
    user_id: str,
    role: str,
    *,
    client_meta: Optional[dict] = None,
    agreed: bool = False,
) -> Tuple[Optional[dict], Optional[str]]:
    """勾选确认签署；不再要求手写签名图。"""
    role = (role or '').strip().lower()
    if role not in ROLE_SET:
        return None, '无效角色'
    if not agreed:
        return None, '请勾选同意协议'

    cid = (club_id or '').strip() or CLUB_ID_DEFAULT
    row = ClubRoleAgreement.query.filter(club_id=cid, role=role).first()
    if not row or not row.enabled or int(row.version or 0) <= 0:
        return None, '当前无需签署或协议未发布'

    version = int(row.version)
    existed = ClubRoleAgreementSign.query.filter(
        club_id=cid, user_id=str(user_id), role=role, version=version,
    ).first()
    if existed:
        return {
            'already_signed': True,
            'sign_id': existed.id,
            'version': version,
            'signed_at': existed.signed_at.isoformat() if existed.signed_at else '',
        }, None

    meta = dict(client_meta or {})
    meta.setdefault('sign_mode', 'checkbox')
    sign = ClubRoleAgreementSign.query.create(
        club_id=cid,
        user_id=str(user_id),
        role=role,
        agreement_id=row.id,
        version=version,
        title_snapshot=(row.title or '')[:128],
        content_snapshot=row.content_text or '',
        images_snapshot=_images(row.images_json),
        sign_image_url='',
        client_meta=meta,
    )
    return {
        'already_signed': False,
        'sign_id': sign.id,
        'version': version,
        'signed_at': sign.signed_at.isoformat() if sign.signed_at else '',
    }, None


def upload_agreement_image(file_obj, club_id: str, kind: str = 'content') -> Tuple[Optional[str], Optional[str]]:
    """后台上传协议配图，返回相对路径。"""
    from utils.oss_utils import validate_image, upload_to_oss

    ok, err = validate_image(file_obj)
    if not ok:
        return None, err or '图片无效'
    cid = (club_id or 'xq').strip() or 'xq'
    kind = 'content'
    ext = 'png'
    name = getattr(file_obj, 'name', '') or ''
    if '.' in name:
        ext = name.rsplit('.', 1)[-1].lower()[:8] or 'png'
    key = f'role_agreement/{cid}/{kind}/{uuid.uuid4().hex}.{ext}'
    try:
        path = upload_to_oss(file_obj, key)
        if not path:
            return None, '上传失败'
    except Exception as e:
        return None, f'上传失败: {e}'
    return path, None
