use crate::services::item_service::ItemService;
/// Common utilities and types for the server module.
///
/// This module provides shared structures, functions, and middleware used across
/// different parts of the server implementation, including configuration, state
/// management, API responses, authentication, and logging.
///
/// # Usage
///
/// ```rust,ignore
/// // Illustrative — requires runtime values (db connection, settings).
/// use keep::modes::server::common::{ServerConfig, AppState};
/// let config = ServerConfig { address: "127.0.0.1".to_string(), port: Some(8080), /* ... */ };
/// ```
use anyhow::Result;
use axum::{
    extract::{ConnectInfo, Request},
    http::{HeaderMap, Method, StatusCode},
    middleware::Next,
    response::Response,
};
use base64::Engine;
use log::{info, warn};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Instant;
use subtle::ConstantTimeEq;
use tokio::sync::Mutex;
use utoipa::ToSchema;

/// Server configuration structure.
///
/// This struct holds the configuration parameters for the HTTP server, including
/// binding address, port, and authentication settings.
///
/// # Examples
///
/// ```rust
/// use keep::modes::server::common::ServerConfig;
/// let config = ServerConfig {
///     address: "127.0.0.1".to_string(),
///     port: Some(8080),
///     username: None,
///     password: None,
///     password_hash: None,
///     jwt_secret: None,
///     cert_file: None,
///     key_file: None,
///     cors_origin: None,
/// };
/// ```
#[derive(Debug, Clone)]
pub struct ServerConfig {
    /// Server bind address.
    ///
    /// The IP address or hostname the server should bind to. Defaults to "127.0.0.1"
    /// for local-only access.
    pub address: String,
    /// Optional server port.
    ///
    /// The TCP port number to listen on. If not specified, a default port (typically
    /// 8080 or 21080) will be used.
    pub port: Option<u16>,
    /// Optional authentication username.
    ///
    /// Username for Basic authentication. Defaults to "keep" when not specified.
    pub username: Option<String>,
    /// Optional authentication password.
    ///
    /// Plain text password for Basic authentication. This should be
    /// used only for testing or low-security environments.
    pub password: Option<String>,
    /// Optional hashed authentication password.
    ///
    /// Pre-hashed password (Unix crypt format) for secure authentication. Preferred
    /// over plain text password for production use.
    pub password_hash: Option<String>,
    /// Optional JWT secret for token-based authentication.
    ///
    /// When set, the server validates JWT tokens (HS256) and checks permission claims
    /// (read, write, delete) for each request. Takes priority over password auth.
    pub jwt_secret: Option<String>,
    /// Optional path to TLS certificate file (PEM).
    ///
    /// When both cert_file and key_file are set, the server uses HTTPS.
    pub cert_file: Option<PathBuf>,
    /// Optional path to TLS private key file (PEM).
    ///
    /// When both cert_file and key_file are set, the server uses HTTPS.
    pub key_file: Option<PathBuf>,
    /// Optional CORS allowed origin.
    ///
    /// When set, cross-origin requests are restricted to this origin.
    /// Defaults to "http://localhost" if not specified. Use "*" to allow
    /// all origins (not recommended for production).
    pub cors_origin: Option<String>,
}

/// Application state shared across all routes.
///
/// This struct encapsulates the shared state that is accessible to all request handlers,
/// including database connections, file paths, services, and configuration.
///
/// # Examples
///
/// ```rust,ignore
/// // AppState requires runtime values (db connection, settings) not available in doctests.
/// use keep::modes::server::common::AppState;
/// use std::sync::Arc;
/// use tokio::sync::Mutex;
/// let state = AppState {
///     db: Arc::new(Mutex::new(conn)),
///     data_dir: PathBuf::from("/data"),
///     item_service: Arc::new(ItemService::new(data_dir.clone())),
///     cmd: Arc::new(Mutex::new(Command::new("keep"))),
///     settings: Arc::new(settings),
/// };
/// ```
#[derive(Clone)]
pub struct AppState {
    /// Database connection wrapped in Arc<Mutex>.
    ///
    /// A thread-safe reference to the SQLite database connection, protected by a mutex
    /// for concurrent access safety.
    pub db: Arc<Mutex<rusqlite::Connection>>,
    /// Data directory path.
    ///
    /// The root directory where item files are stored.
    pub data_dir: PathBuf,
    /// Item service instance.
    ///
    /// Shared reference to the service handling item storage and retrieval operations.
    pub item_service: Arc<ItemService>,
    /// Command line argument parser.
    ///
    /// Thread-safe reference to the Clap command builder for configuration access.
    pub cmd: Arc<Mutex<clap::Command>>,
    /// Application settings.
    ///
    /// Shared reference to the application's configuration settings.
    pub settings: Arc<crate::config::Settings>,
}

