"""
绩效服务

负责绩效概览（PERF-1）和绩效明细（PERF-2）的业务逻辑。
所有 FDW 查询通过 fdw_queries 模块执行，本模块不直接操作 SQL。

RNS1.1 组件 5。
"""

from __future__ import annotations

import logging
from collections import defaultdict
from datetime import datetime
from decimal import Decimal

from fastapi import HTTPException

from app.services import fdw_queries
from app.services.task_manager import _get_assistant_id, compute_income_trend
from app.trace.decorators import trace_service

logger = logging.getLogger(__name__)


def _get_connection():
    """延迟导入 get_connection，避免纯函数测试时触发模块级导入失败。"""
    from app.database import get_connection

    return get_connection()


# ---------------------------------------------------------------------------
# 纯函数：可被属性测试直接调用
# ---------------------------------------------------------------------------


@trace_service(description_zh="format_income_desc", description_en="Format Income Desc")
def format_income_desc(rate: float, hours: float) -> str:
    """
    格式化收入明细描述。

    格式: "{rate}元/h × {hours}h"
    """
    # 去除不必要的尾零
    rate_str = f"{rate:g}"
    hours_str = f"{hours:g}"
    return f"{rate_str}元/h × {hours_str}h"


@trace_service(description_zh="group_records_by_date", description_en="Group Records By Date")
def group_records_by_date(
    records: list[dict], *, include_avatar: bool = False,
    rs_map: dict[int, float] | None = None,
) -> list[dict]:
    """
    将服务记录按日期分组为 DateGroup 结构。

    参数:
        records: 服务记录列表（已按 settle_time DESC 排序）
        include_avatar: 是否包含 member_id（PERF-1 需要前端计算头像颜色）

    返回按日期倒序排列的 DateGroup 列表。
    """
    groups: dict[str, list[dict]] = defaultdict(list)

    for rec in records:
        settle_time = rec.get("settle_time")
        if settle_time is None:
            continue

        # 提取日期字符串
        if hasattr(settle_time, "strftime"):
            date_key = settle_time.strftime("%Y-%m-%d")
        else:
            date_key = str(settle_time)[:10]

        # 时间范围
        start_time = rec.get("start_time")
        end_time = rec.get("end_time")
        time_range = _format_time_range(start_time, end_time)

        # CHANGE 2026-03-24 | 课程类型直接用数据库原始值（skill_name），不做二次映射
        raw_course_type = rec.get("course_type", "") or "基础课"
        # 散客（member_id ≤ 0）展示"散客待转换会员"；
        # 真实会员姓名缺失时回退"未知客户"
        mid_for_name = rec.get("member_id")
        is_scattered = not mid_for_name or mid_for_name <= 0
        if is_scattered:
            customer_name = "散客待转换会员"
        else:
            customer_name = rec.get("customer_name") or "未知客户"

        record_item: dict = {
            "customer_name": customer_name,
            "is_scattered": is_scattered,
            "time_range": time_range,
            "hours": f"{rec.get('service_hours', 0.0):.1f}",
            "course_type": raw_course_type,
            "location": rec.get("table_name") or "",
            "income": f"{rec.get('income', 0.0):.2f}",
        }

        # CHANGE 2026-03-24 | 头像颜色改为前端根据 member_id 计算，后端只传 member_id 和首字
        if include_avatar:
            mid = rec.get("member_id")
            record_item["member_id"] = mid
            # 散客/未知客户（member_id 为空、0、负数）→ "?"
            if not mid or mid <= 0:
                record_item["avatar_char"] = "?"
            else:
                record_item["avatar_char"] = customer_name[0] if customer_name else "?"
            # CHANGE 2026-03-27 | 关系爱心标识：注入 heart_score（RS 分数）
            if rs_map and mid:
                record_item["heart_score"] = rs_map.get(mid, 0.0)
            else:
                record_item["heart_score"] = 0.0

        groups[date_key].append(record_item)

    # 按日期倒序排列
    sorted_dates = sorted(groups.keys(), reverse=True)
    result = []
    for date_key in sorted_dates:
        recs = groups[date_key]
        total_hours = sum(float(r["hours"]) for r in recs)
        total_income = sum(float(r["income"]) for r in recs)
        result.append({
            "date": date_key,
            "total_hours": f"{total_hours:.1f}",
            "total_income": f"{total_income:.2f}",
            "records": recs,
        })

    return result


