"""Routes: admin registrations"""
import sqlite3
from datetime import datetime
from flask import abort, flash, jsonify, redirect, render_template, request, send_file, session, url_for

from nexora.config import logger
from nexora.core import app
from nexora.database.connection import db, execute, query
from nexora.utils.helpers import amount_value
from nexora.utils.team import parse_team_members
from nexora.utils.validators import validate_registration_constraints
from nexora.services.images import static_file_abs
from nexora.services.uploads import delete_upload, process_image_upload
from nexora.services.qualifiers import qualifier_entry_list, registration_qualifier_details
from nexora.services.ticket_codes import make_ticket_serial, make_ticket_token
from nexora.services.registrations import (
    REGISTRATION_SELECT,
    build_registration_filters,
    registration_details,
)
from nexora.services.tickets import (
    group_checkin_summary,
    member_ticket_record,
    registration_member_names,
    registration_team_id,
    ticket_member_roster,
)
from nexora.services.email_service import (
    enqueue_status_email,
)
from nexora.services.email_queue import (
    enqueue_bulk_emails,
    get_queue_status,
    get_bulk_operation_status,
    retry_failed_jobs,
    wake_worker,
)
from nexora.security.permissions import admin_required, permission_required
from nexora.database.schema import ensure_member_tickets_for_registration


@app.route("/admin/registrations")
@admin_required
@permission_required("view_registrations")
def admin_registrations():
    where, args, filters = build_registration_filters()
    db_rows = query(REGISTRATION_SELECT + where + " ORDER BY registrations.id DESC", args)
    reg_ids = [row["id"] for row in db_rows]
    email_counts = {}
    qualifier_sections_by_reg = {}
    if reg_ids:
        placeholders = ",".join("?" for _ in reg_ids)
        email_counts = {
            row["registration_id"]: row["c"]
            for row in query(
                f"""SELECT registration_id, COUNT(*) AS c
                    FROM email_logs
                    WHERE registration_id IN ({placeholders})
                    GROUP BY registration_id""",
                tuple(reg_ids),
            )
        }
        qualifier_rows = query(
            f"""SELECT qet.registration_id, qet.qualifier_id, qet.player_name, qet.ticket_serial,
                       qet.entry_status, q.qualifier_number, q.qualifier_name,
                       q.location, q.qualifier_date, q.qualifier_time
                FROM qualifier_entry_tickets qet
                JOIN qualifiers q ON q.id = qet.qualifier_id
                WHERE qet.registration_id IN ({placeholders})
                ORDER BY qet.registration_id, q.qualifier_number, qet.member_number, qet.id""",
            tuple(reg_ids),
        )
        selected_filter_qid = int(filters["qualifier_id"] or 0)
        for qrow in qualifier_rows:
            if selected_filter_qid and qrow["qualifier_id"] != selected_filter_qid:
                continue
            qualifier_sections_by_reg.setdefault(qrow["registration_id"], []).append(qrow)

    def payable_for_row(row):
        for field in ("total_payable_saved", "final_payable_amount"):
            if field in row.keys() and (row[field] or "").strip():
                return row[field].strip()
        if "qualifier_fee" in row.keys() and (row["qualifier_fee"] or "").strip():
            return row["qualifier_fee"].strip()
        return "Not recorded"

    def amount_paid_for_row(row, total_payable):
        value = (row["amount_paid"] or "").strip() if "amount_paid" in row.keys() else ""
        if value:
            return value
        return total_payable if row["payment_status"] == "paid" and total_payable != "Not recorded" else "Rs. 0"

    def remaining_for_row(row, total_payable, amount_paid):
        status = (row["payment_status"] or "").lower()
        if status == "paid":
            return "Rs. 0"
        if status == "under_verification":
            return "Pending"
        total_value = amount_value(total_payable)
        paid_value = amount_value(amount_paid)
        return f"Rs. {max(0, total_value - paid_value):,.0f}" if total_value else "Not recorded"

    rows = []
    for row in db_rows:
        item = dict(row)
        q_sections = qualifier_sections_by_reg.get(row["id"], [])
        item["qualifier_display"] = "\n".join(
            f"Q{sec['qualifier_number']} - {sec['qualifier_name']} | Venue: {sec['location'] or ''} | Date: {sec['qualifier_date'] or 'TBD'} | Time: {sec['qualifier_time'] or 'TBD'}"
            for sec in q_sections
        )
        item["qualifier_ticket_refs"] = "\n".join(
            f"{sec['player_name']}: {sec['ticket_serial']} ({(sec['entry_status'] or 'pending').replace('_', ' ').title()})"
            for sec in q_sections
        )
        item["total_payable"] = payable_for_row(row)
        item["amount_paid"] = amount_paid_for_row(row, item["total_payable"])
        item["remaining_amount"] = remaining_for_row(row, item["total_payable"], item["amount_paid"])
        item["team_id"] = registration_team_id(row)
        item["member_names"] = registration_member_names(row)
        item["qualifier_count"] = int(row["qualifier_count"] or 0) if "qualifier_count" in row.keys() else 0
        item["qualifier_fee"] = row["qualifier_fee"] if "qualifier_fee" in row.keys() else ""
        item["email_count"] = email_counts.get(row["id"], 0)
        rows.append(item)
    # Apply email_status filter
    email_filter = filters.get("email_status", "")
    if email_filter == "sent":
        rows = [r for r in rows if r["email_count"] > 0]
    elif email_filter == "not_sent":
        rows = [r for r in rows if r["email_count"] == 0]
    elif email_filter == "multiple":
        rows = [r for r in rows if r["email_count"] >= 2]
    games = query("SELECT id, title, max_members FROM games ORDER BY title")
    qualifiers = query(
        """SELECT qualifiers.*, games.title AS game_title
           FROM qualifiers JOIN games ON games.id = qualifiers.game_id
           WHERE qualifiers.active = 1
           ORDER BY games.title, qualifiers.qualifier_number, qualifiers.id"""
    )
    games = query("SELECT id, title, max_members FROM games WHERE active = 1 AND deleted_at = '' ORDER BY title")
    rows_by_game = {"all": rows}
    for game in games:
        rows_by_game[game["id"]] = []
    for row in rows:
        if row["game_id"] in rows_by_game:
            rows_by_game[row["game_id"]].append(row)
    totals = {
        "shown": len(rows),
        "paid": sum(1 for r in rows if r["payment_status"] == "paid"),
        "unpaid": sum(1 for r in rows if r["payment_status"] == "unpaid"),
        "under_verification": sum(1 for r in rows if r["payment_status"] == "under_verification"),
        "email_sent": sum(1 for r in rows if r.get("email_count", 0) > 0),
        "email_not_sent": sum(1 for r in rows if r.get("email_count", 0) == 0),
    }
    payment_methods = query(
        "SELECT DISTINCT payment_method FROM registrations WHERE payment_method IS NOT NULL AND payment_method != '' ORDER BY payment_method"
    )
    return render_template(
        "admin_registrations.html", rows=rows, rows_by_game=rows_by_game, filters=filters, games=games,
        qualifiers=qualifiers,
        totals=totals, payment_methods=payment_methods,
    )