/// Standard API response wrapper containing success status, data payload, and error information.
///
/// This generic type is used for all API responses to provide a consistent structure across
/// different endpoints.
///
/// # Type Parameters
///
/// * `T` - The type of the data payload.
///
/// # Examples
///
/// ```rust
/// use keep::modes::server::common::ApiResponse;
/// let response: ApiResponse<String> = ApiResponse {
///     success: true,
///     data: Some("items".to_string()),
///     error: None,
/// };
/// ```
#[derive(Debug, Serialize, Deserialize, ToSchema)]
#[schema(
    description = "Standard API response wrapper containing success status, data payload, and error information"
)]
pub struct ApiResponse<T> {
    /// Success indicator.
    ///
    /// Boolean flag indicating whether the request was successful.
    pub success: bool,
    /// Optional data payload.
    ///
    /// The actual response data, present only if the request was successful.
    pub data: Option<T>,
    /// Optional error message.
    ///
    /// Error description, present only if the request failed.
    pub error: Option<String>,
}

/// Response type for list of item information.
///
/// Specialized response for endpoints that return multiple items.
///
/// # Examples
///
/// ```rust
/// use keep::modes::server::common::ItemInfoListResponse;
/// let response = ItemInfoListResponse {
///     success: true,
///     data: Some(vec![]),
///     error: None,
/// };
/// ```
#[derive(Serialize, Deserialize, ToSchema)]
pub struct ItemInfoListResponse {
    /// Success indicator.
    ///
    /// Boolean flag indicating whether the request was successful.
    pub success: bool,
    /// Optional list of item information.
    ///
    /// Vector of `ItemInfo` structures containing details about each item.
    pub data: Option<Vec<ItemInfo>>,
    /// Optional error message.
    ///
    /// Error description if the request failed.
    pub error: Option<String>,
}

/// Response type for single item information.
///
/// Specialized response for endpoints that return a single item's details.
///
/// # Examples
///
/// ```rust
/// use keep::modes::server::common::ItemInfoResponse;
/// let response = ItemInfoResponse {
///     success: true,
///     data: None,
///     error: None,
/// };
/// ```
#[derive(Serialize, Deserialize, ToSchema)]
pub struct ItemInfoResponse {
    /// Success indicator.
    ///
    /// Boolean flag indicating whether the request was successful.
    pub success: bool,
    /// Optional item information.
    ///
    /// The `ItemInfo` structure containing details about the item.
    pub data: Option<ItemInfo>,
    /// Optional error message.
    ///
    /// Error description if the request failed.
    pub error: Option<String>,
}

/// Response type for item content information.
///
/// Specialized response for endpoints that return item content and related metadata.
///
/// # Examples
///
/// ```rust
/// use keep::modes::server::common::ItemContentInfoResponse;
/// let response = ItemContentInfoResponse {
///     success: true,
///     data: None,
///     error: None,
/// };
/// ```
#[derive(Serialize, Deserialize, ToSchema)]
pub struct ItemContentInfoResponse {
    /// Success indicator.
    ///
    /// Boolean flag indicating whether the request was successful.
    pub success: bool,
    /// Optional item content information.
    ///
    /// The `ItemContentInfo` structure containing content and metadata.
    pub data: Option<ItemContentInfo>,
    /// Optional error message.
    ///
    /// Error description if the request failed.
    pub error: Option<String>,
}