@trace_service(description_zh="paginate_records", description_en="Paginate Records")
def paginate_records(
    records: list[dict], page: int, page_size: int
) -> tuple[list[dict], bool]:
    """
    对记录列表进行分页。

    返回 (当前页记录, has_more)。
    """
    total = len(records)
    start = (page - 1) * page_size
    end = start + page_size
    page_records = records[start:end]
    has_more = total > page * page_size
    return page_records, has_more


@trace_service(description_zh="compute_summary", description_en="Compute Summary")
def compute_summary(records: list[dict]) -> dict:
    """
    计算月度汇总。

    返回 { total_count, total_hours, total_hours_raw, total_income }。
    """
    total_count = len(records)
    total_hours = sum(r.get("service_hours", 0.0) for r in records)
    total_hours_raw = sum(r.get("service_hours_raw", 0.0) for r in records)
    total_income = sum(r.get("income", 0.0) for r in records)
    return {
        "total_count": total_count,
        "total_hours": round(total_hours, 2),
        "total_hours_raw": round(total_hours_raw, 2),
        "total_income": round(total_income, 2),
    }


def _format_time_range(start_time, end_time) -> str:
    """格式化时间范围为 "HH:MM-HH:MM" 格式。"""
    parts = []
    for t in (start_time, end_time):
        if t is None:
            parts.append("--:--")
        elif hasattr(t, "strftime"):
            parts.append(t.strftime("%H:%M"))
        else:
            s = str(t)
            # 尝试提取 HH:MM
            if len(s) >= 16:
                parts.append(s[11:16])
            else:
                parts.append(str(t))
    return f"{parts[0]}-{parts[1]}"


def _format_date_label(dt) -> str:
    """格式化日期为 "M月D日" 格式。"""
    if dt is None:
        return ""
    if hasattr(dt, "strftime"):
        return f"{dt.month}月{dt.day}日"
    s = str(dt)[:10]
    try:
        d = datetime.strptime(s, "%Y-%m-%d")
        return f"{d.month}月{d.day}日"
    except (ValueError, TypeError):
        return s


# ---------------------------------------------------------------------------
# PERF-1: 绩效概览
# ---------------------------------------------------------------------------