@app.route("/admin/registrations/search-ajax")
@admin_required
@permission_required("view_registrations")
def admin_registrations_search_ajax():
    """AJAX live-search for the 'Quick Send Email' tool.

    Matches the keyword against name / email / ticket / team / phone /
    qualifier player, same SQL used by the main filters, and returns a small
    JSON list (id, ticket, name, email, team, game, status, members) so the
    admin can select multiple and send them an email.
    """
    q = (request.args.get("q") or "").strip()
    game_id = request.args.get("game_id", type=int)
    status = request.args.get("status", "").strip()

    where, args = [], []
    if q:
        like = f"%{q}%"
        where.append(
            "(r.full_name LIKE ? OR r.email LIKE ? OR r.phone LIKE ? "
            " OR r.team_name LIKE ? OR r.leader_name LIKE ? OR r.members LIKE ? "
            " OR r.ticket_serial LIKE ? OR r.transaction_reference LIKE ? "
            " OR EXISTS (SELECT 1 FROM qualifier_entry_tickets qet"
            "   WHERE qet.registration_id = r.id AND (qet.player_name LIKE ? OR qet.ticket_serial LIKE ?)))"
        )
        args.extend([like, like, like, like, like, like, like, like, like, like])
    if game_id:
        where.append("r.game_id = ?")
        args.append(game_id)
    if status in ("paid", "unpaid", "under_verification", "partial", "refunded"):
        where.append("r.payment_status = ?")
        args.append(status)

    sql = (
        "SELECT r.id, r.ticket_serial, r.full_name, r.email, r.team_name, "
        "       r.entry_type, r.payment_status, r.amount_paid, r.total_payable_saved, "
        "       g.title AS game "
        "  FROM registrations r LEFT JOIN games g ON g.id = r.game_id"
    )
    if where:
        sql += " WHERE " + " AND ".join(where)
    sql += " ORDER BY r.id DESC LIMIT 60"
    rows = query(sql, args) or []

    results = []
    for row in rows:
        results.append({
            "id": row["id"],
            "ticket": row["ticket_serial"] or f"NX-{int(row['id']):04d}",
            "name": row["full_name"] or "",
            "email": row["email"] or "",
            "team": row["team_name"] or "",
            "game": row["game"] or "",
            "entry_type": row["entry_type"] or "individual",
            "status": row["payment_status"] or "unpaid",
        })
    return jsonify({"ok": True, "q": q, "results": results, "count": len(results)})


