# -*- coding: utf-8 -*-
"""中台：考试管理（创建/编辑、选题、成绩查看、参与人员）。"""
import datetime
import json
from flask import render_template, request, redirect, url_for, flash
from flask_login import login_required, current_user

from app.models import Exam, Question, ExamQuestion, ExamRecord, Chain, Staff, ExamParticipant, Store, GameExclusion, GameReward
from app.extensions import db
from app.admin import bp
from app.admin.views import _admin_only
from app.admin.admins import _super_only
from app.services import log_action, add_points
from app.game_templates import GAME_TEMPLATES


@bp.route("/exams")
@login_required
def exams():
    _admin_only()
    exams = Exam.query.filter_by(deleted=False).order_by(Exam.id.desc()).all()
    now = datetime.datetime.now()
    for e in exams:
        if e.status == "draft":
            e.status_label, e.status_class = "草稿", "bg-secondary"
        elif e.status == "closed":
            e.status_label, e.status_class = "关闭", "bg-danger"
        elif e.end_at and e.end_at < now:
            e.status_label, e.status_class = "已过期", "bg-secondary"
        elif e.start_at and e.start_at > now:
            e.status_label, e.status_class = "开放", "bg-info"
        else:
            e.status_label, e.status_class = "考试中", "bg-success"
    return render_template("admin/exam_list.html", exams=exams, active="exams")


@bp.route("/exams/new", methods=["GET", "POST"])
@bp.route("/exams/<int:id>/edit", methods=["GET", "POST"])
@login_required
def exam_edit(id=None):
    _admin_only()
    e = Exam.query.get_or_404(id) if id else Exam(
        status="draft", pass_score=60, max_attempts=1, points_reward=0)
    if request.method == "POST":
        e.title = request.form.get("title", "").strip()
        e.description = request.form.get("description", "").strip()
        e.mode = request.form.get("mode", "quiz")
        e.pass_score = request.form.get("pass_score", 60, type=int)
        e.max_attempts = request.form.get("max_attempts", 0 if e.mode == "game" else 1, type=int)
        e.time_limit = request.form.get("time_limit", 0, type=int)
        e.points_reward = request.form.get("points_reward", 0, type=int)
        e.status = request.form.get("status", "draft")
        if e.mode == "game":
            rc = request.form.get("game_round_count", 4, type=int)
            e.game_config = json.dumps({"round_count": max(2, min(rc or 4, 6))})
        else:
            e.game_config = None
        for f in ("start_at", "end_at"):
            v = request.form.get(f, "").strip()
            setattr(e, f, datetime.datetime.strptime(v, "%Y-%m-%dT%H:%M") if v else None)
        if not e.title:
            flash("考试标题必填", "danger")
        else:
            if id is None:
                db.session.add(e)
            db.session.commit()
            log_action("exam_create" if id is None else "exam_edit",
                       target_type="exam", target_id=e.id, summary="考试：%s" % e.title)
            flash("已保存，请到「选题」配置题目", "success")
            return redirect(url_for("admin.exam_questions", id=e.id))
    gc = json.loads(e.game_config) if e.game_config else {}
    return render_template("admin/exam_form.html", e=e, active="exams", is_new=(id is None),
                           game_round_count=gc.get("round_count", 4))


@bp.route("/exams/<int:id>/questions", methods=["GET", "POST"])
@login_required
def exam_questions(id):
    """选题：普通考试=扁平勾选；游戏闯关=按 4 个关卡分桶（每桶按题型过滤）。"""
    _admin_only()
    e = Exam.query.get_or_404(id)
    if (e.mode or "quiz") == "game":
        return _game_rounds(e)
    if request.method == "POST":
        ids = request.form.getlist("qids")
        ExamQuestion.query.filter_by(exam_id=e.id).delete()
        for idx, qid in enumerate(ids):
            db.session.add(ExamQuestion(exam_id=e.id, question_id=int(qid), order=idx))
        e.status = "open"
        db.session.commit()
        log_action("exam_publish", target_type="exam", target_id=e.id,
                   summary="发布考试《%s》（%d 题）" % (e.title, len(ids)))
        flash("已发布考试（%d 题），店员现已可见" % len(ids), "success")
        return redirect(url_for("admin.exams"))
    selected = set(eq.question_id for eq in e.questions.all())
    all_q = Question.query.order_by(Question.id.desc()).all()
    cats = sorted(set(q.category for q in all_q if q.category))
    qtypes = sorted(set(q.qtype for q in all_q if q.qtype))
    return render_template("admin/exam_questions.html", e=e, all_q=all_q,
                           cats=cats, qtypes=qtypes, selected=selected, active="exams")