@trace_service("获取绩效概览", "Get performance overview")
async def get_overview(
    user_id: int, site_id: int, year: int, month: int
) -> dict:
    """
    绩效概览（PERF-1）。

    1. 获取 assistant_id
    2. fdw_queries.get_salary_calc() → 档位/收入/费率
    3. fdw_queries.get_service_records() → 按日期分组为 DateGroup（含 avatarChar/avatarColor）
    4. 聚合新客/常客列表
    5. 计算 incomeItems（含 desc 费率描述）
    6. 查询上月收入 lastMonthIncome
    """
    conn = _get_connection()
    try:
        assistant_id = _get_assistant_id(conn, user_id, site_id)

        # ── 1. 当月绩效数据 ──
        salary = fdw_queries.get_salary_calc(conn, site_id, assistant_id, year, month)

        # ── 2. 上月绩效数据（用于 lastMonthIncome） ──
        prev_year, prev_month = (year, month - 1) if month > 1 else (year - 1, 12)
        prev_salary = None
        try:
            prev_salary = fdw_queries.get_salary_calc(
                conn, site_id, assistant_id, prev_year, prev_month
            )
        except Exception:
            logger.warning("FDW 查询上月绩效失败", exc_info=True)

        last_month_income = prev_salary["total_income"] if prev_salary else 0.0

        # ── 3. 服务记录（全量，用于 DateGroup + 新客/常客） ──
        # 获取全部记录（不分页）
        all_records = fdw_queries.get_service_records(
            conn, site_id, assistant_id, year, month,
            limit=10000, offset=0,
        )

        # 按日期分组（含 avatar）
        # CHANGE 2026-03-27 | 批量查 RS 分数，注入到服务记录和客户列表
        member_ids = list({r.get("member_id") for r in all_records if r.get("member_id")})
        rs_map: dict[int, float] = {}
        if member_ids:
            try:
                with fdw_queries._fdw_context(conn, site_id) as cur:
                    cur.execute(
                        """
                        SELECT member_id, COALESCE(rs_display, 0)
                        FROM app.v_dws_member_assistant_relation_index
                        WHERE assistant_id = %s AND member_id = ANY(%s)
                        """,
                        (assistant_id, member_ids),
                    )
                    for row in cur.fetchall():
                        rs_map[row[0]] = float(row[1])
            except Exception:
                logger.warning("查询 RS 分数失败", exc_info=True)

        date_groups = group_records_by_date(all_records, include_avatar=True, rs_map=rs_map)

        # ── 4. 新客/常客列表 ──
        new_customers, regular_customers = _build_customer_lists(
            conn, site_id, assistant_id, year, month, all_records, rs_map=rs_map
        )

        # ── 5. 构建响应 ──
        # 助教信息（从 salary 或默认值）
        coach_name = ""
        coach_role = ""
        store_name = ""
        # ⚠️ auth 表结构: users(nickname), user_assistant_binding(binding_type)
        # auth.sites 不存在; users 无 display_name; uab 无 role_label
        try:
            with conn.cursor() as cur:
                cur.execute(
                    """
                    SELECT u.nickname, uab.binding_type
                    FROM auth.user_assistant_binding uab
                    JOIN auth.users u ON uab.user_id = u.id
                    WHERE uab.assistant_id = %s AND uab.site_id = %s
                      AND uab.is_removed = false
                    LIMIT 1
                    """,
                    (assistant_id, site_id),
                )
                row = cur.fetchone()
                if row:
                    coach_name = row[0] or ""
                    coach_role = row[1] or ""
            conn.commit()
        except Exception:
            logger.warning("查询助教信息失败", exc_info=True)

        current_income = salary["total_income"] if salary else 0.0
        # CHANGE 2026-03-24 | basic_rate/incentive_rate 改为助教到手单价（客户价 - 球房提成），
        # 不再使用 base_course_price/bonus_course_price（客户收费标准）
        base_course_price = salary["basic_rate"] if salary else 0.0       # 客户收费标准
        bonus_course_price = salary["incentive_rate"] if salary else 0.0  # 客户收费标准
        base_deduction = salary["base_deduction"] if salary else 0.0
        bonus_deduction_ratio = salary["bonus_deduction_ratio"] if salary else 0.0
        # 助教到手单价 = 客户价 - 球房提成
        basic_rate = base_course_price - base_deduction
        incentive_rate = bonus_course_price * (1 - bonus_deduction_ratio)

        basic_hours = salary["basic_hours"] if salary else 0.0
        bonus_hours = salary["bonus_hours"] if salary else 0.0
        pd_money = salary["assistant_pd_money_total"] if salary else 0.0
        cx_money = salary["assistant_cx_money_total"] if salary else 0.0
        top_rank_bonus = salary["top_rank_bonus"] if salary else 0.0
        recharge_commission = salary["recharge_commission"] if salary else 0.0

        # 收入明细项
        income_items = _build_income_items(
            basic_rate, incentive_rate, basic_hours, bonus_hours,
            pd_money, cx_money,
            top_rank_bonus=top_rank_bonus,
            recharge_commission=recharge_commission,
        )

        # CHANGE 2026-03-24 | 档位信息从 cfg_performance_tier 配置表计算，
        # 复用 task_manager._build_performance_summary 的逻辑
        total_hours = salary["total_hours"] if salary else 0.0
        tiers: list[dict] = []
        try:
            tiers = fdw_queries.get_performance_tiers(conn, site_id)
        except Exception:
            logger.warning("查询 cfg_performance_tier 失败", exc_info=True)

        # 找到当前档位和下一档
        tier_completed = False
        next_tier_hours = 0.0
        current_tier_data = None
        next_tier_data = None
        if tiers:
            for i, t in enumerate(tiers):
                if t["min_hours"] > total_hours:
                    next_tier_data = t
                    current_tier_data = tiers[i - 1] if i > 0 else tiers[0]
                    next_tier_hours = t["min_hours"]
                    break
            if next_tier_data is None:
                # 已达到或超过最高档
                tier_completed = True
                current_tier_data = tiers[-1]

        upgrade_hours_needed = max(0.0, next_tier_hours - total_hours) if not tier_completed else 0.0

        # 下一档到手费率
        if next_tier_data:
            next_basic_rate = base_course_price - next_tier_data["base_deduction"]
            next_incentive_rate = bonus_course_price * (1 - next_tier_data["bonus_deduction_ratio"])
        else:
            next_basic_rate = 0.0
            next_incentive_rate = 0.0

        # bonus_money: 升到下一档后因抽成降低能多拿的钱
        # 公式同 task_manager._build_performance_summary
        upgrade_bonus = 0.0
        if not tier_completed and current_tier_data and next_tier_data:
            base_ded_diff = current_tier_data["base_deduction"] - next_tier_data["base_deduction"]
            base_saving = next_tier_data["min_hours"] * base_ded_diff if base_ded_diff > 0 else 0.0
            bonus_ratio_diff = current_tier_data["bonus_deduction_ratio"] - next_tier_data["bonus_deduction_ratio"]
            bonus_saving = bonus_hours * bonus_course_price * bonus_ratio_diff if bonus_ratio_diff > 0 else 0.0
            upgrade_bonus = round(base_saving + bonus_saving, 2)

        return {
            "coach_name": coach_name,
            "coach_role": coach_role,
            "store_name": store_name,
            "monthly_income": f"¥{current_income:,.0f}",
            "last_month_income": f"¥{last_month_income:,.0f}",
            "current_tier": {
                "basic_rate": basic_rate,
                "incentive_rate": incentive_rate,
            },
            "next_tier": {
                "basic_rate": next_basic_rate,
                "incentive_rate": next_incentive_rate,
            },
            "upgrade_hours_needed": round(upgrade_hours_needed, 2),
            "upgrade_bonus": upgrade_bonus,
            "income_items": income_items,
            "monthly_total": f"¥{current_income:,.2f}",
            "this_month_records": date_groups,
            "new_customers": new_customers,
            "regular_customers": regular_customers,
        }

    finally:
        conn.close()


