"""
备注服务

负责备注 CRUD、星星评分存储与读取。
备注类型根据关联任务自动确定：follow_up_visit 任务 → follow_up，否则 normal。
星星评分不参与回访完成判定，不参与 AI 分析，仅存储。
"""

import json
import logging

from fastapi import HTTPException
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()


def _record_history(
    cur,
    task_id: int,
    action: str,
    old_status: str | None = None,
    new_status: str | None = None,
    old_task_type: str | None = None,
    new_task_type: str | None = None,
    detail: dict | None = None,
) -> None:
    """在 coach_task_history 中记录变更（复用 task_manager 的模式）。"""
    cur.execute(
        """
        INSERT INTO biz.coach_task_history
            (task_id, action, old_status, new_status,
             old_task_type, new_task_type, detail)
        VALUES (%s, %s, %s, %s, %s, %s, %s)
        """,
        (
            task_id,
            action,
            old_status,
            new_status,
            old_task_type,
            new_task_type,
            json.dumps(detail) if detail else None,
        ),
    )



@trace_service(description_zh="ai_analyze_note", description_en="Ai Analyze Note")
async def ai_analyze_note(note_id: int, site_id: int, member_id: int, content: str, user_name: str = "") -> int | None:
    """
    AI 应用 6 备注分析：调用百炼 Application API 获取评分。

    CHANGE 2026-03-27 | 打通 AI 应用 6 调用链
    仅执行 App6 评分，不触发 App8 线索整合（后续统一处理）。
    返回 score（1-10），失败返回 None。
    """
    try:
        from app.ai.config import AIConfig
        from app.ai.dashscope_client import DashScopeClient
        import json

        config = AIConfig.from_env()
        client = DashScopeClient(api_key=config.api_key, workspace_id=config.workspace_id)

        # 构建 prompt（简化版，直接传给百炼应用）
        prompt = json.dumps({
            "site_id": site_id,
            "member_id": member_id,
            "note_content": content,
            "noted_by_name": user_name,
        }, ensure_ascii=False)

        result, tokens_used, _ = await client.call_app(config.app_id_6_note, prompt)
        score = result.get("score") if isinstance(result, dict) else None

        if score is not None:
            score = max(1, min(10, int(score)))
            logger.info("App6 备注评分完成: note_id=%d score=%d tokens=%d", note_id, score, tokens_used)

        return score
    except Exception:
        logger.warning("App6 备注评分失败: note_id=%d", note_id, exc_info=True)
        return None


async def _async_ai_score(note_id: int, site_id: int, member_id: int, content: str) -> None:
    """后台异步执行 AI 评分，不阻塞 API 响应。"""
    try:
        ai_score_val = await ai_analyze_note(
            note_id=note_id, site_id=site_id, member_id=member_id, content=content,
        )
        if ai_score_val is not None:
            conn = _get_connection()
            try:
                with conn.cursor() as cur:
                    cur.execute(
                        "UPDATE biz.notes SET ai_score = %s, updated_at = NOW() WHERE id = %s",
                        (ai_score_val, note_id),
                    )
                conn.commit()
                logger.info("AI 评分已写入: note_id=%d ai_score=%d", note_id, ai_score_val)
            finally:
                conn.close()
    except Exception:
        logger.warning("后台 AI 评分失败: note_id=%d", note_id, exc_info=True)