@app.route("/admin/registrations/<int:reg_id>")
@admin_required
@permission_required("view_registrations")
def registration_view(reg_id):
    row = query(REGISTRATION_SELECT + " WHERE registrations.id = ?", (reg_id,), one=True)
    details = registration_details(reg_id)
    if not row or not details:
        flash("Registration not found.", "danger")
        return redirect(url_for("admin_registrations"))
    members = parse_team_members(row["members"])
    with db() as conn:
        ensure_member_tickets_for_registration(conn, reg_id)
        conn.commit()
    member_tickets = query(
        "SELECT * FROM member_tickets WHERE registration_id = ? AND member_number > 1 ORDER BY member_number", (reg_id,)
    )
    member_tickets = [dict(ticket) for ticket in member_tickets]
    for ticket in member_tickets:
        member_index = int(ticket["member_number"]) - 2
        ticket["member_phone"] = members[member_index]["phone"] if 0 <= member_index < len(members) else ""
    leader_ticket = member_ticket_record(details, {
        "role": "leader",
        "member_number": 1,
        "name": details.get("leader_name") or details.get("name") or "",
        "email": details.get("email") or "",
        "cnic": details.get("cnic") or "",
    })
    audit_log = query(
        "SELECT * FROM ticket_audit_log WHERE registration_id = ? ORDER BY id DESC", (reg_id,)
    )
    checkin_summary = group_checkin_summary(row)
    # Fetch qualifier details for this registration
    selected_q_ids_str = details.get("selected_qualifiers", "")
    qualifier_names = []
    qualifier_details = []
    qualifier_sections = []
    if selected_q_ids_str:
        q_ids = [int(qid) for qid in selected_q_ids_str.split(",") if qid.strip().isdigit()]
        if q_ids:
            q_rows = query(
                "SELECT * FROM qualifiers WHERE id IN ({}) ORDER BY qualifier_number".format(
                    ",".join("?" for _ in q_ids)
                ),
                tuple(q_ids),
            )
            qualifier_names = [q["qualifier_name"] for q in q_rows]
            qualifier_details = [dict(q) for q in q_rows]
    qualifier_sections = qualifier_entry_list(details) if details else []
    game_id = details.get("game_id") if details else None
    game_qualifier_list = []
    if game_id:
        game_qualifier_list = [dict(q) for q in query(
            "SELECT id, qualifier_name, qualifier_number, fee, location, qualifier_date FROM qualifiers WHERE game_id = ? AND active = 1 ORDER BY qualifier_number",
            (game_id,),
        )]
    email_logs = query(
        "SELECT * FROM email_logs WHERE registration_id = ? ORDER BY id DESC", (reg_id,)
    )
    return render_template(
        "admin_registration_view.html",
        row=row,
        details=details,
        members=members,
        leader_ticket=leader_ticket,
        member_tickets=member_tickets,
        audit_log=audit_log,
        checkin_summary=checkin_summary,
        qualifier_names=qualifier_names,
        qualifier_details=qualifier_details,
        qualifier_sections=qualifier_sections,
        email_logs=email_logs,
        game_qualifiers=game_qualifier_list,
        selected_q_ids=[int(x) for x in (details.get("selected_qualifiers") or "").split(",") if x.strip().isdigit()] if details else [],
        selected_qualifier_list=qualifier_details,
    )


