use leptos::prelude::*;

use super::otp_service::OtpVerifyResult;

/// Request an OTP code to be sent to the given email address.
///
/// `lang` is the UI language code (`"en"`, `"es"`, …) used to select the
/// email template language.  Defaults to `"en"` on the server side if empty.
#[server(RequestOtp, "/api")]
pub async fn server_request_otp(email: String, lang: String) -> Result<(), ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| {
                ServerFnError::new(
                    "OTP service not configured — register OtpServiceExtension at startup",
                )
            })?;

        let lang_str = if lang.is_empty() { "en" } else { &lang };
        svc.request_otp(&email, lang_str)
            .await
            .map_err(ServerFnError::new)?;
    }

    Ok(())
}

/// Verify an OTP code.
///
/// Returns `OtpVerifyResult::Authenticated` (sets session cookie) for existing
/// accounts, or `OtpVerifyResult::NeedsConsent` for new emails.
#[server(VerifyOtp, "/api")]
pub async fn server_verify_otp(
    email: String,
    code: String,
) -> Result<OtpVerifyResult, ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::{http::header, Extension};
        use leptos_axum::{extract, ResponseOptions};

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| {
                ServerFnError::new(
                    "OTP service not configured — register OtpServiceExtension at startup",
                )
            })?;

        let result = svc
            .verify_otp(&email, &code)
            .await
            .map_err(ServerFnError::new)?;

        // Only set the session cookie for authenticated users.
        if let OtpVerifyResult::Authenticated {
            ref session_token, ..
        } = result
        {
            let response = use_context::<ResponseOptions>()
                .ok_or_else(|| ServerFnError::new("No response options in context"))?;

            let cookie_value = format!(
                "session_token={}; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400",
                session_token
            );
            if let Ok(hv) = header::HeaderValue::from_str(&cookie_value) {
                response.insert_header(header::SET_COOKIE, hv);
            }
        }

        return Ok(result);
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Accept GDPR consent for a new account.
///
/// Validates the `pending_token`, creates the user, sets session cookie.
#[server(AcceptGdpr, "/api")]
pub async fn server_accept_gdpr(
    pending_token: String,
) -> Result<rustelo_core_lib::state::UserInfo, ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::{http::header, Extension};
        use leptos_axum::{extract, ResponseOptions};

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let result = svc
            .accept_gdpr(&pending_token)
            .await
            .map_err(ServerFnError::new)?;

        match result {
            OtpVerifyResult::Authenticated {
                ref user,
                ref session_token,
            } => {
                let response = use_context::<ResponseOptions>()
                    .ok_or_else(|| ServerFnError::new("No response options in context"))?;

                let cookie_value = format!(
                    "session_token={}; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400",
                    session_token
                );
                if let Ok(hv) = header::HeaderValue::from_str(&cookie_value) {
                    response.insert_header(header::SET_COOKIE, hv);
                }

                return Ok(user.clone());
            }
            OtpVerifyResult::NeedsConsent { .. } => {
                return Err(ServerFnError::new(
                    "Unexpected NeedsConsent after GDPR accept",
                ));
            }
        }
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Server-side logout: invalidate session cookie and remove from store.
#[server(Logout, "/api")]
pub async fn server_logout() -> Result<(), ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::{http::header, Extension};
        use leptos_axum::{extract, ResponseOptions};

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        // Extract the session_token cookie from the incoming request.
        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();

        let session_token = headers
            .get(header::COOKIE)
            .and_then(|v| v.to_str().ok())
            .and_then(|cookies| {
                cookies.split(';').find_map(|part| {
                    let part = part.trim();
                    part.strip_prefix("session_token=").map(str::to_string)
                })
            });

        if let Some(token) = session_token {
            // Best-effort: do not abort if the session is already gone.
            let _ = svc.logout_session(&token).await;
        }

        // Expire the cookie on the client.
        let response = use_context::<ResponseOptions>()
            .ok_or_else(|| ServerFnError::new("No response options in context"))?;

        if let Ok(hv) = header::HeaderValue::from_str(
            "session_token=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0",
        ) {
            response.insert_header(header::SET_COOKIE, hv);
        }

        return Ok(());
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Retrieve the current user's bookmarks.
#[server(GetBookmarks, "/api")]
pub async fn server_get_bookmarks() -> Result<Vec<BookmarkInfo>, ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        let bookmarks = svc
            .get_bookmarks(&session_token)
            .await
            .map_err(ServerFnError::new)?;

        return Ok(bookmarks);
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Add a new bookmark for the current user.
#[server(AddBookmark, "/api")]
pub async fn server_add_bookmark(
    content_type: String,
    content_id: String,
    content_title: Option<String>,
    content_url: Option<String>,
) -> Result<String, ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        let id = svc
            .add_bookmark(
                &session_token,
                content_type,
                content_id,
                content_title,
                content_url,
            )
            .await
            .map_err(ServerFnError::new)?;

        return Ok(id);
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Remove a bookmark by ID.
#[server(RemoveBookmark, "/api")]
pub async fn server_remove_bookmark(bookmark_id: String) -> Result<(), ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        svc.remove_bookmark(&session_token, bookmark_id)
            .await
            .map_err(ServerFnError::new)?;

        return Ok(());
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Check whether the current user has bookmarked a specific content item.
#[server(IsBookmarked, "/api")]
pub async fn server_is_bookmarked(
    content_id: String,
    content_type: String,
) -> Result<bool, ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        let bookmarked = svc
            .is_bookmarked(&session_token, content_id, content_type)
            .await
            .map_err(ServerFnError::new)?;

        return Ok(bookmarked);
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Delete the current user's account.
#[server(DeleteAccount, "/api")]
pub async fn server_delete_account() -> Result<(), ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::{extract, ResponseOptions};

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        svc.delete_account(&session_token)
            .await
            .map_err(ServerFnError::new)?;

        // Expire the session cookie.
        let response = use_context::<ResponseOptions>()
            .ok_or_else(|| ServerFnError::new("No response options in context"))?;

        if let Ok(hv) = axum::http::HeaderValue::from_str(
            "session_token=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0",
        ) {
            response.insert_header(axum::http::header::SET_COOKIE, hv);
        }

        return Ok(());
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Update the display name for the current authenticated user.
#[server(UpdateDisplayName, "/api")]
pub async fn server_update_display_name(name: String) -> Result<(), ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        svc.update_display_name(&session_token, name)
            .await
            .map_err(ServerFnError::new)?;

        return Ok(());
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Rotate the current session: issues a fresh JWT and sets it as a new cookie.
///
/// Called by WASM when the WS channel sends `TokenRefreshRequested` (~120 s
/// before the current token expires).  Returns `Ok(())` — the new token is
/// delivered via the `Set-Cookie` response header, not the JSON body.
#[server(RefreshToken, "/api")]
pub async fn server_refresh_token() -> Result<(), ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::{http::header, Extension};
        use leptos_axum::{extract, ResponseOptions};

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        let new_token = svc
            .refresh_session(&session_token)
            .await
            .map_err(ServerFnError::new)?;

        let response = use_context::<ResponseOptions>()
            .ok_or_else(|| ServerFnError::new("No response options in context"))?;

        let cookie_value = format!(
            "session_token={}; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400",
            new_token
        );
        if let Ok(hv) = header::HeaderValue::from_str(&cookie_value) {
            response.insert_header(header::SET_COOKIE, hv);
        }

        return Ok(());
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Invalidate all active sessions for the current user and expire this
/// session's cookie.
///
/// The adapter emits a `SessionRevoked` WS notification to all other active
/// connections of the same user so they redirect to the login page immediately.
#[server(LogoutAll, "/api")]
pub async fn server_logout_all() -> Result<(), ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::{http::header, Extension};
        use leptos_axum::{extract, ResponseOptions};

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        // Invalidates all DB sessions and emits SessionRevoked WS notification.
        svc.logout_all_sessions(&session_token)
            .await
            .map_err(ServerFnError::new)?;

        let response = use_context::<ResponseOptions>()
            .ok_or_else(|| ServerFnError::new("No response options in context"))?;

        if let Ok(hv) = header::HeaderValue::from_str(
            "session_token=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0",
        ) {
            response.insert_header(header::SET_COOKIE, hv);
        }

        return Ok(());
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Validate the current session cookie and return the authenticated user.
///
/// Returns `Ok(None)` when no cookie is present or the JWT is invalid/expired.
/// Returns `Ok(Some(user))` for a valid session.
///
/// Called by the WASM client on every new tab open to restore `AuthState`
/// from an existing `session_token` cookie without requiring re-login.
/// Accessible to anonymous requests so the RBAC middleware does not block the
/// initial hydration call made before auth state is known.
#[server(GetSessionUser, "/api")]
pub async fn server_get_session_user(
) -> Result<Option<rustelo_core_lib::state::UserInfo>, ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();

        let Some(token) = extract_session_token(&headers) else {
            return Ok(None);
        };

        match svc.get_session_user(&token).await {
            Ok(user) => return Ok(Some(user)),
            // Invalid / expired token — not an error from the client's perspective.
            Err(_) => return Ok(None),
        }
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Bookmark data returned to clients.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct BookmarkInfo {
    pub id: String,
    pub content_type: String,
    pub content_id: String,
    pub content_title: Option<String>,
    pub content_url: Option<String>,
}