/// Response type for metadata.
///
/// Specialized response for metadata-only endpoints.
///
/// # Examples
///
/// ```rust
/// use keep::modes::server::common::MetadataResponse;
/// let response = MetadataResponse {
///     success: true,
///     data: None,
///     error: None,
/// };
/// ```
#[derive(Serialize, Deserialize, ToSchema)]
pub struct MetadataResponse {
    /// Success indicator.
    ///
    /// Boolean flag indicating whether the request was successful.
    pub success: bool,
    /// Optional metadata hashmap.
    ///
    /// HashMap containing key-value pairs of metadata.
    pub data: Option<HashMap<String, String>>,
    /// Optional error message.
    ///
    /// Error description if the request failed.
    pub error: Option<String>,
}

/// Response type for status information.
///
/// Specialized response for system status endpoints.
///
/// # Examples
///
/// ```rust
/// use keep::modes::server::common::StatusInfoResponse;
/// let response = StatusInfoResponse {
///     success: true,
///     data: None,
///     error: None,
/// };
/// ```
#[derive(Serialize, Deserialize, ToSchema)]
pub struct StatusInfoResponse {
    /// Success indicator.
    ///
    /// Boolean flag indicating whether the request was successful.
    pub success: bool,
    /// Optional status information.
    ///
    /// The `StatusInfo` structure containing system status details.
    pub data: Option<crate::common::status::StatusInfo>,
    /// Optional error message.
    ///
    /// Error description if the request failed.
    pub error: Option<String>,
}

/// Complete information about a stored item including metadata and tags.
///
/// This structure represents the full details of an item, combining basic item
/// properties with associated tags and metadata.
///
/// # Examples
///
/// ```rust
/// use keep::modes::server::common::ItemInfo;
/// use std::collections::HashMap;
/// let item_info = ItemInfo {
///     id: 42,
///     ts: "2023-12-01T15:30:45Z".to_string(),
///     size: Some(1024),
///     compression: "gzip".to_string(),
///     tags: vec!["important".to_string()],
///     metadata: HashMap::from([("mime_type".to_string(), "text/plain".to_string())]),
/// };
/// ```
#[derive(Serialize, Deserialize, ToSchema)]
#[schema(description = "Complete information about a stored item including metadata and tags")]
pub struct ItemInfo {
    /// Item ID.
    ///
    /// The unique identifier for the item in the database.
    #[schema(example = 42)]
    pub id: i64,
    /// Timestamp.
    ///
    /// The creation timestamp of the item in ISO 8601 format.
    #[schema(example = "2023-12-01T15:30:45Z")]
    pub ts: String,
    /// Size in bytes.
    ///
    /// The size of the item's content in bytes, may be None if not set.
    #[schema(example = 1024)]
    pub size: Option<i64>,
    /// Compression type.
    ///
    /// The compression algorithm used for the item's content.
    #[schema(example = "gzip")]
    pub compression: String,
    /// List of tags.
    ///
    /// Vector of strings representing the tags associated with the item.
    #[schema(example = json!(["important", "work", "document"]))]
    pub tags: Vec<String>,
    /// Metadata hashmap.
    ///
    /// Key-value pairs containing additional metadata about the item.
    #[schema(example = json!({"mime_type": "text/plain", "mime_encoding": "utf-8", "line_count": "42"}))]
    pub metadata: HashMap<String, String>,
}

/// Item information including content and metadata, with binary detection.
///
/// This structure provides item details along with its content, handling binary
/// content detection and safe string representation.
///
/// # Examples
///
/// ```rust
/// use keep::modes::server::common::ItemContentInfo;
/// use std::collections::HashMap;
/// let content_info = ItemContentInfo {
///     metadata: HashMap::from([("mime_type".to_string(), "text/plain".to_string())]),
///     content: Some("Hello, world!".to_string()),
///     binary: false,
/// };
/// ```
#[derive(Serialize, Deserialize, ToSchema)]
#[schema(description = "Item information including content and metadata, with binary detection")]
pub struct ItemContentInfo {
    /// Metadata hashmap.
    ///
    /// Key-value pairs of metadata, flattened into the structure.
    #[serde(flatten)]
    #[schema(example = json!({"mime_type": "text/plain", "mime_encoding": "utf-8", "line_count": "42"}))]
    pub metadata: HashMap<String, String>,
    /// Optional content as string.
    ///
    /// The item's content as a string, only present if the content is text.
    #[schema(example = "Hello, world!\nThis is the content of the file.")]
    pub content: Option<String>,
    /// Binary content indicator.
    ///
    /// Boolean flag indicating whether the content is binary (true) or text (false).
    #[schema(example = false)]
    pub binary: bool,
}