@app.route("/admin/registrations/<int:reg_id>/edit", methods=["GET", "POST"])
@admin_required
@permission_required("edit_registrations")
@permission_required("view_payment_details")
def edit_registration(reg_id):
    row = query(REGISTRATION_SELECT + " WHERE registrations.id = ?", (reg_id,), one=True)
    if not row:
        flash("Registration not found.", "danger")
        return redirect(url_for("admin_registrations"))

    games = query("SELECT id, title, platform FROM games ORDER BY title")
    if request.method == "POST":
        previous_status = row["payment_status"]
        proof = row["proof_image"]
        new_proof, proof_error = process_image_upload(
            request.files.get("proof_image"), category="registrations", game_title=row["game_title"],
            override_filename=request.form.get("email", row["email"]).strip(),
        )
        if proof_error:
            flash(proof_error, "danger")
            return redirect(url_for("edit_registration", reg_id=reg_id))
        if new_proof:
            delete_upload(proof)
            proof = new_proof

        # Serial numbers are system-generated to keep database, PDF, email, and scanner in sync.
        ticket_serial = row["ticket_serial"] or make_ticket_serial(reg_id, row["game_title"])
        ticket_token = row["ticket_token"] or make_ticket_token()
        if request.form.get("regenerate_token"):
            ticket_token = make_ticket_token()

        new_status = request.form.get("payment_status", "under_verification").lower()
        if new_status not in ("under_verification", "unpaid", "paid", "partial", "rejected", "refunded", "cancelled"):
            new_status = "under_verification"
        reason = (request.form.get("reason") or request.form.get("rejection_reason") or "").strip()

        new_team_name = request.form.get("team_name", "").strip()
        new_entry_type = request.form.get("entry_type", "individual")
        new_members = request.form.get("members", "").strip()

        form_amount_paid = request.form.get("amount_paid", "").strip()
        if new_status == "paid":
            current_details = registration_details(reg_id)
            full_pay = current_details["total_payable"] if current_details else ""
            if full_pay and (not form_amount_paid or amount_value(form_amount_paid) < amount_value(full_pay)):
                form_amount_paid = full_pay

        # â”€â”€ Transactional validation + update â”€â”€
        try:
            with db() as conn:
                errors = validate_registration_constraints(
                    conn,
                    email=request.form["email"],
                    cnic=request.form["cnic"],
                    game_id=request.form["game_id"],
                    team_name=new_team_name,
                    entry_type=new_entry_type,
                    members_text=new_members,
                    exclude_id=reg_id,
                )
                if errors:
                    for err in errors:
                        flash(err, "danger")
                    return redirect(url_for("edit_registration", reg_id=reg_id))

                conn.execute(
                    """
                    UPDATE registrations
                    SET full_name = ?, email = ?, phone = ?, cnic = ?, institution_type = ?,
                        university = ?, roll_no = ?, entry_type = ?, game_id = ?, team_name = ?,
                        leader_name = ?, members = ?, payment_method = ?, payment_status = ?,
                        amount_paid = ?, transaction_reference = ?, ticket_issued_at = ?,
                        proof_image = ?, ticket_serial = ?, ticket_token = ?,
                        rejection_reason = ?
                    WHERE id = ?
                    """,
                    (
                        request.form["full_name"].strip(),
                        request.form["email"].strip(),
                        request.form["phone"].strip(),
                        request.form["cnic"].strip(),
                        request.form.get("institution_type", "University").strip() or "University",
                        request.form["university"].strip(),
                        request.form.get("roll_no", "").strip(),
                        new_entry_type,
                        request.form["game_id"],
                        new_team_name,
                        request.form.get("leader_name", "").strip(),
                        new_members,
                        request.form.get("payment_method", "").strip() or row["payment_method"],
                        new_status,
                        form_amount_paid,
                        request.form.get("transaction_reference", "").strip(),
                        row["ticket_issued_at"] or datetime.now().isoformat(timespec="seconds"),
                        proof,
                        ticket_serial,
                        ticket_token,
                        reason if new_status in ("rejected", "cancelled", "refunded") else "",
                        reg_id,
                    ),
                )
                ensure_member_tickets_for_registration(conn, reg_id)
                conn.commit()

        except sqlite3.IntegrityError:
            flash("This update conflicts with an existing registration. Please check Team Name, Nickname, CNIC, and try again.", "danger")
            return redirect(url_for("edit_registration", reg_id=reg_id))
        except Exception:
            flash("An unexpected error occurred while updating. Please try again.", "danger")
            return redirect(url_for("edit_registration", reg_id=reg_id))

        if new_status == "paid" and previous_status != "paid":
            flash("Registration updated. Sending status email through the official mail server.", "success")
        elif new_status == "unpaid" and previous_status != "unpaid":
            flash("Registration updated. Sending status email through the official mail server.", "success")
        else:
            flash("Registration updated.", "success")

        # Send status-change email if status changed
        old_status_key = (previous_status or "").lower().replace(" ", "_")
        if new_status != old_status_key:
            try:
                with db() as conn:
                    email_result = enqueue_status_email(
                        conn, reg_id, new_status, reason=reason,
                        sent_by=session.get("username", "admin"), priority=2,
                        old_status=old_status_key,
                    )
                    conn.commit()
                if email_result and email_result.get("ok"):
                    flash("Status change email sent instantly.", "success")
                else:
                    flash((email_result or {}).get("message", "Status email could not be sent."), "danger")
            except Exception as e:
                logger.warning("Email queue failed for reg %d: %s", reg_id, e)

        return redirect(url_for("registration_view", reg_id=reg_id))

    return render_template("admin_registration_edit.html", row=row, games=games, qualifier_details=registration_qualifier_details(row))