@trace_service("创建备注", "Create note")
async def create_note(
    site_id: int,
    user_id: int,
    target_type: str,
    target_id: int,
    content: str,
    task_id: int | None = None,
    rating_service_willingness: int | None = None,
    rating_revisit_likelihood: int | None = None,
    score: int | None = None,
) -> dict:
    """
    创建备注。

    1. 验证评分范围（1-5 或 NULL），不合法则 422
    2. 确定 note type：
       - 若 task_id 关联的任务 task_type='follow_up_visit' → type='follow_up'
       - 否则 → type='normal'
    3. INSERT INTO biz.notes
    4. 若 type='follow_up'：
       - 保留 AI 占位调用（P5 接入时调用链不变），返回值仅更新 ai_score
       - 不论 ai_score 如何，有备注即标记关联 active 回访任务 completed
    5. 返回创建的备注记录

    注意：星星评分不参与回访完成判定，不参与 AI 分析，仅存储。
    """
    # 验证评分范围
    for label, val in [
        ("再次服务意愿评分", rating_service_willingness),
        ("再来店可能性评分", rating_revisit_likelihood),
        ("备注星星评分", score),
    ]:
        if val is not None and (val < 1 or val > 5):
            raise HTTPException(
                status_code=422, detail=f"{label}必须在 1-5 范围内"
            )

    conn = _get_connection()
    try:
        # 确定 note type
        note_type = "normal"
        task_info = None

        if task_id is not None:
            with conn.cursor() as cur:
                cur.execute(
                    """
                    SELECT id, task_type, status, site_id
                    FROM biz.coach_tasks
                    WHERE id = %s
                    """,
                    (task_id,),
                )
                row = cur.fetchone()

            if row is None:
                raise HTTPException(status_code=404, detail="关联任务不存在")

            task_info = {
                "id": row[0],
                "task_type": row[1],
                "status": row[2],
                "site_id": row[3],
            }

            if task_info["site_id"] != site_id:
                raise HTTPException(status_code=403, detail="权限不足")

            if task_info["task_type"] == "follow_up_visit":
                note_type = "follow_up"

        # INSERT 备注
        with conn.cursor() as cur:
            cur.execute("BEGIN")
            cur.execute(
                """
                INSERT INTO biz.notes
                    (site_id, user_id, target_type, target_id, type,
                     content, rating_service_willingness,
                     rating_revisit_likelihood, task_id, score)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                RETURNING id, site_id, user_id, target_type, target_id,
                          type, content, rating_service_willingness,
                          rating_revisit_likelihood, task_id,
                          ai_score, ai_analysis, created_at, updated_at, score
                """,
                (
                    site_id, user_id, target_type, target_id, note_type,
                    content, rating_service_willingness,
                    rating_revisit_likelihood, task_id, score,
                ),
            )
            row = cur.fetchone()

            note = {
                "id": row[0],
                "site_id": row[1],
                "user_id": row[2],
                "target_type": row[3],
                "target_id": row[4],
                "type": row[5],
                "content": row[6],
                "rating_service_willingness": row[7],
                "rating_revisit_likelihood": row[8],
                "task_id": row[9],
                "ai_score": row[10],
                "ai_analysis": row[11],
                "created_at": row[12].isoformat() if row[12] else None,
                "updated_at": row[13].isoformat() if row[13] else None,
                "score": row[14],
            }

            # 若 type='follow_up'，标记回访任务完成（不依赖 AI 评分）
            if note_type == "follow_up" and task_id is not None:
                if task_info and task_info["status"] == "active":
                    cur.execute(
                        """
                        UPDATE biz.coach_tasks
                        SET status = 'completed',
                            completed_at = NOW(),
                            completed_task_type = task_type,
                            updated_at = NOW()
                        WHERE id = %s AND status = 'active'
                        """,
                        (task_id,),
                    )
                    _record_history(
                        cur,
                        task_id,
                        action="completed_by_note",
                        old_status="active",
                        new_status="completed",
                        old_task_type=task_info["task_type"],
                        new_task_type=task_info["task_type"],
                        detail={"note_id": note["id"]},
                    )

        conn.commit()

        # CHANGE 2026-03-27 | AI 评分：后台异步执行，不阻塞 API 响应
        # 备注先返回给前端（aiScore=null），AI 评分完成后写入数据库
        # 前端下次加载页面时自动获取最新 aiScore
        import asyncio
        asyncio.create_task(_async_ai_score(note["id"], site_id, target_id, content))

        # 触发 AI 备注分析链（App6 → App8）
        # target_type='member' 时 target_id 即 member_id；'assistant' 时不触发（AI 只分析会员备注）
        if target_type == "member":
            try:
                from app.services.trigger_scheduler import fire_event
                fire_event(
                    "ai_note_created",
                    {
                        "site_id": site_id,
                        "member_id": target_id,
                        "note_content": content,
                        "noted_by_name": note.get("recorded_by_name")
                            or note.get("user_nickname") or "",
                        "noted_by_created_at": note.get("created_at") or "",
                    },
                )
            except Exception:
                logger.exception(
                    "触发 ai_note_created 事件失败: note_id=%s member_id=%s",
                    note["id"], target_id,
                )

        return note

    except HTTPException:
        conn.rollback()
        raise
    except Exception:
        conn.rollback()
        raise
    finally:
        conn.close()


@trace_service("查询备注列表", "Get notes")
async def get_notes(
    site_id: int, target_type: str, target_id: int
) -> list[dict]:
    """
    查询某目标的备注列表。

    按 created_at DESC 排序，包含星星评分和 AI 评分。
    """
    conn = _get_connection()
    try:
        with conn.cursor() as cur:
            cur.execute(
                """
                SELECT id, site_id, user_id, target_type, target_id,
                       type, content, rating_service_willingness,
                       rating_revisit_likelihood, task_id,
                       ai_score, ai_analysis, created_at, updated_at
                FROM biz.notes
                WHERE site_id = %s
                  AND target_type = %s
                  AND target_id = %s
                ORDER BY created_at DESC
                """,
                (site_id, target_type, target_id),
            )
            rows = cur.fetchall()
        conn.commit()

        return [
            {
                "id": r[0],
                "site_id": r[1],
                "user_id": r[2],
                "target_type": r[3],
                "target_id": r[4],
                "type": r[5],
                "content": r[6],
                "rating_service_willingness": r[7],
                "rating_revisit_likelihood": r[8],
                "task_id": r[9],
                "ai_score": r[10],
                "ai_analysis": r[11],
                "created_at": r[12].isoformat() if r[12] else None,
                "updated_at": r[13].isoformat() if r[13] else None,
            }
            for r in rows
        ]

    finally:
        conn.close()


@trace_service("删除备注", "Delete note")
async def delete_note(note_id: int, user_id: int, site_id: int) -> dict:
    """
    删除备注。

    验证备注归属（user_id + site_id）后执行硬删除。
    - 不存在 → 404
    - 不属于当前用户 → 403
    """
    conn = _get_connection()
    try:
        with conn.cursor() as cur:
            cur.execute(
                """
                SELECT id, user_id, site_id
                FROM biz.notes
                WHERE id = %s
                """,
                (note_id,),
            )
            row = cur.fetchone()

        if not row:
            raise HTTPException(status_code=404, detail="资源不存在")

        if row[2] != site_id or row[1] != user_id:
            raise HTTPException(status_code=403, detail="权限不足")

        with conn.cursor() as cur:
            cur.execute("BEGIN")
            cur.execute(
                "DELETE FROM biz.notes WHERE id = %s",
                (note_id,),
            )
        conn.commit()

        return {"id": note_id, "deleted": True}

    except HTTPException:
        conn.rollback()
        raise
    except Exception:
        conn.rollback()
        raise
    finally:
        conn.close()