/// Query parameters for tags.
///
/// Structure for handling tag-based query parameters in API requests.
///
/// # Examples
///
/// ```rust
/// use keep::modes::server::common::TagsQuery;
/// let query = TagsQuery { tags: Some("tag1,tag2".to_string()) };
/// ```
#[derive(Debug, Deserialize)]
pub struct TagsQuery {
    /// Optional comma-separated tags.
    ///
    /// String containing comma-separated tag names for filtering.
    pub tags: Option<String>,
}

/// Query parameters for listing items.
///
/// Structure for pagination and sorting parameters in item listing endpoints.
///
/// # Examples
///
/// ```rust
/// use keep::modes::server::common::ListItemsQuery;
/// let query = ListItemsQuery {
///     tags: Some("important".to_string()),
///     order: Some("newest".to_string()),
///     start: Some(0),
///     count: Some(10),
///     meta: None,
/// };
/// ```
#[derive(Debug, Deserialize)]
pub struct ListItemsQuery {
    /// Optional comma-separated tags for filtering.
    ///
    /// String containing tags to filter the item list.
    pub tags: Option<String>,
    /// Optional sort order.
    ///
    /// String specifying sort direction: "newest" or "oldest".
    pub order: Option<String>,
    /// Optional pagination start index.
    ///
    /// Unsigned integer indicating the starting index for pagination.
    pub start: Option<u32>,
    /// Optional number of items to return.
    ///
    /// Unsigned integer limiting the number of items returned.
    pub count: Option<u32>,
    /// Optional metadata filter as JSON string.
    ///
    /// JSON object where keys are metadata keys and values are either
    /// `null` (filter by key existence) or a string (filter by exact value match).
    pub meta: Option<String>,
}

/// Query parameters for item retrieval.
///
/// Structure for content retrieval parameters, including binary handling and streaming options.
///
/// # Examples
///
/// ```rust
/// use keep::modes::server::common::ItemQuery;
/// let query = ItemQuery {
///     allow_binary: true,
///     offset: 0,
///     length: 1024,
///     stream: false,
///     as_meta: false,
///     decompress: true,
/// };
/// ```
#[derive(Debug, Deserialize, utoipa::ToSchema)]
pub struct ItemQuery {
    /// Allow binary content (default: true).
    ///
    /// Boolean flag to allow or deny binary content in responses.
    #[serde(default = "default_allow_binary")]
    pub allow_binary: bool,
    /// Byte offset (default: 0).
    ///
    /// Unsigned integer specifying the starting byte position for content retrieval.
    #[serde(default)]
    pub offset: u64,
    /// Byte length (default: 0, meaning all).
    ///
    /// Unsigned integer specifying the maximum number of bytes to retrieve (0 = all remaining).
    #[serde(default)]
    pub length: u64,
    /// Stream response (default: false).
    ///
    /// Boolean flag to enable streaming responses for large content.
    #[serde(default = "default_stream")]
    pub stream: bool,
    /// Return as metadata JSON (default: false).
    ///
    /// Boolean flag to return content and metadata in a structured JSON format.
    #[serde(default = "default_as_meta")]
    pub as_meta: bool,
    /// Whether the server should decompress the content (default: true).
    /// Set to false when the client wants raw stored bytes for local decompression.
    #[serde(default = "default_true")]
    pub decompress: bool,
}