def _build_income_items(
    basic_rate: float,
    incentive_rate: float,
    basic_hours: float,
    bonus_hours: float,
    pd_money: float,
    cx_money: float,
    *,
    top_rank_bonus: float = 0.0,
    recharge_commission: float = 0.0,
) -> list[dict]:
    """
    构建收入明细项列表。

    CHANGE 2026-03-24 | 始终显示所有项（基础课、激励课、Top3销冠奖、充值提成），即使为 0；
    Top3销冠奖为 0 时 desc 显示"继续努力"。
    """
    items = []

    # 基础课收入（始终显示）
    items.append({
        "icon": "💰",
        "label": "基础课收入",
        "desc": format_income_desc(basic_rate, basic_hours),
        "value": f"¥{pd_money:,.2f}",
    })

    # 激励课收入（始终显示）
    items.append({
        "icon": "🎯",
        "label": "激励课收入",
        "desc": format_income_desc(incentive_rate, bonus_hours),
        "value": f"¥{cx_money:,.2f}",
    })

    # Top3销冠奖（始终显示，为 0 时 desc 显示"继续努力"）
    items.append({
        "icon": "🏆",
        "label": "Top3销冠奖",
        "desc": "继续努力" if top_rank_bonus == 0 else "本月销冠奖励",
        "value": f"¥{top_rank_bonus:,.2f}",
    })

    # CHANGE 2026-03-24 | 充值提成（始终显示）
    items.append({
        "icon": "💳",
        "label": "充值提成",
        "desc": "充值激励",
        "value": f"¥{recharge_commission:,.2f}",
    })

    return items