@app.route("/admin/qualifier-setup/<int:reg_id>", methods=["POST"])
@admin_required
@permission_required("edit_registrations")
def admin_qualifier_setup(reg_id):
    details = registration_details(reg_id)
    if not details:
        return jsonify({"error": "Registration not found."}), 404
    include = (request.form.get("include_qualifiers") or "0") == "1"
    if not include:
        execute("UPDATE registrations SET selected_qualifiers = '' WHERE id = ?", (reg_id,))
        return jsonify({"ok": True, "updated": 0})
    q_ids = request.form.getlist("q_id[]")
    q_names = request.form.getlist("q_name[]")
    q_venues = request.form.getlist("q_venue[]")
    q_dates = request.form.getlist("q_date[]")
    q_times = request.form.getlist("q_time[]")
    q_comments = request.form.getlist("q_comment[]")
    valid_ids = []
    with db() as conn:
        for i, qid in enumerate(q_ids):
            qid = (qid or "").strip()
            if not qid.isdigit():
                continue
            qid_int = int(qid)
            valid_ids.append(qid_int)
            venue = (q_venues[i] if i < len(q_venues) else "").strip() or "TBD"
            date_val = (q_dates[i] if i < len(q_dates) else "").strip()
            time_val = (q_times[i] if i < len(q_times) else "").strip()
            comment = (q_comments[i] if i < len(q_comments) else "").strip()
            name = (q_names[i] if i < len(q_names) else "").strip()
            parts = []
            params = []
            if venue and venue != "TBD":
                parts.append("location = ?")
                params.append(venue)
            if comment:
                parts.append("qualifier_name = ?")
                params.append(comment)
            elif name:
                parts.append("qualifier_name = ?")
                params.append(name)
            if date_val or time_val:
                dt_str = f"{date_val}T{time_val}" if date_val and time_val else (date_val or time_val)
                parts.append("qualifier_date = ?")
                params.append(dt_str)
            if parts:
                params.append(qid_int)
                conn.execute(f"UPDATE qualifiers SET {', '.join(parts)} WHERE id = ?", params)
        sel = ",".join(str(x) for x in valid_ids)
        conn.execute("UPDATE registrations SET selected_qualifiers = ? WHERE id = ?", (sel, reg_id))
        conn.commit()
    return jsonify({"ok": True, "updated": len(valid_ids)})


@app.route("/admin/registrations/<int:reg_id>/proof")
@admin_required
@permission_required("view_payment_details")
def registration_proof(reg_id):
    row = query(REGISTRATION_SELECT + " WHERE registrations.id = ?", (reg_id,), one=True)
    if not row:
        flash("Registration not found.", "danger")
        return redirect(url_for("admin_registrations"))
    return render_template("admin_registration_proof.html", row=row)


@app.route("/admin/registrations/<int:reg_id>/proof/image")
@admin_required
@permission_required("view_payment_details")
def serve_proof_image(reg_id):
    row = query("SELECT proof_image FROM registrations WHERE id = ?", (reg_id,), one=True)
    if not row or not row["proof_image"]:
        return abort(404)
    file_path = static_file_abs(row["proof_image"])
    if not file_path or not file_path.is_file():
        return abort(404)
    return send_file(str(file_path))


@app.route("/admin/registrations/<int:reg_id>/status", methods=["POST"])
@admin_required
@permission_required("confirm_payments")
def update_payment(reg_id):
    new_status = request.form.get("payment_status", "under_verification").lower()
    is_ajax = request.headers.get("X-Requested-With") == "XMLHttpRequest"
    if new_status not in ("under_verification", "paid", "unpaid", "rejected", "cancelled", "refunded"):
        if is_ajax:
            return jsonify({"ok": False, "message": "Invalid payment status."}), 400
        flash("Invalid payment status.", "danger")
        return redirect(request.referrer or url_for("admin_registrations"))
    previous = query("SELECT payment_status FROM registrations WHERE id = ?", (reg_id,), one=True)
    if not previous:
        if is_ajax:
            return jsonify({"ok": False, "message": "Registration not found."}), 404
        flash("Registration not found.", "danger")
        return redirect(url_for("admin_registrations"))
    old_status = (previous["payment_status"] or "").lower().replace(" ", "_")
    reason = (request.form.get("reason") or "").strip()

    details = registration_details(reg_id)
    payable_amount = details["total_payable"] if details else "Rs. 0"
    confirmed_amount = payable_amount if new_status == "paid" else "Rs. 0"
    execute(
        "UPDATE registrations SET payment_status = ?, amount_paid = ? WHERE id = ?",
        (new_status, confirmed_amount, reg_id),
    )
    # Save reason for rejection / cancellation
    if new_status in ("rejected", "cancelled"):
        reason_col = "rejection_reason" if new_status == "rejected" else "rejection_reason"
        execute(
            f"UPDATE registrations SET {reason_col} = ? WHERE id = ?",
            (reason, reg_id),
        )
    elif new_status == "refunded":
        execute(
            "UPDATE registrations SET rejection_reason = ? WHERE id = ?",
            (reason, reg_id),
        )

    updated_details = registration_details(reg_id)

    email_msg = ""
    if new_status != old_status:
        try:
            with db() as conn:
                email_result = enqueue_status_email(
                    conn, reg_id, new_status, reason=reason,
                    sent_by=session.get("username", "admin"), priority=2,
                    old_status=old_status,
                )
                conn.commit()
            if email_result and email_result.get("ok"):
                email_msg = " Email sent instantly."
            else:
                email_msg = " Email failed: " + (email_result or {}).get("message", "Unknown SMTP error.")
        except Exception as e:
            logger.warning("Email queue failed for reg %d: %s", reg_id, e)
            email_msg = " Email could not be sent."

    status_labels = {
        "paid": "Paid", "unpaid": "Unpaid", "under_verification": "Under Verification",
        "rejected": "Rejected", "cancelled": "Cancelled", "refunded": "Refunded",
    }
    message = f"Marked as {status_labels.get(new_status, new_status.title())}.{email_msg}"
    if is_ajax:
        return jsonify({
            "ok": True,
            "status": new_status,
            "label": new_status.replace("_", " ").title(),
            "amount_paid": updated_details["amount_paid"] if updated_details else confirmed_amount,
            "remaining_amount": updated_details["remaining_amount"] if updated_details else payable_amount,
            "message": message,
        })
    flash(message, "success")
    return redirect(request.referrer or url_for("admin_registrations"))


