#!/usr/bin/env python3
"""
渠道4-5月线索 - 退款相关性分析 V2
修正：考虑 Sheet2 中一个用户多个角色的情况

新增分析维度：
- 角色数量 vs 退款
- 多角色行课覆盖（全角色无行课/部分有/全有）
- 角色创建时间跨度
"""

import pandas as pd
import numpy as np
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
import glob, sys
from datetime import datetime

U0_COLS = ['L1_U00_L01_完成时间', 'L1_U00_L02_完成时间', 'L1_U00_L03_完成时间',
           'L1_U00_L04_完成时间', 'L1_U00_L05_完成时间',
           'L2_U00_L01_完成时间', 'L2_U00_L02_完成时间', 'L2_U00_L03_完成时间',
           'L2_U00_L04_完成时间', 'L2_U00_L05_完成时间']
L1_COLS = U0_COLS[:5]
L2_COLS = U0_COLS[5:]

def is_valid(val):
    """检查值是否有效的非空值"""
    if pd.isna(val) or str(val).strip() in ('', 'nan', 'None'):
        return False
    return True

def load_data():
    files = glob.glob('/root/.openclaw/media/inbound/*d58af024*')
    if not files:
        print("未找到输入文件"); sys.exit(1)
    print(f"读取: {files[0]}")
    df1 = pd.read_excel(files[0], sheet_name='Sheet1', dtype=str)
    df2 = pd.read_excel(files[0], sheet_name='Sheet2', dtype=str)
    return df1, df2

def count_role_lessons(row):
    """计算单个角色的U0完成课数"""
    l1 = sum(1 for c in L1_COLS if is_valid(row.get(c)))
    l2 = sum(1 for c in L2_COLS if is_valid(row.get(c)))
    return l1, l2, l1 + l2

def aggregate_user(group):
    """按用户聚合Sheet2，保留多角色信息"""
    row = {}
    n_roles = len(group)
    row['角色数量'] = n_roles
    row['注册时间'] = group['用户注册时间'].iloc[0]

    # 角色创建时间
    create_times = pd.to_datetime(group['角色创建时间'], errors='coerce').dropna()
    row['首角色创建时间'] = create_times.min() if len(create_times) > 0 else pd.NaT
    row['末角色创建时间'] = create_times.max() if len(create_times) > 0 else pd.NaT
    if len(create_times) >= 2:
        row['角色创建跨度_天'] = (create_times.max() - create_times.min()).total_seconds() / 86400
    else:
        row['角色创建跨度_天'] = 0

    # 每个角色的U0完成课数
    role_lesson_counts = []
    role_l1_counts = []
    role_l2_counts = []
    for _, r in group.iterrows():
        l1, l2, total = count_role_lessons(r)
        role_l1_counts.append(l1)
        role_l2_counts.append(l2)
        role_lesson_counts.append(total)

    row['各角色U0完成课数'] = str(role_lesson_counts)
    row['角色中最大完成课数'] = max(role_lesson_counts)
    row['角色中最小完成课数'] = min(role_lesson_counts)
    row['所有角色总完成课数'] = sum(role_lesson_counts)
    row['平均每角色完成课数'] = round(sum(role_lesson_counts) / n_roles, 1)

    # L1/L2 分别统计（取各角色最大值）
    row['L1最大完成课数'] = max(role_l1_counts)
    row['L2最大完成课数'] = max(role_l2_counts)

    # 行课覆盖情况
    roles_with_learning = sum(1 for c in role_lesson_counts if c > 0)
    if roles_with_learning == 0:
        row['行课覆盖'] = '全角色无行课'
    elif roles_with_learning == n_roles:
        row['行课覆盖'] = '全角色有行课'
    else:
        row['行课覆盖'] = f'部分有行课({roles_with_learning}/{n_roles})'

    # 简化版：是否有任何角色行课
    row['是否有行课'] = '有' if roles_with_learning > 0 else '无'

    # 行课类型（基于所有角色聚合）
    total_l1 = sum(role_l1_counts)
    total_l2 = sum(role_l2_counts)
    if total_l1 > 0 and total_l2 > 0:
        row['行课类型'] = 'L1+L2都有'
    elif total_l1 > 0:
        row['行课类型'] = '仅L1'
    elif total_l2 > 0:
        row['行课类型'] = '仅L2'
    else:
        row['行课类型'] = '无行课'

    # 角色创建节奏（首末角色时间差分类）
    span = row['角色创建跨度_天']
    if n_roles == 1:
        row['角色创建节奏'] = '单角色'
    elif span <= 1:
        row['角色创建节奏'] = '同日多角色'
    elif span <= 7:
        row['角色创建节奏'] = '1周内多角色'
    else:
        row['角色创建节奏'] = '1周以上多角色'

    # 首角色行课情况
    row['首角色完成课数'] = role_lesson_counts[0] if role_lesson_counts else 0

    # U0各课完成（任一角色完成即算）
    for c in U0_COLS:
        vals = group[c].dropna()
        vals = [v for v in vals if str(v).strip() not in ('', 'nan')]
        row[c] = vals[0] if vals else ''

    return pd.Series(row)