def _game_rounds(e):
    """游戏闯关：动态 N 关选题（N 来自 e.game_config.round_count，默认 4）。
    每关先选游戏形式（11 选 1），前端按该形式允许的题型筛题；保存时连 game_code 一起写入。"""
    cfg = json.loads(e.game_config) if e.game_config else {}
    round_count = max(2, min(int(cfg.get("round_count") or 4), 6))
    assigned = {eq.question_id: eq.round for eq in e.questions.all()}
    round_codes = {}
    for eq in e.questions.all():
        if eq.round and eq.round not in round_codes:
            round_codes[eq.round] = eq.game_code or ""
    if request.method == "POST":
        ExamQuestion.query.filter_by(exam_id=e.id).delete()
        count = 0
        for r in range(1, round_count + 1):
            code = request.form.get("game_code_round%d" % r, "").strip()
            ids = request.form.getlist("round%d" % r)
            for idx, qid in enumerate(ids):
                db.session.add(ExamQuestion(exam_id=e.id, question_id=int(qid),
                                            round=r, game_code=code or None, order=idx))
                count += 1
        e.status = "open"
        db.session.commit()
        log_action("exam_publish", target_type="exam", target_id=e.id,
                   summary="发布游戏《%s》（%d 题 / %d 关）" % (e.title, count, round_count))
        flash("已发布游戏（%d 题 / %d 关），店员现已可见" % (count, round_count), "success")
        return redirect(url_for("admin.exams"))
    all_q = Question.query.order_by(Question.id.desc()).all()
    cats = sorted(set(q.category for q in all_q if q.category))
    buckets = [{"round": r, "game_code": round_codes.get(r, "")} for r in range(1, round_count + 1)]
    templates = sorted(
        [{"code": c, "name": t["name"], "qtypes": t["qtypes"]} for c, t in GAME_TEMPLATES.items()],
        key=lambda x: GAME_TEMPLATES[x["code"]]["sort"])
    return render_template("admin/exam_rounds.html", e=e, buckets=buckets, all_q=all_q,
                           cats=cats, assigned=assigned, templates=templates,
                           templates_json=json.dumps(
                               {c: {"qtypes": t["qtypes"]} for c, t in GAME_TEMPLATES.items()},
                               ensure_ascii=False),
                           active="exams")


@bp.route("/exams/<int:id>/participants", methods=["GET", "POST"])
@login_required
def exam_participants(id):
    """参与人员范围：全员 / 按连锁 / 指定人员（考试与游戏通用）。"""
    _admin_only()
    e = Exam.query.get_or_404(id)
    if request.method == "POST":
        e.scope_mode = request.form.get("scope_mode", "all")
        if e.scope_mode == "chain":
            chains = request.form.getlist("chains")
            e.allowed_chains = json.dumps([int(c) for c in chains])
        else:
            e.allowed_chains = None
        # 指定人员：全量替换；其它模式清空
        ExamParticipant.query.filter_by(exam_id=e.id).delete()
        if e.scope_mode == "staff":
            for sid in request.form.getlist("staff_ids"):
                db.session.add(ExamParticipant(exam_id=e.id, staff_id=int(sid)))
        db.session.commit()
        log_action("exam_set_participants", target_type="exam", target_id=e.id,
                   summary="考试《%s》参与范围：%s" % (e.title, e.scope_mode))
        flash("已更新参与人员", "success")
        return redirect(url_for("admin.exam_participants", id=e.id))
    chains = Chain.query.order_by(Chain.name).all()
    selected_chains = set(json.loads(e.allowed_chains) if e.allowed_chains else [])
    selected_staff = set(p.staff_id for p in ExamParticipant.query.filter_by(exam_id=e.id).all())
    staff_list = Staff.query.filter_by(role="staff").order_by(Staff.id.desc()).limit(500).all()
    # 各连锁人数（供展示）
    chain_counts = {}
    for c in chains:
        chain_counts[c.id] = Staff.query.join(Staff.store).filter(
            Staff.role == "staff", Store.chain_id == c.id).count()
    return render_template("admin/exam_participants.html", e=e, chains=chains,
                           selected_chains=selected_chains, staff_list=staff_list,
                           selected_staff=selected_staff, chain_counts=chain_counts,
                           active="exams")


@bp.route("/exams/<int:id>/records")
@login_required
def exam_records(id):
    _admin_only()
    e = Exam.query.get_or_404(id)
    records = ExamRecord.query.filter_by(exam_id=e.id).order_by(ExamRecord.id.desc()).all()
    return render_template("admin/exam_records.html", e=e, records=records, active="exams")