/// Query parameters for item content retrieval.
///
/// Extended query parameters for content-specific operations, including tag filtering.
///
/// # Examples
///
/// ```rust
/// use keep::modes::server::common::ItemContentQuery;
/// let query = ItemContentQuery {
///     tags: Some("important".to_string()),
///     allow_binary: true,
///     offset: 0,
///     length: 1024,
///     stream: false,
///     as_meta: false,
///     decompress: true,
/// };
/// ```
#[derive(Debug, Deserialize, utoipa::ToSchema)]
pub struct ItemContentQuery {
    /// Optional comma-separated tags for filtering.
    ///
    /// String containing tags to filter the item selection.
    pub tags: Option<String>,
    /// Allow binary content (default: true).
    ///
    /// Boolean flag to allow or deny binary content in responses.
    #[serde(default = "default_allow_binary")]
    pub allow_binary: bool,
    /// Byte offset (default: 0).
    ///
    /// Unsigned integer specifying the starting byte position for content retrieval.
    #[serde(default)]
    pub offset: u64,
    /// Byte length (default: 0, meaning all).
    ///
    /// Unsigned integer specifying the maximum number of bytes to retrieve (0 = all remaining).
    #[serde(default)]
    pub length: u64,
    /// Stream response (default: false).
    ///
    /// Boolean flag to enable streaming responses for large content.
    #[serde(default = "default_stream")]
    pub stream: bool,
    /// Return as metadata JSON (default: false).
    ///
    /// Boolean flag to return content and metadata in a structured JSON format.
    #[serde(default = "default_as_meta")]
    pub as_meta: bool,
    /// Whether the server should decompress the content (default: true).
    /// Set to false when the client wants raw stored bytes for local decompression.
    #[serde(default = "default_true")]
    pub decompress: bool,
}

/// Default function for allow_binary parameter.
///
/// # Returns
///
/// `true` as the default value for allowing binary content.
fn default_allow_binary() -> bool {
    true
}

/// Default function for stream parameter.
///
/// # Returns
///
/// `false` as the default value for streaming responses.
fn default_stream() -> bool {
    false
}

/// Default function for as_meta parameter.
///
/// # Returns
///
/// `false` as the default value for metadata JSON responses.
fn default_as_meta() -> bool {
    false
}

/// Default function for true boolean parameters.
///
/// # Returns
///
/// `true` as the default value.
fn default_true() -> bool {
    true
}

/// Query parameters for creating an item via POST.
///
/// Query parameters for POST /api/item/ with streaming binary body.
#[derive(Debug, Deserialize)]
pub struct CreateItemQuery {
    /// Optional comma-separated tags to associate with the item.
    pub tags: Option<String>,
    /// Optional metadata as JSON string.
    pub metadata: Option<String>,
    /// Whether the server should compress the content (default: true).
    /// Set to false when the client has already compressed the content.
    #[serde(default = "default_true")]
    pub compress: bool,
    /// Whether the server should run meta plugins (default: true).
    /// Set to false when the client has already collected metadata.
    #[serde(default = "default_true")]
    pub meta: bool,
    /// Compression type used by the client (e.g. "lz4", "gzip").
    /// Only used when compress=false — tells the server what compression
    /// the client applied so the correct type is recorded in the database.
    pub compression_type: Option<String>,
    /// Optional timestamp for the item (RFC 3339 format).
    /// Used during import to preserve the original item's timestamp.
    /// If not provided, the server uses the current time.
    pub ts: Option<String>,
}

/// Query parameters for updating item metadata via POST.
///
/// Query parameters for POST /api/item/{item_id}/update.
/// Re-runs specified meta plugins on the stored content and/or
/// applies direct metadata key-value overrides.
#[derive(Debug, Deserialize)]
pub struct UpdateItemQuery {
    /// Optional comma-separated list of plugin names to re-run.
    pub plugins: Option<String>,
    /// Optional metadata overrides as JSON string.
    pub metadata: Option<String>,
    /// Optional comma-separated tags to add.
    pub tags: Option<String>,
    /// Optional uncompressed size to set on the item.
    pub size: Option<i64>,
}

/// Request body for creating a new item.
///
/// Contains the content to store and optional tags.
#[derive(Debug, Deserialize, Serialize, ToSchema)]
pub struct CreateItemRequest {
    /// The content to store.
    #[schema(example = "Hello, world!")]
    pub content: String,
    /// Optional tags to associate with the item.
    #[schema(example = json!(["important", "work"]))]
    pub tags: Option<Vec<String>>,
    /// Optional metadata key-value pairs.
    pub metadata: Option<std::collections::HashMap<String, String>>,
}