def _build_customer_lists(
    conn,
    site_id: int,
    assistant_id: int,
    year: int,
    month: int,
    all_records: list[dict],
    *,
    rs_map: dict[int, float] | None = None,
) -> tuple[list[dict], list[dict]]:
    """
    构建新客和常客列表。

    新客: 本月有服务记录但本月之前无记录的客户
    常客: 本月服务次数 ≥ 2 的客户（统计数据拉近90天）

    CHANGE 2026-03-24 | 头像颜色改为前端根据 member_id 计算，后端只传 member_id 和首字。
    CHANGE 2026-03-24 | 常客展示数据改为近90天聚合（判定标准不变：本月≥2次）。
    """
    if not all_records:
        return [], []

    # 按 member_id 聚合本月记录
    member_stats: dict[int, dict] = {}
    for rec in all_records:
        mid = rec.get("member_id")
        if mid is None:
            continue
        if mid not in member_stats:
            member_stats[mid] = {
                "customer_name": rec.get("customer_name") or "未知客户",
                "count": 0,
                "total_hours": 0.0,
                "total_income": 0.0,
                "last_service": rec.get("settle_time"),
            }
        stats = member_stats[mid]
        stats["count"] += 1
        stats["total_hours"] += rec.get("service_hours", 0.0)
        stats["total_income"] += rec.get("income", 0.0)
        if stats["last_service"] is None:
            stats["last_service"] = rec.get("settle_time")

    member_ids = list(member_stats.keys())

    # 查询历史记录（本月之前是否有服务记录）— 用于新客判定
    historical_members: set[int] = set()
    try:
        start_date = f"{year}-{month:02d}-01"
        with fdw_queries._fdw_context(conn, site_id) as cur:
            cur.execute(
                """
                SELECT DISTINCT tenant_member_id
                FROM app.v_dwd_assistant_service_log
                WHERE site_assistant_id = %s
                  AND is_delete = 0
                  AND create_time < %s::timestamptz
                  AND tenant_member_id = ANY(%s)
                """,
                (assistant_id, start_date, member_ids),
            )
            for row in cur.fetchall():
                historical_members.add(row[0])
    except Exception:
        logger.warning("查询历史客户记录失败", exc_info=True)

    # 查询近90天聚合数据（常客展示用）
    if month == 12:
        next_month_start = f"{year + 1}-01-01"
    else:
        next_month_start = f"{year}-{month + 1:02d}-01"
    stats_90d: dict[int, dict] = {}
    try:
        rows_90d = fdw_queries.get_service_records_90days(
            conn, site_id, assistant_id, next_month_start,
        )
        for r in rows_90d:
            stats_90d[r["member_id"]] = r
    except Exception:
        logger.warning("查询近90天服务记录失败", exc_info=True)

    new_customers = []
    regular_customers = []

    for mid, stats in member_stats.items():
        # CHANGE 2026-03-27 | 过滤散客/未知客户（member_id ≤ 0），不进入新客和常客列表
        if mid <= 0:
            continue
        name = stats["customer_name"]
        char = name[0] if name else "?"

        # 新客：历史无记录
        if mid not in historical_members:
            last_service_dt = stats["last_service"]
            new_customers.append({
                "name": name,
                "member_id": mid,
                "avatar_char": char,
                "last_service": _format_date_label(last_service_dt),
                "count": stats["count"],
                "heart_score": rs_map.get(mid, 0.0) if rs_map else 0.0,
            })

        # 常客：本月 ≥ 2 次（展示数据用90天聚合）
        if stats["count"] >= 2:
            s90 = stats_90d.get(mid)
            if s90:
                reg_count = s90["count"]
                reg_hours = round(s90["total_hours"], 2)
                reg_income = s90["total_income"]
            else:
                # 90天查询失败时回退到本月数据
                reg_count = stats["count"]
                reg_hours = round(stats["total_hours"], 2)
                reg_income = stats["total_income"]

            regular_customers.append({
                "name": name,
                "member_id": mid,
                "avatar_char": char,
                "hours": reg_hours,
                "income": f"¥{reg_income:,.2f}",
                "count": reg_count,
                "heart_score": rs_map.get(mid, 0.0) if rs_map else 0.0,
            })

    # 新客按最后服务时间倒序
    new_customers.sort(key=lambda x: x.get("last_service", ""), reverse=True)
    # 常客按收入倒序
    regular_customers.sort(
        key=lambda x: float(x.get("income", "¥0").replace("¥", "").replace(",", "")),
        reverse=True,
    )

    return new_customers, regular_customers