@bp.route("/exams/<int:id>/delete", methods=["POST"])
@login_required
def exam_delete(id):
    """删除考试（仅超级管理员）。有考试记录(含积分)→隐藏(软删除)；无记录→物理删除。"""
    _super_only()
    e = Exam.query.get_or_404(id)
    has_records = db.session.query(ExamRecord.id).filter_by(exam_id=id).first() is not None
    if has_records:
        e.deleted = True
        log_action("exam_hide", target_type="exam", target_id=id,
                   summary="隐藏考试（有记录，保留数据）：%s" % e.title, commit=False)
        db.session.commit()
        flash("该考试已有考试记录，已隐藏（数据保留，不在列表显示）", "success")
    else:
        title = e.title
        ExamParticipant.query.filter_by(exam_id=id).delete()  # 清参与人员关联
        db.session.delete(e)  # ExamQuestion 由 cascade 级联删除
        log_action("delete_exam", target_type="exam", target_id=id,
                   summary="删除考试：%s" % title, commit=False)
        db.session.commit()
        flash("已删除", "success")
    return redirect(url_for("admin.exams"))


# -------------------- 游戏英雄榜（排名 + 手动发分）--------------------

def _game_score_of(rec):
    """从 ExamRecord.rounds JSON 取速度奖励分 game_score（容错）。"""
    try:
        return int((json.loads(rec.rounds) if rec.rounds else {}).get("game_score", 0))
    except Exception:
        return 0


def _leaderboard_rows(exam_id):
    """游戏英雄榜排名数据：按速度奖励分(game_score)降序，每人取最高分。
    返回 rows（含 staff_id/name/phone/chain/best/count/duration/time/excluded/rewarded）。
    exam_leaderboard（后台管理）与 exam_leaderboard_view（前台预览）共用。"""
    recs = ExamRecord.query.filter_by(exam_id=exam_id).all()
    excluded = set(x.staff_id for x in GameExclusion.query.filter_by(exam_id=exam_id).all())
    rewards = {r.staff_id: r.points for r in GameReward.query.filter_by(exam_id=exam_id).all()}
    agg = {}  # staff_id -> 聚合字典
    for r in recs:
        if not r.staff_id:
            continue
        gs = _game_score_of(r)
        a = agg.get(r.staff_id)
        if a is None:
            agg[r.staff_id] = {"best": gs, "count": 1, "duration": r.duration or 0,
                               "time": r.created_at}
        else:
            a["count"] += 1
            if gs > a["best"]:
                a["best"] = gs
                a["duration"] = r.duration or 0
            if r.created_at and (not a["time"] or r.created_at > a["time"]):
                a["time"] = r.created_at
    rows = []
    for sid, a in agg.items():
        st = Staff.query.get(sid)
        if not st:
            continue
        rows.append({
            "staff_id": sid, "name": st.name or "-", "phone": st.phone or "",
            "chain": (st.store.chain.name if (st.store and st.store.chain) else "-"),
            "best": a["best"], "count": a["count"], "duration": a["duration"],
            "time": a["time"], "excluded": sid in excluded,
            "rewarded": rewards.get(sid, 0),
        })
    rows.sort(key=lambda x: -x["best"])
    return rows


@bp.route("/exams/<int:id>/leaderboard")
@login_required
def exam_leaderboard(id):
    """游戏英雄榜后台：排名+统计+删除/清空/屏蔽/发分。"""
    _admin_only()
    e = Exam.query.get_or_404(id)
    rows = _leaderboard_rows(id)
    visible = [r for r in rows if not r["excluded"]]
    total = len(visible)
    avg = round(sum(r["best"] for r in visible) / total) if total else 0
    mx = max((r["best"] for r in visible), default=0)
    return render_template("admin/exam_leaderboard.html", e=e, rows=rows, active="exams",
                           stat_total=total, stat_avg=avg, stat_max=mx)


@bp.route("/exams/<int:id>/leaderboard/view")
@login_required
def exam_leaderboard_view(id):
    """前台英雄榜预览：管理员看店员玩完后看到的那张榜（移动端样式，只显示未屏蔽）。"""
    _admin_only()
    e = Exam.query.get_or_404(id)
    rows = [r for r in _leaderboard_rows(id) if not r["excluded"]]
    return render_template("admin/exam_leaderboard_view.html", e=e, rows=rows)