/// Checks authorization header for valid credentials.
///
/// This function inspects the HTTP Authorization header for valid Basic
/// authentication credentials against the provided username and password or hash.
/// Bearer tokens are not checked here — JWT validation is handled separately
/// in the middleware.
///
/// # Arguments
///
/// * `headers` - HTTP headers from the request.
/// * `username` - Optional expected username (defaults to "keep").
/// * `password` - Optional expected password.
/// * `password_hash` - Optional expected password hash.
///
/// # Returns
///
/// * `true` - If authorized (or no auth required).
/// * `false` - If unauthorized.
pub fn check_auth(
    headers: &HeaderMap,
    username: &Option<String>,
    password: &Option<String>,
    password_hash: &Option<String>,
) -> bool {
    // If neither password nor hash is set, no authentication required
    if password.is_none() && password_hash.is_none() {
        return true;
    }

    let effective_username = username.as_deref().unwrap_or("keep");

    if let Some(auth_header) = headers.get("authorization")
        && let Ok(auth_str) = auth_header.to_str()
    {
        return check_basic_auth(
            auth_str,
            effective_username,
            password.as_deref().unwrap_or(""),
            password_hash,
        );
    }
    false
}

/// Validates basic authentication credentials.
///
/// This function decodes and validates Basic Auth credentials from the authorization
/// header against the expected username and password or hash.
///
/// # Arguments
///
/// * `auth_str` - The authorization string from the header.
/// * `expected_username` - The expected username.
/// * `expected_password` - The expected plain text password.
/// * `expected_hash` - Optional expected password hash.
///
/// # Returns
///
/// * `true` - If authentication succeeds.
/// * `false` - Otherwise.
///
/// # Errors
///
/// Returns false on decode or validation failure.
fn check_basic_auth(
    auth_str: &str,
    expected_username: &str,
    expected_password: &str,
    expected_hash: &Option<String>,
) -> bool {
    if !auth_str.starts_with("Basic ") {
        return false;
    }

    let encoded = &auth_str[6..];
    if let Ok(decoded_bytes) = base64::engine::general_purpose::STANDARD.decode(encoded)
        && let Ok(decoded_str) = String::from_utf8(decoded_bytes)
        && let Some(colon_pos) = decoded_str.find(':')
    {
        let provided_username = &decoded_str[..colon_pos];
        let provided_password = &decoded_str[colon_pos + 1..];

        // Check username with constant-time comparison
        if !bool::from(
            provided_username
                .as_bytes()
                .ct_eq(expected_username.as_bytes()),
        ) {
            return false;
        }

        // If we have a password hash, verify against it
        if let Some(hash) = expected_hash {
            return pwhash::unix::verify(provided_password, hash);
        }

        // Otherwise, do constant-time comparison to prevent timing attacks
        return bool::from(
            provided_password
                .as_bytes()
                .ct_eq(expected_password.as_bytes()),
        );
    }
    false
}

/// Middleware for logging requests and responses.
///
/// This middleware logs incoming requests and outgoing responses, including method,
/// URI, status code, response size, duration, and Accept header.
///
/// # Arguments
///
/// * `ConnectInfo(addr)` - Connection info with client address.
/// * `request` - Incoming request.
/// * `next` - Next middleware.
///
/// # Returns
///
/// The processed response with logging.
///
/// # Examples
///
/// Used in Axum router: `.layer_handler(logging_middleware)`.
pub async fn logging_middleware(
    ConnectInfo(addr): ConnectInfo<SocketAddr>,
    request: Request,
    next: Next,
) -> Response {
    let method = request.method().clone();
    let uri = request.uri().clone();

    // Log the Accept header - extract before moving the request
    let accept_header = request
        .headers()
        .get("accept")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("-")
        .to_string();

    let start = Instant::now();
    let response = next.run(request).await;
    let duration = start.elapsed();

    // Try to get response body size from content-length header, or default to 0
    let response_content_length = response
        .headers()
        .get("content-length")
        .and_then(|v| v.to_str().ok())
        .and_then(|s| s.parse::<u64>().ok())
        .unwrap_or(0);

    info!(
        "{} {} {} {} {} bytes - {:?} - Accept: {}",
        addr,
        method,
        uri,
        response.status(),
        response_content_length,
        duration,
        accept_header
    );

    response
}