/// Message data returned to clients.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct UserMessage {
    pub id: String,
    pub sender_email: String,
    pub subject: String,
    pub body: String,
    pub is_read: bool,
    /// One of "sent" | "read" | "deleted".
    pub status: String,
    pub created_at: String,
    pub received_at: Option<String>,
}

/// Note data returned to clients.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct UserNote {
    pub id: String,
    pub title: String,
    pub content: String,
    pub created_at: String,
    pub updated_at: String,
}

/// Service token metadata returned to admins (no token hash exposed).
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ServiceTokenInfo {
    pub id: String,
    pub name: String,
    pub scopes: Vec<String>,
    pub is_active: bool,
    pub last_used_at: Option<String>,
    pub expires_at: Option<String>,
    pub created_at: String,
}

/// Returned once on token creation; contains the raw token (shown only once).
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CreatedServiceToken {
    pub id: String,
    pub name: String,
    pub raw_token: String,
}

/// Resource data returned to clients.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct UserResource {
    pub id: String,
    pub name: String,
    pub description: Option<String>,
    pub url: Option<String>,
    pub resource_type: String,
    pub role_name: String,
}

/// Retrieve the current user's messages.
#[server(GetMessages, "/api")]
pub async fn server_get_messages() -> Result<Vec<UserMessage>, ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        let messages = svc
            .get_messages(&session_token)
            .await
            .map_err(ServerFnError::new)?;

        return Ok(messages);
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Mark a message as read for the current user.
#[server(MarkMessageRead, "/api")]
pub async fn server_mark_message_read(message_id: String) -> Result<(), ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        svc.mark_message_read(&session_token, message_id)
            .await
            .map_err(ServerFnError::new)?;

        return Ok(());
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Soft-delete a message for the current user (sets status = 'deleted').
#[server(DeleteMessage, "/api")]
pub async fn server_delete_message(message_id: String) -> Result<(), ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        svc.delete_message(&session_token, message_id)
            .await
            .map_err(ServerFnError::new)?;

        return Ok(());
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Retrieve the current user's notes.
#[server(GetNotes, "/api")]
pub async fn server_get_notes() -> Result<Vec<UserNote>, ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        let notes = svc
            .get_notes(&session_token)
            .await
            .map_err(ServerFnError::new)?;

        return Ok(notes);
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Create a new note for the current user. Returns the new note's UUID.
#[server(CreateNote, "/api")]
pub async fn server_create_note(title: String, content: String) -> Result<String, ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        let id = svc
            .create_note(&session_token, title, content)
            .await
            .map_err(ServerFnError::new)?;

        return Ok(id);
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Update an existing note for the current user.
#[server(UpdateNote, "/api")]
pub async fn server_update_note(
    note_id: String,
    title: String,
    content: String,
) -> Result<(), ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        svc.update_note(&session_token, note_id, title, content)
            .await
            .map_err(ServerFnError::new)?;

        return Ok(());
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Delete a note for the current user.
#[server(DeleteNote, "/api")]
pub async fn server_delete_note(note_id: String) -> Result<(), ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        svc.delete_note(&session_token, note_id)
            .await
            .map_err(ServerFnError::new)?;

        return Ok(());
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Retrieve resources available to the current user based on their roles.
#[server(GetResources, "/api")]
pub async fn server_get_resources() -> Result<Vec<UserResource>, ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        let resources = svc
            .get_resources(&session_token)
            .await
            .map_err(ServerFnError::new)?;

        return Ok(resources);
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// List service tokens created by the current admin user.
#[server(ListServiceTokens, "/api")]
pub async fn server_list_service_tokens() -> Result<Vec<ServiceTokenInfo>, ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        return svc
            .list_service_tokens(&session_token)
            .await
            .map_err(ServerFnError::new);
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Create a new service token (admin only).  Returns the raw token once.
#[server(CreateServiceToken, "/api")]
pub async fn server_create_service_token(
    name: String,
    scopes: Vec<String>,
    expires_days: Option<u32>,
) -> Result<CreatedServiceToken, ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        return svc
            .create_service_token(&session_token, name, scopes, expires_days)
            .await
            .map_err(ServerFnError::new);
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