@bp.route("/exams/<int:id>/leaderboard/delete-staff", methods=["POST"])
@login_required
def leaderboard_delete_staff(id):
    """删除某店员在该游戏的全部成绩（排名自动重算；游戏 max_attempts 多为 1，等同删单条）。"""
    _admin_only()
    sid = request.form.get("staff_id", type=int)
    if not sid:
        flash("参数错误", "danger")
        return redirect(url_for("admin.exam_leaderboard", id=id))
    st = Staff.query.get(sid)
    nm = st.name if st else "?"
    n = ExamRecord.query.filter_by(exam_id=id, staff_id=sid).delete()
    log_action("leaderboard_delete_staff", target_type="exam", target_id=id,
               summary="删除 %s 在游戏 #%d 的全部成绩（%d 条）" % (nm, id, n), commit=False)
    db.session.commit()
    flash("已删除 %s 的成绩（%d 条）" % (nm, n), "success")
    return redirect(url_for("admin.exam_leaderboard", id=id))


@bp.route("/exams/<int:id>/leaderboard/clear", methods=["POST"])
@login_required
def leaderboard_clear(id):
    """清空该游戏全部成绩（仅超级管理员）。"""
    _super_only()
    e = Exam.query.get_or_404(id)
    n = ExamRecord.query.filter_by(exam_id=id).delete()
    log_action("leaderboard_clear", target_type="exam", target_id=id,
               summary="清空游戏《%s》全部成绩（%d 条）" % (e.title, n), commit=False)
    db.session.commit()
    flash("已清空该游戏全部成绩（%d 条）" % n, "success")
    return redirect(url_for("admin.exam_leaderboard", id=id))


@bp.route("/exams/<int:id>/leaderboard/exclude", methods=["POST"])
@login_required
def leaderboard_exclude(id):
    """屏蔽/取消屏蔽店员（被屏蔽者不上榜，成绩保留，可恢复）。"""
    _admin_only()
    sid = request.form.get("staff_id", type=int)
    if not sid:
        flash("参数错误", "danger")
        return redirect(url_for("admin.exam_leaderboard", id=id))
    st = Staff.query.get(sid)
    nm = st.name if st else "?"
    ex = GameExclusion.query.filter_by(exam_id=id, staff_id=sid).first()
    if ex:
        db.session.delete(ex)
        log_action("leaderboard_include", target_type="exam", target_id=id,
                   summary="取消屏蔽 %s（游戏 #%d）" % (nm, id), commit=False)
        flash("已取消屏蔽 %s" % nm, "success")
    else:
        db.session.add(GameExclusion(exam_id=id, staff_id=sid, admin_id=current_user.id))
        log_action("leaderboard_exclude", target_type="exam", target_id=id,
                   summary="屏蔽 %s 不上榜（游戏 #%d）" % (nm, id), commit=False)
        flash("已屏蔽 %s（成绩保留，不上榜）" % nm, "success")
    db.session.commit()
    return redirect(url_for("admin.exam_leaderboard", id=id))


@bp.route("/exams/<int:id>/leaderboard/reward", methods=["POST"])
@login_required
def leaderboard_reward(id):
    """手动发放游戏积分（50/100/200/300/500 五档，每人每游戏仅一次，GameReward 防重复）。"""
    _admin_only()
    sid = request.form.get("staff_id", type=int)
    pts = request.form.get("points", type=int)
    ALLOWED = {50, 100, 200, 300, 500}
    if not sid or pts not in ALLOWED:
        flash("积分档位非法（仅 50/100/200/300/500）", "danger")
        return redirect(url_for("admin.exam_leaderboard", id=id))
    e = Exam.query.get_or_404(id)
    st = Staff.query.get_or_404(sid)
    exist = GameReward.query.filter_by(exam_id=id, staff_id=sid).first()
    if exist:
        flash("%s 已发放过 %d 积分，不可重复发放" % (st.name, exist.points), "warning")
        return redirect(url_for("admin.exam_leaderboard", id=id))
    db.session.add(GameReward(exam_id=id, staff_id=sid, points=pts, admin_id=current_user.id))
    ok = add_points(st, pts, "game", ref_id=id,
                    remark="游戏英雄榜发奖：%s" % e.title, commit=False)
    if not ok:
        db.session.rollback()
        flash("发分失败，请重试", "danger")
        return redirect(url_for("admin.exam_leaderboard", id=id))
    log_action("leaderboard_reward", target_type="exam", target_id=id,
               summary="给 %s 发游戏积分 %d（《%s》）" % (st.name, pts, e.title), commit=False)
    db.session.commit()
    flash("已给 %s 发放 %d 积分" % (st.name, pts), "success")
    return redirect(url_for("admin.exam_leaderboard", id=id))