def prepare_data(df1, df2):
    """匹配并准备分析数据"""
    df1['账号ID_clean'] = pd.to_numeric(df1['账号ID'], errors='coerce')
    df1['is_refund'] = (df1['订单状态'] == '已退款').map({True: '是', False: '否'})

    df2['用户ID_num'] = pd.to_numeric(df2['用户ID'], errors='coerce')
    df2_agg = df2.groupby('用户ID_num', as_index=False).apply(aggregate_user).reset_index()

    merged = df1.merge(df2_agg, left_on='账号ID_clean', right_on='用户ID_num', how='left')
    merged['匹配状态'] = merged['用户ID_num'].notna().map({True: '已匹配', False: '未匹配'})

    # 衍生字段
    merged['注册月份'] = pd.to_datetime(merged['注册时间'], errors='coerce').dt.to_period('M').astype(str).fillna('未知')
    merged['角色创建月份'] = pd.to_datetime(merged['首角色创建时间'], errors='coerce').dt.to_period('M').astype(str).fillna('未知')

    reg_dt = pd.to_datetime(merged['注册时间'], errors='coerce')
    create_dt = pd.to_datetime(merged['首角色创建时间'], errors='coerce')
    diff_days = (create_dt - reg_dt).dt.total_seconds() / 86400
    def day_bucket(d):
        if pd.isna(d) or d < 0: return '未知'
        elif d <= 1: return '≤1天'
        elif d <= 7: return '2-7天'
        elif d <= 30: return '8-30天'
        elif d <= 90: return '31-90天'
        else: return '>90天'
    merged['注册到首角色创建间隔'] = diff_days.apply(day_bucket)

    # 角色数量分段
    def role_bucket(n):
        if pd.isna(n): return '未知'
        n = int(n)
        if n == 1: return '1个角色'
        if n == 2: return '2个角色'
        if n == 3: return '3个角色'
        return '≥4个角色'
    merged['角色数量分段'] = merged['角色数量'].apply(role_bucket)

    return merged

def calc_stats(df, col, refund_col='is_refund'):
    total = df.groupby(col).size().reset_index(name='总数')
    refund = df[df[refund_col] == '是'].groupby(col).size().reset_index(name='退款数')
    result = total.merge(refund, on=col, how='left')
    result['退款数'] = result['退款数'].fillna(0).astype(int)
    result['退费率'] = (result['退款数'] / result['总数'] * 100).round(1).apply(lambda x: f'{x}%')
    return result.sort_values('总数', ascending=False)