@app.route("/admin/registrations/<int:reg_id>/email-logs")
@admin_required
@permission_required("confirm_payments")
def registration_email_logs(reg_id):
    row = query("SELECT id, full_name, email FROM registrations WHERE id = ?", (reg_id,), one=True)
    if not row:
        return jsonify({"ok": False, "message": "Registration not found."}), 404
    logs = query(
        """SELECT id, recipient_email, subject, status, error_message, sent_by, created_at,
                  email_type, previous_status, new_status, pdf_attached, retry_count, idempotency_key
           FROM email_logs WHERE registration_id = ? ORDER BY id DESC""",
        (reg_id,),
    )
    return jsonify({
        "ok": True,
        "registration": {"id": row["id"], "full_name": row["full_name"], "email": row["email"]},
        "logs": [dict(log) for log in logs],
    })


@app.route("/admin/registrations/email-log/<int:log_id>/retry", methods=["POST"])
@admin_required
@permission_required("confirm_payments")
def retry_email(log_id):
    is_ajax = request.headers.get("X-Requested-With") == "XMLHttpRequest"
    row = query("SELECT registration_id, new_status, status FROM email_logs WHERE id = ?", (log_id,), one=True)
    if not row:
        result = {"ok": False, "message": "Email log not found."}
    else:
        try:
            status = row["new_status"] or row["status"] or "under_verification"
            with db() as conn:
                result = enqueue_status_email(
                    conn, row["registration_id"], status,
                    sent_by=session.get("username", "admin"), priority=1,
                )
                conn.commit()
            if result and result.get("ok"):
                result["message"] = "Retry email sent instantly."
        except Exception as exc:
            logger.exception("Retry email queue failed for log %d: %s", log_id, exc)
            result = {"ok": False, "message": "Retry email could not be sent."}
    if is_ajax:
        return jsonify(result)
    if result.get("ok"):
        flash(result["message"], "success")
    else:
        flash(result["message"], "danger")
    return redirect(request.referrer or url_for("admin_registrations"))


@app.route("/admin/registrations/<int:reg_id>/resend-status-email", methods=["POST"])
@admin_required
@permission_required("confirm_payments")
def resend_current_status_email(reg_id):
    """Resend the current status email with updated PDF."""
    is_ajax = request.headers.get("X-Requested-With") == "XMLHttpRequest"
    row = query("SELECT payment_status FROM registrations WHERE id = ?", (reg_id,), one=True)
    if not row:
        if is_ajax:
            return jsonify({"ok": False, "message": "Registration not found."}), 404
        flash("Registration not found.", "danger")
        return redirect(url_for("admin_registrations"))

    current_status = (row["payment_status"] or "").lower().replace(" ", "_")
    reason = (request.form.get("reason") or "").strip()

    try:
        with db() as conn:
            result = enqueue_status_email(
                conn, reg_id, current_status, reason=reason,
                sent_by=session.get("username", "admin"), priority=2,
            )
            conn.commit()
        if result and result.get("ok"):
            result["message"] = "Email sent instantly."
    except Exception as e:
        result = {"ok": False, "message": str(e)}

    if is_ajax:
        return jsonify(result)
    if result.get("ok"):
        flash(result["message"], "success")
    else:
        flash(result.get("message", "Failed to send email."), "danger")
    return redirect(request.referrer or url_for("admin_registrations"))