/// Revoke a service token by id (admin only, scoped to creator).
#[server(RevokeServiceToken, "/api")]
pub async fn server_revoke_service_token(token_id: String) -> Result<(), ServerFnError> {
    #[cfg(feature = "ssr")]
    {
        use axum::Extension;
        use leptos_axum::extract;

        use crate::auth::otp_service::server::OtpServiceExtension;

        let Extension(OtpServiceExtension(svc)) = extract::<Extension<OtpServiceExtension>>()
            .await
            .map_err(|_| ServerFnError::new("OTP service not configured"))?;

        let headers = extract::<axum::http::HeaderMap>().await.unwrap_or_default();
        let session_token = extract_session_token(&headers)
            .ok_or_else(|| ServerFnError::new("Not authenticated"))?;

        return svc
            .revoke_service_token(&session_token, token_id)
            .await
            .map_err(ServerFnError::new);
    }

    #[allow(unreachable_code)]
    Err(ServerFnError::new("SSR not enabled"))
}

#[cfg(feature = "ssr")]
fn extract_session_token(headers: &axum::http::HeaderMap) -> Option<String> {
    headers
        .get(axum::http::header::COOKIE)
        .and_then(|v| v.to_str().ok())
        .and_then(|cookies| {
            cookies.split(';').find_map(|part| {
                let part = part.trim();
                part.strip_prefix("session_token=").map(str::to_string)
            })
        })
}
