#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""销售日报审阅 API（按活动原文，不走 latest.md 摘要链）

数据源：数据/daily_activities_mcp.json（飞书活动全量）
评论：数据/daily_review_comments.json（append-only，不覆盖原文）
权限：admin/gm/director/readonly 全量读；manager 本部门；sales 本人。
评论：仅 admin/gm/director/manager 可写；readonly/sales 只读。
"""
import os, json, re, html, uuid, threading
from datetime import date, datetime, timedelta

ROOT = os.path.expanduser('~/Desktop/Hermes输出-工作类')
DATA = os.path.join(ROOT, '数据')
SRC = os.path.join(DATA, 'daily_activities_mcp.json')
PERSONNEL = os.path.join(DATA, 'pricing', '_personnel.json')
ROLES = os.path.join(DATA, 'user_roles.json')
COMMENTS = os.path.join(DATA, 'daily_review_comments.json')
COMMENT_ROLES = ('admin', 'gm', 'director', 'manager')
LOCK = threading.Lock()
_CACHE = {'mtime': None, 'by_date': {}, 'by_id': {}, 'personnel': {}, 'roles': {}}


def _load_json(path, default):
    try:
        with open(path, encoding='utf-8') as f:
            return json.load(f)
    except Exception:
        return default


def _people_maps():
    pm = {}
    d = _load_json(PERSONNEL, {})
    for p in (d.get('personnel') or []):
        if p.get('name'):
            pm[p['name']] = p
    rm = {}
    for u in (_load_json(ROLES, []) or []):
        if u.get('name'):
            rm[u['name']] = u
    return pm, rm


def _clean_text(raw):
    s = str(raw or '')
    s = re.sub(r'<br\s*/?>', '\n', s, flags=re.I)
    s = re.sub(r'</p\s*>', '\n', s, flags=re.I)
    s = re.sub(r'<[^>]+>', '', s)
    s = html.unescape(s)
    s = s.replace('\u00a0', ' ')
    s = re.sub(r'[ \t]+\n', '\n', s)
    s = re.sub(r'\n{3,}', '\n\n', s)
    return s.strip()


def _load_source():
    try:
        mt = os.path.getmtime(SRC)
    except OSError:
        return
    if _CACHE['mtime'] == mt:
        return
    raw = _load_json(SRC, {})
    items = raw.get('items') if isinstance(raw, dict) else raw
    if not isinstance(items, list):
        items = []
    pm, rm = _people_maps()
    by_date, by_id = {}, {}
    for x in items:
        if not isinstance(x, dict):
            continue
        d = str(x.get('start_time') or '')[:10]
        if not d:
            continue
        owner = x.get('owner') or {}
        sales = (owner.get('name_cn') or owner.get('name') or '').strip()
        if not sales:
            continue
        p = pm.get(sales) or {}
        u = rm.get(sales) or {}
        dept = (p.get('dept') or u.get('dept') or (x.get('field_5ed7ab') or {}).get('label') or '未归属').strip()
        customer = ((x.get('field_5f20fc') or {}).get('label') or '').strip()
        atype = ((x.get('field_76654e') or {}).get('label') or x.get('name') or '').strip()
        raw_html = str(x.get('field_b99055') or '')
        text = _clean_text(raw_html)
        wid = str(x.get('work_item_id') or '').strip()
        aid = wid or ('A' + uuid.uuid4().hex[:12])
        rec = {
            'id': aid,
            'date': d,
            'sales': sales,
            'dept': dept,
            'customer': customer or '（未填客户）',
            'type': atype or '活动跟进',
            'source_type': str(x.get('name') or ''),
            'text': text,
            'html': raw_html,
            'chars': len(text),
            'link': ('https://project.feishu.cn/xsguanli/story/detail/' + wid) if wid else '',
            'region': ((x.get('field_5ed7ab') or {}).get('label') or ''),
        }
        by_date.setdefault(d, []).append(rec)
        by_id[aid] = rec
    for d in by_date:
        by_date[d].sort(key=lambda r: (r.get('sales') or '', r.get('id') or ''))
    _CACHE.update({'mtime': mt, 'by_date': by_date, 'by_id': by_id, 'personnel': pm, 'roles': rm})


def _scope_names(au):
    role = (au.get('role') or '').lower()
    name = au.get('name') or ''
    if role in ('admin', 'gm', 'director', 'readonly'):
        return None
    if role == 'manager':
        dept = au.get('dept') or ''
        pm = _CACHE.get('personnel') or {}
        names = set()
        if '生命科学' in dept:
            region = '浙江' if '浙江' in dept else ('广东' if '广东' in dept else '')
            if region:
                for n, p in pm.items():
                    if region in (p.get('region') or '') or region in (p.get('dept') or ''):
                        names.add(n)
        else:
            base = dept.replace('诊断原料', '')
            for n, p in pm.items():
                pd = p.get('dept') or ''
                if pd == dept or base in pd or pd in base:
                    names.add(n)
        names.add(name)
        return names
    return {name} if name else set()


def _can_comment(au):
    return (au.get('role') or '').lower() in COMMENT_ROLES


def _load_comments():
    d = _load_json(COMMENTS, {})
    if isinstance(d, dict):
        return d.get('items') if isinstance(d.get('items'), list) else []
    return d if isinstance(d, list) else []


def _save_comments(items):
    os.makedirs(os.path.dirname(COMMENTS), exist_ok=True)
    tmp = COMMENTS + '.tmp'
    with open(tmp, 'w', encoding='utf-8') as f:
        json.dump({'version': 1, 'items': items}, f, ensure_ascii=False, indent=1)
    os.replace(tmp, COMMENTS)


def status_of(au):
    return {
        'role': (au.get('role') or 'sales'),
        'can_comment': _can_comment(au),
        'scope': 'all' if _scope_names(au) is None else 'own',
    }


def list_payload(au, qs):
    _load_source()
    by_date = _CACHE.get('by_date') or {}
    today = date.today()
    yesterday = (today - timedelta(days=1)).isoformat()
    dates = sorted(by_date.keys())
    default_date = yesterday if yesterday in by_date else next((d for d in reversed(dates) if d <= yesterday), dates[-1] if dates else yesterday)
    d = (qs.get('date') or default_date).strip()[:10]
    scope = _scope_names(au)
    rows = [r for r in (by_date.get(d) or []) if scope is None or r.get('sales') in scope]

    dept = (qs.get('dept') or '').strip()
    sales = (qs.get('sales') or '').strip()
    atype = (qs.get('type') or '').strip()
    q = (qs.get('q') or '').strip().lower()
    cfilter = (qs.get('comment') or '').strip()
    comments = _load_comments()
    cmap = {}
    for c in comments:
        cmap.setdefault(c.get('activity_id'), []).append(c)
    for c in comments:
        pass
    out = []
    for r in rows:
        if dept and r.get('dept') != dept:
            continue
        if sales and r.get('sales') != sales:
            continue
        if atype and r.get('type') != atype:
            continue
        if q and q not in ((r.get('text') or '') + ' ' + (r.get('customer') or '') + ' ' + (r.get('sales') or '')).lower():
            continue
        cs = sorted(cmap.get(r.get('id'), []), key=lambda c: c.get('created_at') or '')
        open_n = sum(1 for c in cs if c.get('status', 'open') == 'open')
        if cfilter == 'open' and open_n == 0:
            continue
        if cfilter == 'has' and not cs:
            continue
        if cfilter == 'none' and cs:
            continue
        rr = dict(r)
        rr['comments'] = cs
        rr['comment_count'] = len(cs)
        rr['open_comment_count'] = open_n
        out.append(rr)

    groups = {}
    for r in out:
        g = groups.setdefault(r['sales'], {'sales': r['sales'], 'dept': r.get('dept') or '', 'count': 0, 'activities': []})
        g['count'] += 1
        g['activities'].append(r)
    glist = sorted(groups.values(), key=lambda g: (g.get('dept') or '', g.get('sales') or ''))
    all_dates = dates[-35:]
    return {
        'ok': True,
        'date': d,
        'default_date': default_date,
        'user': {'name': au.get('name') or '', 'role': au.get('role') or ''},
        'dates': all_dates,
        'role': (au.get('role') or 'sales'),
        'can_comment': _can_comment(au),
        'scope': 'all' if scope is None else 'own',
        'scope_label': ('全量可见' if scope is None else ('本部门可见' if (au.get('role') or '').lower() == 'manager' else '本人可见')),
        'stats': {
            'people': len(glist),
            'activities': len(out),
            'customers': len({r.get('customer') for r in out if r.get('customer')}),
            'comments': sum(len(r.get('comments') or []) for r in out),
            'open_comments': sum(r.get('open_comment_count') or 0 for r in out),
            'chars': sum(r.get('chars') or 0 for r in out),
        },
        'depts': sorted({r.get('dept') for r in out if r.get('dept')}),
        'sales_list': sorted({r.get('sales') for r in out if r.get('sales')}),
        'types': sorted({r.get('type') for r in out if r.get('type')}),
        'groups': glist,
    }



def set_comment_status(au, payload):
    if not _can_comment(au):
        return None, '无评论权限（仅主管/经理/总监/管理员）'
    cid = str(payload.get('comment_id') or '').strip()
    status = str(payload.get('status') or 'resolved').strip()
    if status not in ('open', 'resolved'):
        return None, '非法状态'
    if not cid:
        return None, '缺少评论 id'
    with LOCK:
        items = _load_comments()
        target = next((c for c in items if c.get('id') == cid), None)
        if not target:
            return None, '评论不存在'
        scope = _scope_names(au)
        if scope is not None and target.get('sales') not in scope:
            return None, '无权处理该评论'
        target['status'] = status
        target['resolved_by'] = au.get('name') or ''
        target['resolved_at'] = datetime.now().isoformat(timespec='seconds') if status == 'resolved' else ''
        _save_comments(items)
        return target, None


def add_comment(au, payload):
    if not _can_comment(au):
        return None, '无评论权限（仅主管/经理/总监/管理员）'
    _load_source()
    aid = str(payload.get('activity_id') or '').strip()
    text = str(payload.get('text') or '').strip()
    if not aid:
        return None, '缺少活动 id'
    if not text:
        return None, '评论不能为空'
    if len(text) > 2000:
        return None, '评论不能超过 2000 字'
    rec = (_CACHE.get('by_id') or {}).get(aid)
    if not rec:
        return None, '活动不存在或已过期'
    scope = _scope_names(au)
    if scope is not None and rec.get('sales') not in scope:
        return None, '无权评论该销售员的活动'
    c = {
        'id': 'C' + uuid.uuid4().hex[:16],
        'activity_id': aid,
        'report_date': rec.get('date'),
        'sales': rec.get('sales'),
        'author': au.get('name') or '',
        'role': au.get('role') or '',
        'text': text,
        'status': 'open',
        'created_at': datetime.now().isoformat(timespec='seconds'),
    }
    with LOCK:
        items = _load_comments()
        items.append(c)
        _save_comments(items)
    return c, None