def analyze(df):
    results = {}

    # 原有维度
    results['1-渠道'] = calc_stats(df, '渠道')
    results['2-课包'] = calc_stats(df, '课包')
    results['3-电话接通'] = calc_stats(df, '电话已接通')

    # 渠道×课包交叉
    cross = df.groupby(['渠道', '课包']).agg(总数=('is_refund','count'), 退款数=('is_refund',lambda x:(x=='是').sum())).reset_index()
    cross['退费率'] = (cross['退款数']/cross['总数']*100).round(1).apply(lambda x:f'{x}%')
    results['4-渠道×课包'] = cross.sort_values('总数', ascending=False)

    # 电话×课包
    cross2 = df.groupby(['电话已接通', '课包']).agg(总数=('is_refund','count'), 退款数=('is_refund',lambda x:(x=='是').sum())).reset_index()
    cross2['退费率'] = (cross2['退款数']/cross2['总数']*100).round(1).apply(lambda x:f'{x}%')
    results['5-电话×课包'] = cross2.sort_values('总数', ascending=False)

    # ====== 新增：多角色维度 ======
    results['6-角色数量'] = calc_stats(df, '角色数量分段')
    results['7-角色创建节奏'] = calc_stats(df, '角色创建节奏')
    results['8-行课覆盖(多角色)'] = calc_stats(df, '行课覆盖')

    # 角色数量×行课交叉
    cross3 = df.groupby(['角色数量分段', '是否有行课']).agg(总数=('is_refund','count'), 退款数=('is_refund',lambda x:(x=='是').sum())).reset_index()
    cross3['退费率'] = (cross3['退款数']/cross3['总数']*100).round(1).apply(lambda x:f'{x}%')
    results['9-角色数×是否有行课'] = cross3.sort_values('总数', ascending=False)

    # 角色创建跨度
    def span_bucket(d):
        if pd.isna(d): return '未知'
        d = float(d)
        if d == 0: return '同日/单角色'
        if d <= 7: return '1-7天'
        if d <= 30: return '8-30天'
        return '>30天'
    df['创建跨度分段'] = df['角色创建跨度_天'].apply(span_bucket)
    results['10-角色创建时间跨度'] = calc_stats(df, '创建跨度分段')

    # 原有学习维度
    results['11-是否有行课'] = calc_stats(df, '是否有行课')
    results['12-行课类型'] = calc_stats(df, '行课类型')
    results['13-角色中最大完成课数'] = calc_stats(df, '角色中最大完成课数')
    results['14-L1最大完成课数'] = calc_stats(df, 'L1最大完成课数')
    results['15-L2最大完成课数'] = calc_stats(df, 'L2最大完成课数')

    # 注册时间
    results['16-注册月份'] = calc_stats(df, '注册月份')
    results['17-角色创建月份'] = calc_stats(df, '角色创建月份')
    results['18-注册到角色创建间隔'] = calc_stats(df, '注册到首角色创建间隔')
    results['19-匹配状态'] = calc_stats(df, '匹配状态')

    # 角色数量×课包交叉
    cross4 = df[df['角色数量分段'] != '未知'].groupby(['角色数量分段', '课包']).agg(总数=('is_refund','count'), 退款数=('is_refund',lambda x:(x=='是').sum())).reset_index()
    cross4['退费率'] = (cross4['退款数']/cross4['总数']*100).round(1).apply(lambda x:f'{x}%')
    results['20-角色数量×课包'] = cross4.sort_values('总数', ascending=False)

    # 整体
    total_count = len(df)
    refund_count = (df['is_refund'] == '是').sum()
    matched = (df['匹配状态'] == '已匹配').sum()
    results['_整体'] = {
        '总订单数': total_count,
        '退款订单数': refund_count,
        '退费率': f'{refund_count/total_count*100:.1f}%',
        '已匹配数': matched,
        '未匹配数': total_count - matched,
        '单角色用户数': (df['角色数量'] == 1).sum(),
        '多角色用户数': (df['角色数量'] > 1).sum(),
        'Sheet2总角色行数': len(df[df['匹配状态']=='已匹配']),
    }
    # 角色分布
    for rc in sorted(df['角色数量'].dropna().unique()):
        cnt = (df['角色数量'] == rc).sum()
        ref = ((df['角色数量'] == rc) & (df['is_refund'] == '是')).sum()
        results['_整体'][f'  {int(rc)}个角色'] = f'{int(cnt)}单, 退款{int(ref)} ({ref/cnt*100:.1f}%)'

    return results, df

HEADER_FILL = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
HEADER_FONT = Font(bold=True, size=11, color='FFFFFF')
THIN_BORDER = Border(left=Side(style='thin'), right=Side(style='thin'),
                     top=Side(style='thin'), bottom=Side(style='thin'))
RED_FILL = PatternFill(start_color='FFC7CE', end_color='FFC7CE', fill_type='solid')
YELLOW_FILL = PatternFill(start_color='FFF2CC', end_color='FFF2CC', fill_type='solid')
GREEN_FILL = PatternFill(start_color='C6EFCE', end_color='C6EFCE', fill_type='solid')