/// Creates authentication middleware for the application.
///
/// This function returns a middleware that enforces authentication on protected routes.
///
/// **JWT and Basic Auth are mutually exclusive.** When `jwt_secret` is set, the
/// middleware validates JWT (HS256) tokens and checks permission claims (read, write,
/// delete) based on the HTTP method. Requests without a valid Bearer token are
/// rejected with 401 — Basic Auth is **not** consulted as a fallback.
///
/// When `jwt_secret` is not set, Basic Auth password authentication is used instead.
///
/// # Arguments
///
/// * `username` - Optional username (defaults to "keep").
/// * `password` - Optional plain text password.
/// * `password_hash` - Optional hashed password.
/// * `jwt_secret` - Optional JWT secret for token-based authentication.
///
/// # Returns
///
/// A clonable async middleware function for Axum.
#[allow(clippy::type_complexity)]
pub fn create_auth_middleware(
    username: Option<String>,
    password: Option<String>,
    password_hash: Option<String>,
    jwt_secret: Option<String>,
) -> impl Fn(
    ConnectInfo<SocketAddr>,
    Request,
    Next,
)
    -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Response, StatusCode>> + Send>>
+ Clone
+ Send {
    move |ConnectInfo(addr): ConnectInfo<SocketAddr>, request: Request, next: Next| {
        let username = username.clone();
        let password = password.clone();
        let password_hash = password_hash.clone();
        let jwt_secret = jwt_secret.clone();
        Box::pin(async move {
            let headers = request.headers().clone();
            let uri = request.uri().clone();
            let method = request.method().clone();

            // CORS preflight requests pass through without authentication
            if method == Method::OPTIONS {
                return Ok(next.run(request).await);
            }

            // JWT authentication takes priority when secret is configured
            if let Some(ref secret) = jwt_secret
                && let Some(auth_header) = headers.get("authorization")
                && let Ok(auth_str) = auth_header.to_str()
                && let Some(token) = auth_str.strip_prefix("Bearer ")
            {
                match super::auth::validate_jwt(token, secret) {
                    Ok(claims) => {
                        let required = super::auth::required_permission(&method);
                        if !super::auth::check_permission(&claims, required) {
                            warn!(
                                "Forbidden: {method} {uri} from {addr} \
                                 (sub={}, missing permission: {required})",
                                claims.sub
                            );
                            let mut response = Response::new(axum::body::Body::from("Forbidden"));
                            *response.status_mut() = StatusCode::FORBIDDEN;
                            return Ok(response);
                        }
                        // JWT valid and authorized, proceed
                        let response = next.run(request).await;
                        return Ok(response);
                    }
                    Err(e) => {
                        warn!("JWT validation failed for {uri} from {addr}: {e}");
                        let mut response = Response::new(axum::body::Body::from("Unauthorized"));
                        *response.status_mut() = StatusCode::UNAUTHORIZED;
                        return Ok(response);
                    }
                }
            }

            // JWT secret configured but no valid Bearer token provided
            if jwt_secret.is_some() {
                warn!("Missing JWT token for {uri} from {addr}");
                let mut response = Response::new(axum::body::Body::from("Unauthorized"));
                *response.status_mut() = StatusCode::UNAUTHORIZED;
                return Ok(response);
            }

            // Fall back to Basic Auth password authentication
            if !check_auth(&headers, &username, &password, &password_hash) {
                warn!("Unauthorized request to {uri} from {addr}");
                // Add WWW-Authenticate header to trigger basic auth in browsers
                let mut response = Response::new(axum::body::Body::from("Unauthorized"));
                *response.status_mut() = StatusCode::UNAUTHORIZED;
                response.headers_mut().insert(
                    "www-authenticate",
                    "Basic realm=\"Keep Server\", charset=\"UTF-8\""
                        .parse()
                        .unwrap(),
                );
                return Ok(response);
            }

            let response = next.run(request).await;
            Ok(response)
        })
    }
}
