# -*- coding: utf-8 -*-
"""
客户服务 —— CUST-1 客户详情、CUST-2 客户服务记录。

数据来源：
- ETL 直连（fdw_queries）：会员信息、余额、消费、关系指数、服务记录
- 业务库（biz.*）：AI 缓存、维客线索、备注、助教任务

⚠️ DWD-DOC 强制规则：
- 规则 1: 金额使用 items_sum 口径（ledger_amount），禁止 consume_money
- 规则 2: 助教费用使用 assistant_pd_money + assistant_cx_money，禁止 service_fee
- DQ-6: 会员信息通过 member_id JOIN v_dim_member (scd2_is_current=1)
- DQ-7: 余额通过 member_id JOIN v_dim_member_card_account (scd2_is_current=1)
- 废单排除: is_delete = 0
"""

from __future__ import annotations

import json
import logging

from fastapi import HTTPException

from decimal import Decimal

from app.services import fdw_queries
from app.services.task_generator import compute_heart_icon

logger = logging.getLogger(__name__)


# ── 颜色/样式映射 ──────────────────────────────────────────

LEVEL_COLOR_MAP = {
    "星级": "#FF6B6B",
    "高级": "#FFA726",
    "中级": "#42A5F5",
    "初级": "#66BB6A",
}

TASK_TYPE_MAP = {
    "follow_up_visit": {"label": "回访", "color": "#42A5F5", "bg_class": "bg-blue"},
    "high_priority_recall": {"label": "紧急召回", "color": "#FF6B6B", "bg_class": "bg-red"},
    "priority_recall": {"label": "优先召回", "color": "#FFA726", "bg_class": "bg-orange"},
}

LEVEL_BG_MAP = {
    "星级": "bg-red",
    "高级": "bg-orange",
    "中级": "bg-blue",
    "初级": "bg-green",
}


def _mask_phone(phone: str | None) -> str:
    """手机号脱敏：139****5678 格式。"""
    if not phone or len(phone) < 7:
        return phone or ""
    return f"{phone[:3]}****{phone[-4:]}"


def _get_biz_connection():
    """延迟导入业务库连接。"""
    from app.database import get_connection
    return get_connection()


# ── 3.1 核心函数 ──────────────────────────────────────────

async def get_customer_detail(customer_id: int, site_id: int) -> dict:
    """
    客户详情（CUST-1）。

    核心字段查询失败 → 500；扩展模块查询失败 → 空默认值（优雅降级）。
    """
    conn = _get_biz_connection()
    try:
        # ── 核心字段（失败直接抛 500）──
        member_info_map = fdw_queries.get_member_info(conn, site_id, [customer_id])
        if customer_id not in member_info_map:
            raise HTTPException(status_code=404, detail="客户不存在")

        info = member_info_map[customer_id]
        phone_full = info.get("mobile") or ""
        phone = _mask_phone(phone_full)
        name = info.get("nickname") or ""

        # Banner 字段：查询失败返回 null（需求 1.7）
        balance = None
        try:
            balance_map = fdw_queries.get_member_balance(conn, site_id, [customer_id])
            if customer_id in balance_map:
                balance = float(balance_map[customer_id])
        except Exception:
            logger.warning("查询 balance 失败，降级为 null", exc_info=True)

        consumption_60d = None
        try:
            val = fdw_queries.get_consumption_60d(conn, site_id, customer_id)
            if val is not None:
                consumption_60d = float(val)
        except Exception:
            logger.warning("查询 consumption_60d 失败，降级为 null", exc_info=True)

        days_since_visit = None
        try:
            visit_map = fdw_queries.get_last_visit_days(conn, site_id, [customer_id])
            if customer_id in visit_map:
                days_since_visit = visit_map[customer_id]
        except Exception:
            logger.warning("查询 daysSinceVisit 失败，降级为 null", exc_info=True)

        # ── 扩展模块（独立 try/except 优雅降级）──
        try:
            ai_insight = _build_ai_insight(customer_id, conn)
        except Exception:
            logger.warning("构建 aiInsight 失败，降级为空", exc_info=True)
            ai_insight = {"summary": "", "strategies": []}

        try:
            retention_clues = _build_retention_clues(customer_id, conn)
        except Exception:
            logger.warning("构建 retentionClues 失败，降级为空列表", exc_info=True)
            retention_clues = []

        try:
            notes = _build_notes(customer_id, conn)
        except Exception:
            logger.warning("构建 notes 失败，降级为空列表", exc_info=True)
            notes = []

        try:
            consumption_records = _build_consumption_records(customer_id, site_id, conn)
        except Exception:
            logger.warning("构建 consumptionRecords 失败，降级为空列表", exc_info=True)
            consumption_records = []

        try:
            coach_tasks = _build_coach_tasks(customer_id, site_id, conn)
        except Exception:
            logger.warning("构建 coachTasks 失败，降级为空列表", exc_info=True)
            coach_tasks = []

        try:
            favorite_coaches = _build_favorite_coaches(customer_id, site_id, conn)
        except Exception:
            logger.warning("构建 favoriteCoaches 失败，降级为空列表", exc_info=True)
            favorite_coaches = []

        return {
            "id": customer_id,
            "name": name,
            "phone": phone,
            "phone_full": phone_full,
            "avatar": "",
            "member_level": "",
            "relation_index": "",
            "tags": [],
            # Banner
            "balance": balance,
            "consumption_60d": consumption_60d,
            "ideal_interval": None,
            "days_since_visit": days_since_visit,
            # 扩展模块
            "ai_insight": ai_insight,
            "coach_tasks": coach_tasks,
            "favorite_coaches": favorite_coaches,
            "retention_clues": retention_clues,
            "consumption_records": consumption_records,
            "notes": notes,
        }
    finally:
        conn.close()


