"""Routes: registration"""
import sqlite3
import time
from datetime import datetime
from flask import flash, jsonify, redirect, render_template, request, session, url_for

from nexora.config import PUBLIC_CACHE_SECONDS, logger
from nexora.core import app
from nexora.database.connection import cached_query, db, query
from nexora.utils.helpers import amount_value
from nexora.utils.formatting import format_qualifier_parts, split_venue_location
from nexora.utils.validators import field_duplicate_message, validate_registration_constraints
from nexora.services.uploads import process_image_upload
from nexora.services.pricing import (
    discount_active,
    discount_pct_value, get_discount_type,
    effective_max_members,
    game_price_for_size,
    game_status,
    registration_effective_price,
)
from nexora.services.qualifiers import (
    calc_qualifier_fee,
    get_game_qualifiers,
    qualifier_fee_type,
    qualifiers_enabled,
    registration_qualifier_details,
)
from nexora.services.ticket_codes import make_ticket_serial, make_ticket_token
from nexora.services.registrations import registration_details
from nexora.database.schema import (
    ensure_member_tickets_for_registration,
    ensure_qualifier_entry_tickets_for_registration,
)


@app.route("/register", methods=["GET", "POST"])
def register():
    settings = cached_query("settings", "SELECT * FROM settings WHERE id = 1", one=True, ttl=PUBLIC_CACHE_SECONDS)
    games = cached_query("register_games", "SELECT * FROM games WHERE active = 1 AND deleted_at = '' ORDER BY id DESC", ttl=PUBLIC_CACHE_SECONDS)

    if not settings or not settings["registration_open"]:
        return render_template("register.html", games=games, closed=True, selected_game=None)

    open_games = [g for g in games if game_status(g, settings["registration_open"] if settings else 1)[0]]

    if request.method == "POST":
        t0 = time.perf_counter()
        stage_t = t0

        def log_stage(name):
            nonlocal stage_t
            now = time.perf_counter()
            logger.info("Registration timing stage=%s elapsed_ms=%.1f total_ms=%.1f", name, (now - stage_t) * 1000, (now - t0) * 1000)
            stage_t = now

        dedupe_key = request.form.get("submission_key", "").strip()
        if dedupe_key and session.get("last_registration_submission_key") == dedupe_key:
            flash("Registration already submitted. Duplicate entry was prevented.", "warning")
            return redirect(url_for("register"))

        chosen = next((g for g in games if str(g["id"]) == str(request.form.get("game_id"))), None)
        if not chosen or not game_status(chosen, settings["registration_open"] if settings else 1)[0]:
            flash("Registration for that game is currently closed.", "danger")
            return redirect(url_for("register"))
        selected_team_size = request.form.get("team_size", type=int) or 1
        min_m = int(chosen["min_members"] or 1) if "min_members" in chosen.keys() else 1
        max_m = effective_max_members(chosen)
        if selected_team_size < min_m:
            flash(f"Minimum {min_m} player{'s' if min_m != 1 else ''} required for this game.", "danger")
            return redirect(url_for("register", game=chosen["id"]))
        if selected_team_size > max_m:
            flash(f"Maximum {max_m} player{'s' if max_m != 1 else ''} allowed for this game.", "danger")
            return redirect(url_for("register", game=chosen["id"]))
        if not game_price_for_size(chosen, selected_team_size):
            flash(f"Price is not configured for {selected_team_size} player(s).", "danger")
            return redirect(url_for("register", game=chosen["id"]))
        entry_type = "group" if selected_team_size > 1 else "individual"
        if entry_type == "individual" and not chosen["individual_price"]:
            flash("Individual entry is not available for this game.", "danger")
            return redirect(url_for("register", game=chosen["id"]))

        team_name = request.form.get("team_name", "").strip()
        if not team_name:
            flash("Team Name / Nick Name is required.", "danger")
            return redirect(url_for("register", game=chosen["id"]))

        # Proof is waived for a 100% active discount OR a game marked Free Entry.
        is_full_discount = bool(discount_active(chosen) and discount_pct_value(chosen) >= 100)
        game_is_free = str(chosen["is_free"]) == "1" if "is_free" in chosen.keys() else False
        proof_waived = is_full_discount or game_is_free
        proof = ""
        if not proof_waived:
            proof, proof_error = process_image_upload(
                request.files.get("proof_image"), category="registrations", game_title=chosen["title"],
                override_filename=(request.form.get("email", "") or "player").strip(),
            )
            log_stage("proof_image")
            if proof_error:
                flash(proof_error, "danger")
                return redirect(url_for("register", game=chosen["id"]))
        else:
            log_stage("proof_image")
        initial_payment_status = "paid" if proof_waived else "under_verification"
        initial_amount_paid = "Rs. 0" if proof_waived else ""
        members = []
        member_names = request.form.getlist("member_name[]")
        member_emails = request.form.getlist("member_email[]")
        member_phones = request.form.getlist("member_phone[]")
        member_cnic_list = request.form.getlist("member_cnic[]")
        if entry_type == "group":
            required_members = max(0, selected_team_size - 1)
            for index in range(required_members):
                name = member_names[index].strip() if index < len(member_names) else ""
                email = member_emails[index].strip() if index < len(member_emails) else ""
                phone = member_phones[index].strip() if index < len(member_phones) else ""
                cnic = member_cnic_list[index].strip() if index < len(member_cnic_list) else ""
                # Email is optional per member: a missing address only removes
                # that member from the confirmation-email CC list, it must not
                # block the registration itself.
                if not name or not phone:
                    flash(f"Please complete details for team member {index + 1}.", "danger")
                    return redirect(url_for("register", game=chosen["id"]))
                members.append(f"Member {index + 1}: {name} | {email} | {phone} | {cnic}")

        members_text = "\n".join(members)

        # Validate qualifier selections if qualifiers are enabled for this game
        selected_q_ids = []
        selected_q_names = []
        qualifier_fee_str = ""
        q_result = {"total_value": 0, "total_fee": "Rs. 0"}
        if qualifiers_enabled(chosen):
            raw_ids_str = request.form.get("qualifier_ids", "").strip()
            selected_q_ids = [int(qid) for qid in raw_ids_str.split(",") if qid.strip().isdigit()]
            if not selected_q_ids:
                flash("You must select at least one qualifier for this game before you can register.", "danger")
                return redirect(url_for("register", game=chosen["id"]))
            if selected_q_ids:
                q_rows = query(
                    "SELECT * FROM qualifiers WHERE id IN ({}) AND game_id = ?".format(
                        ",".join("?" for _ in selected_q_ids)
                    ),
                    (*selected_q_ids, chosen["id"]),
                )
                selected_q_names = [q["qualifier_name"] for q in q_rows]
                q_result = calc_qualifier_fee(chosen, selected_q_ids)
                qualifier_fee_str = q_result["total_fee"]

        # ── Transactional validation + insert ──
        try:
            with db() as conn:
                errors = validate_registration_constraints(
                    conn,
                    email=request.form.get("email", ""),
                    cnic=request.form.get("cnic", ""),
                    game_id=chosen["id"],
                    team_name=team_name,
                    entry_type=entry_type,
                    members_text=members_text,
                    selected_q_ids=selected_q_ids,
                )
                if errors:
                    if dedupe_key:
                        session["last_registration_submission_key"] = dedupe_key
                    for err in errors:
                        flash(err, "danger")
                    return redirect(url_for("register", game=chosen["id"]))

                ticket_token = make_ticket_token()
                now_ts = datetime.now().isoformat(timespec="seconds")

                # Calculate and freeze total payable at registration time
                display_member_count = selected_team_size
                base_price_info = registration_effective_price(chosen, entry_type, display_member_count)
                base_val = amount_value(base_price_info["discounted"])
                q_val = float(q_result.get("total_value", 0) if selected_q_ids else 0)
                # When qualifiers enabled, qualifier fee IS the entry fee — no game fee on top
                if selected_q_ids and q_val > 0:
                    saved_total_payable = f"Rs. {q_val:,.0f}"
                else:
                    saved_total_payable = f"Rs. {base_val:,.0f}"
                saved_original_price = base_price_info.get("original", "Rs. 0")
                saved_discounted_price = base_price_info.get("discounted", "Rs. 0")

                # Snapshot fields for PDF Payment Breakdown
                if selected_q_ids:
                    snap_orig_amount = q_result.get("total_original_fee", "Rs. 0")
                    snap_disc_amount = q_result.get("total_discount_fee", "Rs. 0")
                    snap_disc_type = "percentage" if q_result.get("discount_pct", 0) > 0 else "none"
                    snap_disc_val = str(q_result.get("discount_pct", 0))
                else:
                    snap_orig_amount = saved_original_price
                    snap_disc_amount = f"Rs. {max(0, amount_value(saved_original_price) - amount_value(saved_discounted_price)):,.0f}"
                    # chosen is sqlite3.Row, so convert to dict or use 'in chosen.keys()'
                    chosen_dict = dict(chosen)
                    snap_disc_type = "percentage" if "discount" in str(chosen_dict.get("discount_type", "percentage")) else "fixed"
                    snap_disc_val = str(chosen_dict.get("discount_pct", 0))

                cur = conn.execute(
                    """
                    INSERT INTO registrations
                    (full_name, email, phone, cnic, institution_type, university, roll_no, entry_type,
                     game_id, team_name, leader_name, members, payment_method, payment_status,
                     proof_image, ticket_token, ticket_serial, created_at, amount_paid,
                     selected_qualifiers, qualifier_count, qualifier_fee, total_payable_saved,
                     original_price_saved, discounted_price_saved, original_amount,
                     discount_type_at_registration, discount_value_at_registration, discount_amount, final_payable_amount)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    (
                        request.form["full_name"].strip(),
                        request.form.get("email", "").strip(),
                        request.form.get("phone", "").strip(),
                        request.form.get("cnic", "").strip(),
                        request.form.get("institution_type", "University").strip() or "University",
                        request.form["university"].strip(),
                        request.form.get("roll_no", "").strip(),
                        entry_type,
                        request.form["game_id"],
                        team_name,
                        request.form.get("leader_name", "").strip(),
                        members_text,
                        settings["payment_method_label"] or "G-Pay",
                        initial_payment_status,
                        proof,
                        ticket_token,
                        "",
                        now_ts,
                        initial_amount_paid,
                        ",".join(str(qid) for qid in selected_q_ids),
                        str(len(selected_q_ids)),
                        qualifier_fee_str,
                        saved_total_payable,
                        saved_original_price,
                        saved_discounted_price,
                        snap_orig_amount,
                        snap_disc_type,
                        snap_disc_val,
                        snap_disc_amount,
                        saved_total_payable,
                    ),
                )
                registration_id = cur.lastrowid
                conn.execute(
                    "UPDATE registrations SET ticket_serial = ? WHERE id = ?",
                    (make_ticket_serial(registration_id, chosen["title"], settings["event_date"]), registration_id),
                )
                ensure_member_tickets_for_registration(conn, registration_id)
                ensure_qualifier_entry_tickets_for_registration(conn, registration_id)

                conn.commit()
                log_stage("database")

            # Enqueue the initial registration email after the registration
            # transaction commits, because the email builder reads details
            # through a separate database connection.
            email_result = {"ok": True, "message": "Email sent instantly.", "emails_sent": 0, "email_type": "initial"}
            try:
                from nexora.services.email_service import send_status_email
                email_result = send_status_email(
                    registration_id, initial_payment_status,
                    force=True, sent_by="system"
                ) or {"ok": False, "message": "Email could not be sent.", "emails_sent": 0, "email_type": "initial"}

            except Exception as e:
                logger.warning("Initial email enqueue failed: %s", e)
                email_result = {"ok": False, "message": "Email could not be sent.", "emails_sent": 0, "email_type": "initial"}

        except sqlite3.IntegrityError:
            if dedupe_key:
                session["last_registration_submission_key"] = dedupe_key
            flash("This Team Name / Nick Name is already taken. Please try another one.", "danger")
            return redirect(url_for("register", game=chosen["id"]))
        except Exception as e:
            import logging
            logging.getLogger("nexora").exception("Registration submit error: %s", e)
            if dedupe_key:
                session["last_registration_submission_key"] = dedupe_key
            flash("An unexpected error occurred. Please try again.", "danger")
            return redirect(url_for("register", game=chosen["id"]))

        if dedupe_key:
            session["last_registration_submission_key"] = dedupe_key
        saved_serial = query("SELECT ticket_serial FROM registrations WHERE id = ?", (registration_id,), one=True)
        serial_text = saved_serial["ticket_serial"] if saved_serial and saved_serial["ticket_serial"] else f"NX-{registration_id:04d}"

        log_stage("response_ready")
        sent_ok = bool(email_result and email_result.get("emails_sent", 0) > 0)
        session["registration_success"] = {
            "id": registration_id,
            "ref": serial_text,
            "email_sent": sent_ok,
            "email_recipients": [],
            "email_configured": True,
            "email_message": (
                "Your confirmation email with ticket PDF has been sent."
                if (email_result or {}).get("ok")
                else (email_result or {}).get("message", "")
            ),
        }
        return redirect(url_for("registration_success"))

    sel = request.args.get("game", type=int)
    selected_game = next((g for g in games if g["id"] == sel), None)
    if selected_game and not game_status(selected_game, settings["registration_open"])[0]:
        flash("Registration for that game is closed. Please choose another title.", "warning")
        selected_game = None
    return render_template(
        "register.html",
        games=games,
        open_games=open_games,
        closed=False,
        selected_game=selected_game,
        qualifiers=get_game_qualifiers(selected_game["id"]) if selected_game else [],
    )


@app.route("/register/success")
def registration_success():
    """Post-submit confirmation page. The registration id travels through the
    server session only, so a visitor can only ever see their own submission."""
    payload = session.pop("registration_success", None)
    if not payload:
        return redirect(url_for("register"))
    reg_id = payload.get("id")
    details = registration_details(reg_id) if reg_id else None
    if not details:
        return redirect(url_for("register"))
    return render_template(
        "registration_success.html",
        details=details,
        qualifier_details=registration_qualifier_details(details),
        email_sent=payload.get("email_sent"),
        email_recipients=payload.get("email_recipients", []),
        email_configured=payload.get("email_configured", True),
        email_message=payload.get("email_message", ""),
    )


@app.route("/api/validate-registration", methods=["POST"])
def api_validate_registration():
    """AJAX endpoint: check constraints before form submit. Returns JSON."""
    data = request.get_json(silent=True) or {}
    email = data.get("email", "")
    cnic = data.get("cnic", "")
    game_id = data.get("game_id")
    team_name = data.get("team_name", "")
    entry_type = data.get("entry_type", "individual")
    members_text = data.get("members_text", "")
    selected_q_ids = data.get("selected_q_ids", [])

    if not game_id:
        return jsonify({"ok": True, "errors": []})

    with db() as conn:
        errors = validate_registration_constraints(
            conn,
            email=email,
            cnic=cnic,
            game_id=game_id,
            team_name=team_name,
            entry_type=entry_type,
            members_text=members_text,
            selected_q_ids=selected_q_ids,
        )
    return jsonify({"ok": not errors, "errors": errors})


@app.route("/api/check-duplicate", methods=["POST"])
def api_check_duplicate():
    """Live per-field duplicate check used by the registration form.

    Returns JSON: {"ok": true, "taken": bool, "message": "..."}.
    `field` must be one of team_name / cnic / email.
    """
    data = request.get_json(silent=True) or {}
    field = data.get("field", "")
    value = (data.get("value") or "").strip()
    game_id = data.get("game_id")
    allowed = {"team_name", "cnic", "email"}
    if field not in allowed or not value or not game_id:
        return jsonify({"ok": True, "taken": False, "message": ""})
    with db() as conn:
        message = field_duplicate_message(
            conn, field, value, game_id, data.get("selected_q_ids", [])
        )
    return jsonify({"ok": True, "taken": bool(message), "message": message})


@app.route("/api/game-qualifiers/<int:game_id>")
def api_game_qualifiers(game_id):
    """Return qualifier configuration and options for a game as JSON."""
    game = query("SELECT * FROM games WHERE id = ?", (game_id,), one=True)
    if not game:
        return jsonify({"ok": False, "error": "Game not found"})
    enabled = qualifiers_enabled(game)
    if not enabled:
        return jsonify({"ok": True, "enabled": False, "qualifiers": []})
    qualifiers = get_game_qualifiers(game_id)
    active_discount = discount_active(game)
    pricing_preview = calc_qualifier_fee(game, [q["id"] for q in qualifiers])
    fee_type = qualifier_fee_type(game)
    pkg_fee = 0
    pkg_size = 0
    addl_fee = 0
    if fee_type == "package":
        pkg_fee = max(0, amount_value(game["qualifier_package_fee"] if "qualifier_package_fee" in game.keys() else "0"))
        try:
            pkg_size = max(1, int(game["qualifier_package_size"] if "qualifier_package_size" in game.keys() else 1))
        except (TypeError, ValueError):
            pkg_size = 1
        addl_fee = max(0, amount_value(game["qualifier_additional_fee"] if "qualifier_additional_fee" in game.keys() else "0"))
    return jsonify({
        "ok": True,
        "enabled": True,
        "fee_type": fee_type,
        "pricing_rule": "package" if fee_type == "package" else "per_qualifier",
        "discount_active": bool(active_discount),
        "discount_pct": discount_pct_value(game) if active_discount else 0,
        "discount_type": get_discount_type(game) if active_discount else "percentage",
        "package_fee": pkg_fee,
        "package_size": pkg_size,
        "additional_fee": addl_fee,
        "package_label": pricing_preview.get("breakdown", ""),
        "pricing_items": pricing_preview.get("items", []),
        "team_label": game["team_label"] if "team_label" in game.keys() else "",
        "qualifiers": [
            {
                "id": q["id"],
                "name": q["qualifier_name"],
                "number": q["qualifier_number"],
                "fee": q["fee"],
                "date_display": q["qualifier_date"] if "qualifier_date" in q.keys() else "",
                "time_display": q["qualifier_time"] if "qualifier_time" in q.keys() else "",
                "end_time": q["qualifier_end_time"] if "qualifier_end_time" in q.keys() else "",
                "venue": q["location"] if "location" in q.keys() else "",
                "venue_address": q["venue_address"] if "venue_address" in q.keys() else "",
            }
            for q in qualifiers
        ],
    })