def write_excel(results, df, output_path):
    wb = Workbook()
    wb.remove(wb.active)

    # 汇总页
    ws = wb.create_sheet('汇总')
    ws.append(['指标', '值'])
    for k, v in results['_整体'].items():
        ws.append([k, v])
    ws.append([])
    ws.append(['分析维度摘要（按退费率降序）'])

    # 收集所有维度的退费率最高项
    for name, stat in results.items():
        if name.startswith('_') or not isinstance(stat, pd.DataFrame) or len(stat) == 0:
            continue
        if '退费率' not in stat.columns:
            continue
        # 找退费率极值
        rates = stat['退费率'].str.replace('%','').astype(float)
        max_idx = rates.idxmax()
        min_idx = rates.idxmin()
        first_col = stat.columns[0]
        ws.append([name, f'🔴最高: {first_col}={stat.iloc[max_idx][first_col]} ({stat.iloc[max_idx]["退费率"]})'])
        ws.append(['', f'🟢最低: {first_col}={stat.iloc[min_idx][first_col]} ({stat.iloc[min_idx]["退费率"]})'])

    ws.column_dimensions['A'].width = 35
    ws.column_dimensions['B'].width = 70
    for row in ws.iter_rows():
        for cell in row:
            if cell.row == 1:
                cell.font = HEADER_FONT
                cell.fill = HEADER_FILL

    # 各维度Sheet
    for name, stat in results.items():
        if name.startswith('_') or not isinstance(stat, pd.DataFrame):
            continue
        ws = wb.create_sheet(name[:31])

        for ci, cn in enumerate(stat.columns, 1):
            c = ws.cell(row=1, column=ci, value=cn)
            c.font = HEADER_FONT; c.fill = HEADER_FILL; c.border = THIN_BORDER
            c.alignment = Alignment(horizontal='center')

        for ri, row in enumerate(stat.itertuples(index=False), 2):
            for ci, val in enumerate(row, 1):
                c = ws.cell(row=ri, column=ci, value=val)
                c.border = THIN_BORDER; c.alignment = Alignment(horizontal='center')
                # 退费率列标色
                if '退费率' in stat.columns and ci == len(row):
                    try:
                        r = float(str(val).replace('%',''))
                        if r >= 60: c.fill = RED_FILL; c.font = Font(color='9C0006', bold=True)
                        elif r >= 45: c.fill = YELLOW_FILL
                        elif r < 30: c.fill = GREEN_FILL
                    except: pass

        for ci in range(1, len(stat.columns)+1):
            try:
                max_len = max(len(str(stat.columns[ci-1])), stat.iloc[:,ci-1].astype(str).str.len().max())
                ws.column_dimensions[chr(64+ci) if ci<=26 else 'A'].width = min(max_len+4, 28)
            except: pass

    # 匹配明细
    ws = wb.create_sheet('匹配明细')
    detail_cols = ['下单日期', '手机号', '渠道', '课包', '订单状态', '电话已接通', '账号ID',
                   '匹配状态', '角色数量', '角色创建节奏', '创建跨度分段', '行课覆盖',
                   '是否有行课', '行课类型', '角色中最大完成课数', 'L1最大完成课数', 'L2最大完成课数',
                   '注册时间', '首角色创建时间', '注册到首角色创建间隔']
    available = [c for c in detail_cols if c in df.columns]
    detail = df[available].copy()

    for ci, cn in enumerate(available, 1):
        c = ws.cell(row=1, column=ci, value=cn)
        c.font = HEADER_FONT; c.fill = HEADER_FILL; c.border = THIN_BORDER
        c.alignment = Alignment(horizontal='center')

    for ri, row in enumerate(detail.itertuples(index=False), 2):
        for ci, val in enumerate(row, 1):
            c = ws.cell(row=ri, column=ci, value=val if pd.notna(val) else '')
            c.border = THIN_BORDER
            if row[4] == '已退款':
                c.fill = YELLOW_FILL

    wb.save(output_path)
    print(f"报表已保存: {output_path}")

def main():
    df1, df2 = load_data()
    merged = prepare_data(df1, df2)

    print(f"\n=== 数据概况 ===")
    print(f"订单总数: {len(merged)}")
    print(f"已匹配: {(merged['匹配状态']=='已匹配').sum()}, 未匹配: {(merged['匹配状态']=='未匹配').sum()}")
    print(f"退款: {(merged['is_refund']=='是').sum()}, 未退款: {(merged['is_refund']=='否').sum()}")
    print(f"\n角色分布:")
    for rc in sorted(merged['角色数量'].dropna().unique()):
        cnt = (merged['角色数量']==rc).sum()
        ref = ((merged['角色数量']==rc)&(merged['is_refund']=='是')).sum()
        print(f"  {int(rc)}个角色: {int(cnt)}单, 退款{int(ref)} ({ref/cnt*100:.1f}%)")

    results, merged = analyze(merged)

    print(f"\n=== 关键维度退费率 ===")
    key_dims = ['6-角色数量', '7-角色创建节奏', '8-行课覆盖(多角色)', '9-角色数×是否有行课', '10-角色创建时间跨度']
    for name in key_dims:
        if name in results and isinstance(results[name], pd.DataFrame):
            print(f"\n--- {name} ---")
            print(results[name].to_string(index=False))

    ts = datetime.now().strftime('%Y%m%d_%H%M%S')
    output_path = f'/root/.openclaw/workspace/output/渠道4-5月线索_退款相关性分析_v2_{ts}.xlsx'
    write_excel(results, merged, output_path)
    return output_path

if __name__ == '__main__':
    main()