# ── 3.2 AI 洞察 / 维客线索 / 备注 ──────────────────────────


def _build_ai_insight(customer_id: int, conn) -> dict:
    """
    构建 aiInsight 模块。

    查询 biz.ai_cache WHERE cache_type='app4_analysis' AND target_id=customerId，
    解析 result_json JSON。无缓存时返回空默认值。
    """
    with conn.cursor() as cur:
        cur.execute(
            """
            SELECT result_json
            FROM biz.ai_cache
            WHERE cache_type = 'app4_analysis'
              AND target_id = %s
            ORDER BY created_at DESC
            LIMIT 1
            """,
            (str(customer_id),),
        )
        row = cur.fetchone()

    if not row or not row[0]:
        return {"summary": "", "strategies": []}

    try:
        data = json.loads(row[0]) if isinstance(row[0], str) else row[0]
    except (json.JSONDecodeError, TypeError):
        return {"summary": "", "strategies": []}

    summary = data.get("summary", "")
    strategies_raw = data.get("strategies", [])
    strategies = []
    for s in strategies_raw:
        if isinstance(s, dict):
            strategies.append({
                "color": s.get("color", ""),
                "text": s.get("text", ""),
            })

    return {"summary": summary, "strategies": strategies}


def _build_retention_clues(customer_id: int, conn) -> list[dict]:
    """
    构建 retentionClues 模块。

    查询 public.member_retention_clue，按 created_at 倒序。
    """
    with conn.cursor() as cur:
        cur.execute(
            """
            SELECT clue_type, clue_text
            FROM public.member_retention_clue
            WHERE member_id = %s
            ORDER BY created_at DESC
            """,
            (customer_id,),
        )
        rows = cur.fetchall()

    return [{"type": r[0] or "", "text": r[1] or ""} for r in rows]


def _build_notes(customer_id: int, conn) -> list[dict]:
    """
    构建 notes 模块。

    查询 biz.notes WHERE target_type='member'，最多 20 条，按 created_at 倒序。
    """
    with conn.cursor() as cur:
        cur.execute(
            """
            SELECT id, type, created_at, content
            FROM biz.notes
            WHERE target_type = 'member'
              AND target_id = %s
            ORDER BY created_at DESC
            LIMIT 20
            """,
            (customer_id,),
        )
        rows = cur.fetchall()

    return [
        {
            "id": r[0],
            "tag_label": r[1] or "",
            "created_at": r[2].isoformat() if r[2] else "",
            "content": r[3] or "",
        }
        for r in rows
    ]


# ── 3.3 消费记录 ──────────────────────────────────────────