@app.route("/admin/registrations/bulk-send-email", methods=["POST"])
@admin_required
@permission_required("confirm_payments")
def bulk_send_email():
    is_ajax = request.headers.get("X-Requested-With") == "XMLHttpRequest"
    reg_ids_raw = request.form.getlist("reg_ids") or ((request.json or {}).get("reg_ids", []) if request.is_json else [])
    reg_ids = [int(rid) for rid in reg_ids_raw if str(rid).strip().isdigit()]
    if not reg_ids:
        return jsonify({"ok": False, "message": "No registrations selected."}), 400

    status = (request.form.get("status") or request.form.get("payment_status") or "paid").lower().replace(" ", "_")
    reason = (request.form.get("reason") or "").strip()
    try:
        with db() as conn:
            op_id, jobs_created = enqueue_bulk_emails(
                conn, reg_ids, status, reason=reason,
                sent_by=session.get("username", "admin"),
            )
            conn.commit()
    except Exception as exc:
        logger.exception("Bulk email queue failed: %s", exc)
        return jsonify({"ok": False, "message": "Could not send emails."}), 500

    return jsonify({
        "ok": jobs_created > 0,
        "message": f"{jobs_created} email(s) sent instantly.",
        "operation_id": op_id,
        "summary": {
            "total_registrations": len(reg_ids),
            "queued": jobs_created,
            "sent": 0,
            "failed": 0,
        },
    })


@app.route("/admin/registrations/<int:reg_id>/send-email", methods=["POST"])
@admin_required
@permission_required("confirm_payments")
def send_registration_email(reg_id):
    is_ajax = request.headers.get("X-Requested-With") == "XMLHttpRequest"
    row = query("SELECT id, full_name, email, payment_status FROM registrations WHERE id = ?", (reg_id,), one=True)
    if not row:
        if is_ajax:
            return jsonify({"ok": False, "message": "Registration not found."}), 404
        flash("Registration not found.", "danger")
        return redirect(url_for("admin_registrations"))

    # Collect all To emails
    to_emails_raw = request.form.getlist("to_email")
    to_emails = list(dict.fromkeys(e.strip() for e in to_emails_raw if e.strip()))
    if not to_emails and row["email"]:
        to_emails = [row["email"].strip()]
    if not to_emails:
        msg = "No email addresses provided."
        if is_ajax:
            return jsonify({"ok": False, "message": msg}), 400
        flash(msg, "danger")
        return redirect(request.referrer or url_for("admin_registrations"))

    # Collect all CC emails
    cc_emails_raw = request.form.getlist("cc_email")
    cc_emails = list(dict.fromkeys(e.strip() for e in cc_emails_raw if e.strip()))

    venue = (request.form.get("venue") or "").strip()
    qualifier_details = (request.form.get("qualifier_details") or "").strip()
    event_date = (request.form.get("event_date") or "").strip()
    event_time = (request.form.get("event_time") or "").strip()
    description = (request.form.get("description") or "").strip()
    custom_subject = (request.form.get("subject") or "").strip()

    # Save qualifier venue/date/time updates from email modal
    eq_ids = request.form.getlist("eq_id")
    eq_venues = request.form.getlist("eq_venue")
    eq_dates = request.form.getlist("eq_date")
    eq_times = request.form.getlist("eq_time")
    for i, eqid in enumerate(eq_ids):
        if not eqid:
            continue
        eq_venue_val = (eq_venues[i] if i < len(eq_venues) else "").strip()
        eq_date_val = (eq_dates[i] if i < len(eq_dates) else "").strip()
        eq_time_val = (eq_times[i] if i < len(eq_times) else "").strip()
        eq_date_part = eq_date_val
        if eq_time_val:
            eq_date_part = f"{eq_date_val} {eq_time_val}" if eq_date_val else eq_time_val
        if eq_venue_val or eq_date_part:
            execute(
                "UPDATE qualifiers SET location = ?, qualifier_date = ? WHERE id = ?",
                (eq_venue_val or "TBD", eq_date_part or "", int(eqid)),
            )

    try:
        with db() as conn:
            result = enqueue_status_email(
                conn, reg_id, row["payment_status"],
                sent_by=session.get("username", "admin"), priority=2,
            )
            conn.commit()
        wake_worker()
        ok = bool(result and result.get("ok"))
        msg = "Email sent instantly." if ok else result.get("message", "Email could not be sent.")
        category = "success" if ok else "danger"
    except Exception as exc:
        logger.exception("Manual email queue failed for reg %d: %s", reg_id, exc)
        ok = False
        msg = "Email could not be sent."
        category = "danger"

    if is_ajax:
        return jsonify({"ok": ok, "message": msg, "email_count": query("SELECT COUNT(*) AS c FROM email_logs WHERE registration_id = ?", (reg_id,), one=True)["c"]})
    flash(msg, category)
    return redirect(request.referrer or url_for("admin_registrations"))