# ---------------------------------------------------------------------------
# PERF-2: 绩效明细
# ---------------------------------------------------------------------------


@trace_service("获取绩效明细", "Get performance records")
async def get_records(
    user_id: int, site_id: int,
    year: int, month: int, page: int, page_size: int,
    assistant_id_override: int | None = None,
) -> dict:
    """
    绩效明细（PERF-2）。

    1. 获取 assistant_id（assistant_id_override 非空时直接使用，跳过 user 绑定查询）
    2. fdw_queries.get_service_records() 带分页
    3. 按日期分组为 dateGroups（不含 avatarChar/avatarColor）
    4. 计算 summary 汇总
    5. 返回 { summary, dateGroups, hasMore }

    assistant_id_override 用于"管理员/店长查看其他助教"场景，
    调用方负责完成越权校验后再传入目标 assistant_id。
    """
    conn = _get_connection()
    try:
        if assistant_id_override is not None:
            assistant_id = assistant_id_override
        else:
            assistant_id = _get_assistant_id(conn, user_id, site_id)

        # CHANGE 2026-04-20 | 性能优化：summary 改用 SQL 聚合，
        # 不再先 limit=100000 全量拉取再 Python 算 summary
        summary = fdw_queries.get_service_records_summary(
            conn, site_id, assistant_id, year, month,
        )

        # 分页获取记录
        offset = (page - 1) * page_size
        page_records = fdw_queries.get_service_records(
            conn, site_id, assistant_id, year, month,
            limit=page_size, offset=offset,
        )

        # 判断 hasMore（由 summary.total_count 直接推算，避免再次拉全量）
        has_more = summary["total_count"] > page * page_size

        # CHANGE 2026-03-27 | 批量查 RS 分数，注入到服务记录
        page_member_ids = list({r.get("member_id") for r in page_records if r.get("member_id")})
        rs_map: dict[int, float] = {}
        if page_member_ids:
            try:
                with fdw_queries._fdw_context(conn, site_id) as cur:
                    cur.execute(
                        """
                        SELECT member_id, COALESCE(rs_display, 0)
                        FROM app.v_dws_member_assistant_relation_index
                        WHERE assistant_id = %s AND member_id = ANY(%s)
                        """,
                        (assistant_id, page_member_ids),
                    )
                    for row in cur.fetchall():
                        rs_map[row[0]] = float(row[1])
            except Exception:
                logger.warning("PERF-2 查询 RS 分数失败", exc_info=True)

        # 按日期分组（含 member_id / avatar_char，前端计算头像颜色）
        date_groups = group_records_by_date(page_records, include_avatar=True, rs_map=rs_map)

        return {
            "summary": summary,
            "date_groups": date_groups,
            "has_more": has_more,
        }

    finally:
        conn.close()