def _build_consumption_records(
    customer_id: int, site_id: int, conn
) -> list[dict]:
    """
    构建 consumptionRecords 模块。

    调用 fdw_queries.get_consumption_records() 获取结算单列表。
    ⚠️ DWD-DOC 规则 1: totalAmount 使用 ledger_amount（items_sum 口径）。
    ⚠️ DWD-DOC 规则 2: coaches fee 使用 assistant_pd_money / assistant_cx_money。
    ⚠️ 废单排除: is_delete = 0，正向交易: settle_type IN (1, 3)。
    """
    raw_records = fdw_queries.get_consumption_records(
        conn, site_id, customer_id, limit=50, offset=0
    )

    result = []
    for rec in raw_records:
        # 构建 coaches 子数组
        coaches = []
        pd_money = rec.get("assistant_pd_money", 0.0)
        cx_money = rec.get("assistant_cx_money", 0.0)

        if pd_money:
            coaches.append({
                "name": rec.get("assistant_name", ""),
                "level": rec.get("level", ""),
                "level_color": LEVEL_COLOR_MAP.get(rec.get("level", ""), ""),
                "course_type": "基础课",
                "hours": rec.get("service_hours", 0.0),
                "perf_hours": None,
                "fee": pd_money,
            })
        if cx_money:
            coaches.append({
                "name": rec.get("assistant_name", ""),
                "level": rec.get("level", ""),
                "level_color": LEVEL_COLOR_MAP.get(rec.get("level", ""), ""),
                "course_type": "激励课",
                "hours": 0.0,
                "perf_hours": None,
                "fee": cx_money,
            })

        settle_time = rec.get("settle_time")
        date_str = settle_time.strftime("%Y-%m-%d") if settle_time else ""
        start_str = rec.get("start_time")
        end_str = rec.get("end_time")

        result.append({
            "id": rec.get("id", ""),
            "type": "table",
            "date": date_str,
            "table_name": str(rec.get("table_id")) if rec.get("table_id") else None,
            "start_time": start_str.isoformat() if start_str else None,
            "end_time": end_str.isoformat() if end_str else None,
            "duration": int(rec.get("service_hours", 0) * 60),
            "table_fee": rec.get("table_charge_money", 0.0),
            "table_orig_price": None,
            "coaches": coaches,
            "food_amount": rec.get("goods_money", 0.0),
            "food_orig_price": None,
            "total_amount": rec.get("total_amount", 0.0),
            "total_orig_price": rec.get("total_amount", 0.0),
            "pay_method": "",
            "recharge_amount": None,
        })

    return result


# ── 3.4 关联助教任务（T2-2）──────────────────────────────


def _build_coach_tasks(
    customer_id: int, site_id: int, conn
) -> list[dict]:
    """
    构建 coachTasks 模块。

    1. 查询 biz.coach_tasks WHERE member_id=customer_id
    2. 对每位助教：fdw_queries 获取等级、近 60 天统计
    3. 映射 levelColor/taskColor/bgClass
    """
    with conn.cursor() as cur:
        cur.execute(
            """
            SELECT id, assistant_id, task_type, status, updated_at
            FROM biz.coach_tasks
            WHERE member_id = %s
              AND status IN ('active', 'inactive')
            ORDER BY created_at DESC
            """,
            (customer_id,),
        )
        rows = cur.fetchall()

    if not rows:
        return []

    # 收集所有助教 ID，批量查询信息
    assistant_ids = list({r[1] for r in rows if r[1]})

    # 获取助教等级（通过 salary_calc）
    import datetime
    now = datetime.date.today()
    salary_map = {}
    for aid in assistant_ids:
        try:
            sc = fdw_queries.get_salary_calc(conn, site_id, aid, now.year, now.month)
            if sc:
                salary_map[aid] = sc
        except Exception:
            logger.warning("查询助教 %s 绩效失败", aid, exc_info=True)

    # 获取助教姓名
    assistant_info_map = {}
    for aid in assistant_ids:
        try:
            info = fdw_queries.get_assistant_info(conn, site_id, aid)
            if info:
                assistant_info_map[aid] = info
        except Exception:
            logger.warning("查询助教 %s 信息失败", aid, exc_info=True)

    result = []
    for row in rows:
        task_id, assistant_id, task_type, status, updated_at = row
        a_info = assistant_info_map.get(assistant_id, {})
        sc = salary_map.get(assistant_id, {})
        level = sc.get("coach_level", a_info.get("level", ""))
        name = a_info.get("name", "")

        task_meta = TASK_TYPE_MAP.get(task_type, {
            "label": task_type or "",
            "color": "#999",
            "bg_class": "bg-gray",
        })

        # 近 60 天统计
        try:
            stats = fdw_queries.get_coach_60d_stats(
                conn, site_id, assistant_id, customer_id
            )
        except Exception:
            stats = {"service_count": 0, "total_hours": 0.0, "avg_hours": 0.0}

        metrics = [
            {"label": "服务次数", "value": str(stats["service_count"]), "color": None},
            {"label": "总时长", "value": f"{stats['total_hours']:.1f}h", "color": None},
            {"label": "次均时长", "value": f"{stats['avg_hours']:.1f}h", "color": None},
        ]

        result.append({
            "name": name,
            "level": level,
            "level_color": LEVEL_COLOR_MAP.get(level, ""),
            "task_type": task_meta["label"],
            "task_color": task_meta["color"],
            "bg_class": LEVEL_BG_MAP.get(level, task_meta["bg_class"]),
            "status": status,
            "last_service": updated_at.isoformat() if updated_at else None,
            "metrics": metrics,
        })

    return result


