from __future__ import annotations

from datetime import datetime, timezone
from typing import Any

from flask import jsonify, redirect, request


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

    @app.route("/api/access/request/status", methods=["POST"])
    def request_access_status() -> Any:
        """Return current provisioning and onboarding status for a request.

        WHY: this endpoint is polled by the public flow, so it also advances
        safe automatic transitions before rendering the latest state.
        """

        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_status",
            limit=deps.settings.ACCESS_REQUEST_STATUS_RATE_LIMIT,
            window_sec=deps.settings.ACCESS_REQUEST_STATUS_RATE_WINDOW_SEC,
        ):
            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")
        )
        if not code:
            return jsonify({"error": "request_code is required"}), 400

        # Additional per-code limiter to avoid global NAT rate-limit blowups.
        if not deps.rate_limit_allow(
            f"{ip}:{code}",
            key="access_request_status_code",
            limit=max(20, deps.settings.ACCESS_REQUEST_STATUS_RATE_LIMIT),
            window_sec=deps.settings.ACCESS_REQUEST_STATUS_RATE_WINDOW_SEC,
        ):
            return jsonify({"error": "rate limited"}), 429

        try:
            with deps.connect() as conn:
                row = conn.execute(
                    """
                    SELECT status,
                           username,
                           initial_password,
                           initial_password_revealed_at,
                           email_verified_at
                    FROM access_requests
                    WHERE request_code = %s
                    """,
                    (code,),
                ).fetchone()
                if not row:
                    return jsonify({"error": "not found"}), 404
                current_status = deps._normalize_status(row.get("status") or "")
                if current_status == "accounts_building" and not deps.ariadne_client.enabled():
                    try:
                        deps.provision_access_request(code)
                    except Exception:
                        pass
                    row = conn.execute(
                        """
                        SELECT status,
                               username,
                               initial_password,
                               initial_password_revealed_at,
                               email_verified_at
                        FROM access_requests
                        WHERE request_code = %s
                        """,
                        (code,),
                    ).fetchone()
                    if not row:
                        return jsonify({"error": "not found"}), 404

                status = deps._advance_status(conn, code, row.get("username") or "", row.get("status") or "")
                response: dict[str, Any] = {
                    "ok": True,
                    "status": status,
                    "username": row.get("username") or "",
                    "email_verified": bool(row.get("email_verified_at")),
                }
                task_rows = conn.execute(
                    """
                    SELECT task, status, detail, updated_at
                    FROM access_request_tasks
                    WHERE request_code = %s
                    ORDER BY task
                    """,
                    (code,),
                ).fetchall()
                if task_rows:
                    tasks: list[dict[str, Any]] = []
                    blocked = False
                    for task_row in task_rows:
                        task_name = task_row.get("task") if isinstance(task_row, dict) else None
                        task_status = task_row.get("status") if isinstance(task_row, dict) else None
                        detail = task_row.get("detail") if isinstance(task_row, dict) else None
                        updated_at = task_row.get("updated_at") if isinstance(task_row, dict) else None

                        if isinstance(task_status, str) and task_status == "error":
                            blocked = True

                        task_payload: dict[str, Any] = {
                            "task": task_name or "",
                            "status": task_status or "",
                        }
                        if isinstance(detail, str) and detail:
                            task_payload["detail"] = detail
                        if isinstance(updated_at, datetime):
                            task_payload["updated_at"] = updated_at.astimezone(timezone.utc).isoformat()
                        tasks.append(task_payload)

                    response["tasks"] = tasks
                    response["automation_complete"] = deps.provision_tasks_complete(conn, code)
                    response["blocked"] = blocked
                if status in {"awaiting_onboarding", "ready"}:
                    revealed_at = row.get("initial_password_revealed_at")
                    if isinstance(revealed_at, datetime):
                        response["initial_password_revealed_at"] = revealed_at.astimezone(timezone.utc).isoformat()
                    if reveal_initial_password:
                        password = row.get("initial_password")
                        if isinstance(password, str) and password and revealed_at is None:
                            response["initial_password"] = password
                            conn.execute(
                                "UPDATE access_requests SET initial_password_revealed_at = NOW() WHERE request_code = %s AND initial_password_revealed_at IS NULL",
                                (code,),
                            )
                if status in {"awaiting_onboarding", "ready"}:
                    response["onboarding_url"] = f"/onboarding?code={code}"
                if status in {"awaiting_onboarding", "ready"}:
                    response["onboarding"] = deps._onboarding_payload(conn, code, row.get("username") or "")
                return jsonify(response)
        except Exception:
            return jsonify({"error": "failed to load status"}), 502

    @app.route("/api/access/request/retry", methods=["POST"])
    def request_access_retry() -> Any:
        """Retry failed provisioning tasks for an 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_retry",
            limit=deps.settings.ACCESS_REQUEST_STATUS_RATE_LIMIT,
            window_sec=deps.settings.ACCESS_REQUEST_STATUS_RATE_WINDOW_SEC,
        ):
            return jsonify({"error": "rate limited"}), 429

        payload = request.get_json(silent=True) or {}
        code = (payload.get("request_code") or payload.get("code") or "").strip()
        tasks = payload.get("tasks")
        task_list = [task for task in tasks if isinstance(task, str) and task.strip()] if isinstance(tasks, list) else []
        if not code:
            return jsonify({"error": "request_code is required"}), 400

        if deps.ariadne_client.enabled():
            retry_payload = {"tasks": task_list} if task_list else None
            return deps.ariadne_client.proxy(
                "POST",
                f"/api/access/requests/{code}/retry",
                payload=retry_payload,
            )

        try:
            with deps.connect() as conn:
                row = conn.execute(
                    "SELECT status FROM access_requests WHERE request_code = %s",
                    (code,),
                ).fetchone()
                if not row:
                    return jsonify({"error": "not found"}), 404
                status = row.get("status") or ""
                if status not in {"accounts_building", "approved"}:
                    return jsonify({"error": "request not retryable"}), 409
                conn.execute(
                    "UPDATE access_requests SET provision_attempted_at = NULL WHERE request_code = %s",
                    (code,),
                )
                if task_list:
                    conn.execute(
                        """
                        UPDATE access_request_tasks
                        SET status = 'pending',
                            detail = 'retry requested',
                            updated_at = NOW()
                        WHERE request_code = %s
                          AND task = ANY(%s::text[])
                          AND status = 'error'
                        """,
                        (code, task_list),
                    )
                else:
                    conn.execute(
                        """
                        UPDATE access_request_tasks
                        SET status = 'pending',
                            detail = 'retry requested',
                            updated_at = NOW()
                        WHERE request_code = %s AND status = 'error'
                        """,
                        (code,),
                    )
        except Exception:
            return jsonify({"error": "failed to retry request"}), 502

        try:
            deps.provision_access_request(code)
        except Exception:
            pass
        return jsonify({"ok": True, "status": "accounts_building"})
