from __future__ import annotations

import secrets
from typing import Any
from urllib.parse import quote

from flask import jsonify, redirect, request
import psycopg


def register_access_request_submission(app, deps) -> None:
    """Register access request submission routes."""

    @app.route("/api/access/request/availability", methods=["GET"])
    def request_access_availability() -> Any:
        """Report whether a requested username can start access signup."""

        if not deps.settings.ACCESS_REQUEST_ENABLED:
            return jsonify({"error": "request access disabled"}), 503
        if not deps.configured():
            return jsonify({"error": "server not deps.configured"}), 503

        username = (request.args.get("username") or "").strip()
        error = deps._validate_username(username)
        if error:
            return jsonify({"available": False, "reason": "invalid", "detail": error})

        if deps.admin_client().ready() and deps.admin_client().find_user(username):
            return jsonify({"available": False, "reason": "exists", "detail": "username already exists"})

        try:
            with deps.connect() as conn:
                existing = conn.execute(
                    """
                    SELECT status
                    FROM access_requests
                    WHERE username = %s
                    ORDER BY created_at DESC
                    LIMIT 1
                    """,
                    (username,),
                ).fetchone()
        except Exception:
            return jsonify({"error": "failed to check availability"}), 502

        if existing:
            status = str(existing.get("status") or "")
            return jsonify(
                {
                    "available": False,
                    "reason": "requested",
                    "status": deps._normalize_status(status),
                }
            )

        return jsonify({"available": True})

    @app.route("/api/access/request", methods=["POST"])
    def request_access() -> Any:
        """Create or refresh an email-verified access request.

        WHY: submissions are anonymous, so this route validates names, rate
        limits by client/request, and emails a proof token before queuing work.
        """

        if not deps.settings.ACCESS_REQUEST_ENABLED:
            return jsonify({"error": "request access disabled"}), 503
        if not deps.configured():
            return jsonify({"error": "server not deps.configured"}), 503

        ip = deps._client_ip()
        username, email, note, first_name, last_name = deps._extract_request_payload()
        first_name = deps._normalize_name(first_name)
        last_name = deps._normalize_name(last_name)

        rate_key = ip
        if username:
            rate_key = f"{ip}:{username}"
        if not deps.rate_limit_allow(
            rate_key,
            key="access_request_submit",
            limit=deps.settings.ACCESS_REQUEST_SUBMIT_RATE_LIMIT,
            window_sec=deps.settings.ACCESS_REQUEST_SUBMIT_RATE_WINDOW_SEC,
        ):
            return jsonify({"error": "rate limited"}), 429

        username_error = deps._validate_username(username)
        if username_error:
            return jsonify({"error": username_error}), 400
        name_error = deps._validate_name(first_name, label="first name", required=False)
        if name_error:
            return jsonify({"error": name_error}), 400
        name_error = deps._validate_name(last_name, label="last name", required=True)
        if name_error:
            return jsonify({"error": name_error}), 400
        if not email:
            return jsonify({"error": "email is required"}), 400
        if "@" not in email:
            return jsonify({"error": "invalid email"}), 400
        email_lower = email.lower()
        if email_lower.endswith(f"@{deps.settings.MAILU_DOMAIN.lower()}") and (
            email_lower not in deps.settings.ACCESS_REQUEST_INTERNAL_EMAIL_ALLOWLIST
        ):
            return jsonify({"error": "email must be an external address"}), 400

        if deps.admin_client().ready() and deps.admin_client().find_user(username):
            return jsonify({"error": "username already exists"}), 409
        if deps.admin_client().ready() and deps.admin_client().find_user_by_email(email):
            return jsonify({"error": "email is already associated with an existing Atlas account"}), 409

        try:
            with deps.connect() as conn:
                existing = conn.execute(
                    """
                    SELECT request_code, status
                    FROM access_requests
                    WHERE username = %s AND status IN (%s, 'pending')
                    ORDER BY created_at DESC
                    LIMIT 1
                    """,
                    (username, deps.EMAIL_VERIFY_PENDING_STATUS),
                ).fetchone()
                if existing:
                    existing_status = str(existing.get("status") or "")
                    request_code = str(existing.get("request_code") or "")
                    if existing_status != deps.EMAIL_VERIFY_PENDING_STATUS:
                        return jsonify({"ok": True, "request_code": request_code, "status": existing_status})

                    token = secrets.token_urlsafe(24)
                    token_hash = deps._hash_verification_token(token)
                    conn.execute(
                        """
                        UPDATE access_requests
                        SET contact_email = %s,
                            note = %s,
                            first_name = %s,
                            last_name = %s,
                            email_verification_token_hash = %s,
                            email_verification_sent_at = NOW(),
                            email_verified_at = NULL
                        WHERE request_code = %s AND status = %s
                        """,
                        (
                            email,
                            note or None,
                            first_name or None,
                            last_name or None,
                            token_hash,
                            request_code,
                            deps.EMAIL_VERIFY_PENDING_STATUS,
                        ),
                    )

                    try:
                        deps._send_verification_email(request_code=request_code, email=email, token=token)
                    except deps.MailerError:
                        return (
                            jsonify({"error": "failed to send verification email", "request_code": request_code}),
                            502,
                        )
                    return jsonify({"ok": True, "request_code": request_code, "status": deps.EMAIL_VERIFY_PENDING_STATUS})

                request_code = deps._random_request_code(username)
                token = secrets.token_urlsafe(24)
                token_hash = deps._hash_verification_token(token)
                try:
                    conn.execute(
                        """
                        INSERT INTO access_requests
                          (request_code, username, contact_email, note, first_name, last_name, status,
                           email_verification_token_hash, email_verification_sent_at)
                        VALUES
                          (%s, %s, %s, %s, %s, %s, %s, %s, NOW())
                        """,
                        (
                            request_code,
                            username,
                            email,
                            note or None,
                            first_name or None,
                            last_name or None,
                            deps.EMAIL_VERIFY_PENDING_STATUS,
                            token_hash,
                        ),
                    )
                except psycopg.errors.UniqueViolation:
                    conn.rollback()
                    existing = conn.execute(
                        """
                        SELECT request_code, status
                        FROM access_requests
                        WHERE username = %s AND status IN (%s, 'pending')
                        ORDER BY created_at DESC
                        LIMIT 1
                        """,
                        (username, deps.EMAIL_VERIFY_PENDING_STATUS),
                    ).fetchone()
                    if not existing:
                        raise
                    return jsonify({"ok": True, "request_code": existing["request_code"], "status": existing["status"]})

                try:
                    deps._send_verification_email(request_code=request_code, email=email, token=token)
                except deps.MailerError:
                    return jsonify({"error": "failed to send verification email", "request_code": request_code}), 502
        except Exception:
            return jsonify({"error": "failed to submit request"}), 502

        return jsonify({"ok": True, "request_code": request_code, "status": deps.EMAIL_VERIFY_PENDING_STATUS})

    @app.route("/api/access/request/verify", methods=["POST"])
    def request_access_verify() -> Any:
        """Verify a submitted access request using a request code and token."""

        if not deps.settings.ACCESS_REQUEST_ENABLED:
            return jsonify({"error": "request access disabled"}), 503
        if not deps.configured():
            return jsonify({"error": "server not deps.configured"}), 503

        ip = deps._client_ip()
        if not deps.rate_limit_allow(
            ip,
            key="access_request_verify",
            limit=60,
            window_sec=60,
        ):
            return jsonify({"error": "rate limited"}), 429

        payload = request.get_json(silent=True) or {}
        code = (payload.get("request_code") or payload.get("code") or "").strip()
        reveal_initial_password = bool(
            payload.get("reveal_initial_password") or payload.get("reveal_password")
        )
        token = (payload.get("token") or payload.get("verify") or "").strip()
        if not code:
            return jsonify({"error": "request_code is required"}), 400
        if not token:
            return jsonify({"error": "token is required"}), 400

        if not deps.rate_limit_allow(
            f"{ip}:{code}",
            key="access_request_verify_code",
            limit=30,
            window_sec=60,
        ):
            return jsonify({"error": "rate limited"}), 429

        try:
            with deps.connect() as conn:
                status = deps._verify_request(conn, code, token)
            return jsonify({"ok": True, "status": status})
        except deps.VerificationError as exc:
            return jsonify({"error": exc.message}), exc.status_code
        except Exception:
            return jsonify({"error": "failed to verify"}), 502

    @app.route("/api/access/request/verify-link", methods=["GET"])
    def request_access_verify_link() -> Any:
        """Verify an emailed access-request link and redirect to the UI."""

        if not deps.settings.ACCESS_REQUEST_ENABLED:
            return jsonify({"error": "request access disabled"}), 503
        if not deps.configured():
            return jsonify({"error": "server not deps.configured"}), 503

        code = (request.args.get("code") or "").strip()
        token = (request.args.get("token") or "").strip()
        if not code or not token:
            return redirect(f"/request-access?code={quote(code)}&verify_error=missing+token")

        try:
            with deps.connect() as conn:
                deps._verify_request(conn, code, token)
            return redirect(f"/request-access?code={quote(code)}&verified=1")
        except deps.VerificationError as exc:
            return redirect(f"/request-access?code={quote(code)}&verify_error={quote(exc.message)}")
        except Exception:
            return redirect(f"/request-access?code={quote(code)}&verify_error=failed+to+verify")

    @app.route("/api/access/request/resend", methods=["POST"])
    def request_access_resend() -> Any:
        """Send a fresh verification token for a pending access request."""

        if not deps.settings.ACCESS_REQUEST_ENABLED:
            return jsonify({"error": "request access disabled"}), 503
        if not deps.configured():
            return jsonify({"error": "server not deps.configured"}), 503

        ip = deps._client_ip()
        if not deps.rate_limit_allow(
            ip,
            key="access_request_resend",
            limit=30,
            window_sec=60,
        ):
            return jsonify({"error": "rate limited"}), 429

        payload = request.get_json(silent=True) or {}
        code = (payload.get("request_code") or payload.get("code") or "").strip()
        if not code:
            return jsonify({"error": "request_code is required"}), 400

        if not deps.rate_limit_allow(
            f"{ip}:{code}",
            key="access_request_resend_code",
            limit=10,
            window_sec=300,
        ):
            return jsonify({"error": "rate limited"}), 429

        try:
            with deps.connect() as conn:
                row = conn.execute(
                    """
                    SELECT status, contact_email
                    FROM access_requests
                    WHERE request_code = %s
                    """,
                    (code,),
                ).fetchone()
                if not row:
                    return jsonify({"error": "not found"}), 404

                status = deps._normalize_status(row.get("status") or "")
                if status != deps.EMAIL_VERIFY_PENDING_STATUS:
                    return jsonify({"ok": True, "status": status})

                email = str(row.get("contact_email") or "").strip()
                if not email:
                    return jsonify({"error": "missing email"}), 409

                token = secrets.token_urlsafe(24)
                token_hash = deps._hash_verification_token(token)
                conn.execute(
                    """
                    UPDATE access_requests
                    SET email_verification_token_hash = %s,
                        email_verification_sent_at = NOW()
                    WHERE request_code = %s AND status = %s
                    """,
                    (token_hash, code, deps.EMAIL_VERIFY_PENDING_STATUS),
                )

                try:
                    deps._send_verification_email(request_code=code, email=email, token=token)
                except deps.MailerError:
                    return jsonify({"error": "failed to send verification email", "request_code": code}), 502
            return jsonify({"ok": True, "status": deps.EMAIL_VERIFY_PENDING_STATUS})
        except Exception:
            return jsonify({"error": "failed to resend verification"}), 502