# ── 3.5 最亲密助教（T2-3）──────────────────────────────


def _build_favorite_coaches(
    customer_id: int, site_id: int, conn
) -> list[dict]:
    """
    构建 favoriteCoaches 模块。

    1. fdw_queries.get_relation_index() → 关系指数列表（已按降序排列，rs_display 0-10 刻度）
    2. emoji 四级映射（P6 AC3）：>8.5→💖 / >7→🧡 / >5→💛 / ≤5→💙
    3. stats 4 项指标
    """
    relations = fdw_queries.get_relation_index(conn, site_id, customer_id)
    if not relations:
        return []

    # 获取助教姓名
    assistant_ids = [r["assistant_id"] for r in relations if r.get("assistant_id")]
    assistant_info_map = {}
    for aid in assistant_ids:
        try:
            info = fdw_queries.get_assistant_info(conn, site_id, aid)
            if info:
                assistant_info_map[aid] = info
        except Exception:
            logger.warning("查询助教 %s 信息失败", aid, exc_info=True)

    result = []
    for rel in relations:
        ri = rel.get("relation_index", 0.0)
        aid = rel.get("assistant_id")
        a_info = assistant_info_map.get(aid, {})

        # 4-level heart icon 映射（P6 AC3，rs_display 0-10 刻度）
        emoji = compute_heart_icon(Decimal(str(ri)))
        if ri > 8.5:
            index_color, bg_class = "#FF6B6B", "bg-red"
        elif ri > 7:
            index_color, bg_class = "#FF8C00", "bg-orange"
        elif ri > 5:
            index_color, bg_class = "#FFA726", "bg-yellow"
        else:
            index_color, bg_class = "#5B9BD5", "bg-blue"

        stats = [
            {"label": "基础课时", "value": f"¥{rel.get('total_income', 0):.0f}", "color": None},
            {"label": "激励课时", "value": "¥0", "color": None},
            {"label": "上课次数", "value": str(rel.get("service_count", 0)), "color": None},
            {"label": "总时长", "value": f"{rel.get('total_hours', 0):.1f}h", "color": None},
        ]

        result.append({
            "emoji": emoji,
            "name": a_info.get("name", ""),
            "relation_index": f"{ri:.2f}",
            "index_color": index_color,
            "bg_class": bg_class,
            "stats": stats,
        })

    return result


# ── CUST-2 客户服务记录（T2-4）──────────────────────────