@app.route("/admin/registrations/<int:reg_id>/delete", methods=["POST"])
@admin_required
@permission_required("registrations_delete")
def delete_registration(reg_id):
    row = query("SELECT proof_image FROM registrations WHERE id = ?", (reg_id,), one=True)
    if row:
        delete_upload(row["proof_image"])
    execute("DELETE FROM registrations WHERE id = ?", (reg_id,))
    flash("Registration deleted.", "success")
    return redirect(request.referrer or url_for("admin_registrations"))


@app.route("/admin/registrations/bulk-status", methods=["POST"])
@admin_required
@permission_required("confirm_payments")
def bulk_update_status():
    """Bulk-change payment status for selected registrations."""
    is_ajax = request.headers.get("X-Requested-With") == "XMLHttpRequest"
    reg_ids_raw = request.form.getlist("reg_ids") or (
        (request.json or {}).get("reg_ids", []) if request.is_json else []
    )
    new_status = (request.form.get("payment_status") or "").lower()
    reason = (request.form.get("reason") or "").strip()

    if new_status not in ("paid", "unpaid", "under_verification", "rejected", "cancelled", "refunded"):
        msg = "Invalid payment status."
        return jsonify({"ok": False, "message": msg}), 400 if is_ajax else flash(msg, "danger") or redirect(request.referrer or url_for("admin_registrations"))

    reg_ids = [int(rid) for rid in reg_ids_raw if str(rid).strip().isdigit()]
    if not reg_ids:
        msg = "No registrations selected."
        if is_ajax:
            return jsonify({"ok": False, "message": msg}), 400
        flash(msg, "danger")
        return redirect(url_for("admin_registrations"))

    # Update all statuses + enqueue emails inside one transaction
    updated = 0
    try:
        with db() as conn:
            for rid in reg_ids:
                prev = conn.execute(
                    "SELECT payment_status FROM registrations WHERE id = ?",
                    (rid,),
                ).fetchone()
                if not prev:
                    continue
                old_s = (prev["payment_status"] or "").lower().replace(" ", "_")
                if old_s == new_status:
                    continue
                confirmed_amount = (
                    query(
                        "SELECT total_payable_saved FROM registrations WHERE id = ?",
                        (rid,), one=True,
                    ) or {}
                ).get("total_payable_saved", "Rs. 0") if new_status == "paid" else "Rs. 0"
                conn.execute(
                    "UPDATE registrations SET payment_status = ?, amount_paid = ? WHERE id = ?",
                    (new_status, confirmed_amount, rid),
                )
                updated += 1

            # Bulk enqueue
            op_id, jobs_created = enqueue_bulk_emails(
                conn, reg_ids, new_status, reason,
                sent_by=session.get("username", "admin"),
            )
            conn.commit()
        wake_worker()

        msg = f"{updated} registration(s) updated. {jobs_created} email(s) queued."
        if is_ajax:
            return jsonify({
                "ok": True,
                "message": msg,
                "operation_id": op_id,
                "jobs_created": jobs_created,
            })
        flash(msg, "success")
    except Exception as e:
        msg = f"Bulk update error: {e}"
        if is_ajax:
            return jsonify({"ok": False, "message": msg}), 500
        flash(msg, "danger")

    return redirect(request.referrer or url_for("admin_registrations"))


@app.route("/admin/queue-status")
@admin_required
@permission_required("view_dashboard")
def queue_status():
    """API: current email queue status."""
    return jsonify(get_queue_status())


@app.route("/admin/queue/bulk/<int:op_id>/status")
@admin_required
@permission_required("view_dashboard")
def bulk_operation_status(op_id):
    """API: status of a specific bulk operation."""
    status = get_bulk_operation_status(op_id)
    if not status:
        return jsonify({"ok": False, "message": "Operation not found."}), 404
    return jsonify({"ok": True, "operation": status})


@app.route("/admin/queue/retry-failed", methods=["POST"])
@admin_required
@permission_required("confirm_payments")
def retry_failed():
    """Reset failed jobs to pending for retry."""
    is_ajax = request.headers.get("X-Requested-With") == "XMLHttpRequest"
    op_id = request.form.get("bulk_operation_id", type=int)
    ok = retry_failed_jobs(op_id)
    msg = "Failed jobs queued for retry." if ok else "Retry failed."
    if is_ajax:
        return jsonify({"ok": ok, "message": msg})
    flash(msg, "success" if ok else "danger")
    return redirect(request.referrer or url_for("admin_registrations"))