async def get_customer_records(
    customer_id: int,
    site_id: int,
    year: int,
    month: int,
    table: str | None,
    page: int,
    page_size: int,
) -> dict:
    """
    客户服务记录（CUST-2）。

    1. fdw_queries.get_member_info() → customerName/customerPhone（DQ-6）
    2. fdw_queries.get_customer_service_records() → 按月分页记录 + total_count
    3. 聚合 monthCount/monthHours（从 total_count 和记录工时）
    4. fdw_queries.get_total_service_count() → totalServiceCount（跨月）
    5. 构建 ServiceRecordItem 列表，含 recordType/isEstimate
    6. hasMore = total_count > page * page_size
    """
    conn = _get_biz_connection()
    try:
        # ── 客户基础信息（DQ-6）──
        member_info_map = fdw_queries.get_member_info(conn, site_id, [customer_id])
        if customer_id not in member_info_map:
            raise HTTPException(status_code=404, detail="客户不存在")

        info = member_info_map[customer_id]
        phone_full = info.get("mobile") or ""
        phone = _mask_phone(phone_full)
        customer_name = info.get("nickname") or ""

        # ── 按月分页服务记录 ──
        offset = (page - 1) * page_size
        records_raw, total_count = fdw_queries.get_customer_service_records(
            conn, site_id, customer_id,
            year, month, table,
            limit=page_size, offset=offset,
        )

        # ── 月度统计汇总（从全量 total_count + 当页记录工时聚合）──
        # monthCount = 当月总记录数（不是当页），monthHours = 当月总工时
        # 需要单独查询当月汇总，因为分页记录只是子集
        month_count, month_hours = _get_month_aggregation(
            conn, site_id, customer_id, year, month, table
        )

        # ── 累计服务总次数（跨所有月份）──
        total_service_count = fdw_queries.get_total_service_count(
            conn, site_id, customer_id
        )

        # ── 构建记录列表 ──
        records = []
        for rec in records_raw:
            create_time = rec.get("create_time")
            date_str = create_time.strftime("%Y-%m-%d") if create_time else ""
            start_time = rec.get("start_time")
            end_time = rec.get("end_time")

            # 时间范围格式化
            time_range = None
            if start_time and end_time:
                time_range = f"{start_time.strftime('%H:%M')}-{end_time.strftime('%H:%M')}"

            # recordType: 根据 course_type 判断
            course_type = rec.get("course_type", "")
            record_type = "recharge" if "充值" in course_type else "course"

            # type / type_class 映射
            if record_type == "recharge":
                type_label = "充值"
                type_class = "tag-recharge"
            else:
                type_label = course_type or "课程"
                type_class = "tag-course"

            records.append({
                "id": str(rec.get("id", "")),
                "date": date_str,
                "time_range": time_range,
                "table": str(rec.get("table_id")) if rec.get("table_id") else None,
                "type": type_label,
                "type_class": type_class,
                "record_type": record_type,
                "duration": rec.get("service_hours", 0.0),
                "duration_raw": rec.get("service_hours_raw"),
                "income": rec.get("income", 0.0),
                "is_estimate": rec.get("is_estimate", False),
                "drinks": None,
            })

        has_more = total_count > page * page_size

        return {
            "customer_name": customer_name,
            "customer_phone": phone,
            "customer_phone_full": phone_full,
            "relation_index": "",
            "tables": [],
            "total_service_count": total_service_count,
            "month_count": month_count,
            "month_hours": round(month_hours, 2),
            "records": records,
            "has_more": has_more,
        }
    finally:
        conn.close()


def _get_month_aggregation(
    conn, site_id: int, customer_id: int,
    year: int, month: int, table: str | None,
) -> tuple[int, float]:
    """
    查询当月汇总统计（monthCount + monthHours）。

    复用 fdw_queries 的 _fdw_context 直连 ETL 库。
    ⚠️ 废单排除: is_delete = 0。
    """
    start_date = f"{year}-{month:02d}-01"
    if month == 12:
        end_date = f"{year + 1}-01-01"
    else:
        end_date = f"{year}-{month + 1:02d}-01"

    base_where = """
        tenant_member_id = %s
        AND is_delete = 0
        AND create_time >= %s::timestamptz
        AND create_time < %s::timestamptz
    """
    params: list = [customer_id, start_date, end_date]
    if table:
        base_where += " AND site_table_id::text = %s"
        params.append(table)

    with fdw_queries._fdw_context(conn, site_id) as cur:
        cur.execute(
            f"""
            SELECT COUNT(*) AS month_count,
                   COALESCE(SUM(income_seconds / 3600.0), 0) AS month_hours
            FROM app.v_dwd_assistant_service_log
            WHERE {base_where}
            """,
            params,
        )
        row = cur.fetchone()

    if not row:
        return 0, 0.0
    return row[0] or 0, float(row[1]) if row[1] is not None else 0.0
