//! HTTP handlers for RAGFS API
//!
//! This module implements all HTTP request handlers for the RAGFS REST API.

use axum::{
    extract::{Query, State},
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;

use crate::core::{FileSystem, FilesystemStats, GrepResult, MountableFS, PluginConfig, WriteFlag};

/// Shared application state
#[derive(Clone)]
pub struct AppState {
    /// The mounted filesystem
    pub fs: Arc<MountableFS>,
}

/// Standard API response
#[derive(Debug, Serialize)]
pub struct ApiResponse<T> {
    /// Whether the operation succeeded
    pub success: bool,
    /// Response data (if successful)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<T>,
    /// Error message (if failed)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
}

impl<T> ApiResponse<T> {
    /// Create a successful response
    pub fn success(data: T) -> Self {
        Self {
            success: true,
            data: Some(data),
            error: None,
        }
    }

    /// Create an error response
    pub fn error(message: impl Into<String>) -> ApiResponse<()> {
        ApiResponse {
            success: false,
            data: None,
            error: Some(message.into()),
        }
    }
}

/// Query parameters for file operations
#[derive(Debug, Deserialize)]
pub struct FileQuery {
    /// File path
    pub path: String,
    /// Read offset in bytes
    #[serde(default)]
    pub offset: u64,
    /// Number of bytes to read (0 = all)
    #[serde(default)]
    pub size: u64,
}

/// Query parameters for directory operations
#[derive(Debug, Deserialize)]
pub struct DirQuery {
    /// Directory path
    pub path: String,
    /// Optional permission mode (octal string, e.g. "755")
    pub mode: Option<String>,
}

/// Query parameters for statistics operations
#[derive(Debug, Deserialize)]
pub struct StatsQuery {
    /// Mount path (optional, if not provided returns all stats)
    pub path: Option<String>,
}

/// Statistics response for a single mount
#[derive(Debug, Serialize)]
pub struct MountStats {
    /// Mount path
    pub path: String,
    /// Plugin name
    pub plugin: String,
    /// Filesystem statistics
    pub stats: FilesystemStats,
}

/// Statistics response for all mounts
#[derive(Debug, Serialize)]
pub struct AllStatsResponse {
    /// Statistics for all mounts
    pub mounts: Vec<MountStats>,
}

/// Request body for mount operation
#[derive(Debug, Deserialize)]
pub struct MountRequest {
    /// Plugin name
    pub plugin: String,
    /// Mount path
    pub path: String,
    /// Plugin configuration parameters
    #[serde(default)]
    pub params: std::collections::HashMap<String, serde_json::Value>,
}

/// Request body for unmount operation
#[derive(Debug, Deserialize)]
pub struct UnmountRequest {
    /// Mount path to unmount
    pub path: String,
}

/// Request body for grep operation
#[derive(Debug, Deserialize)]
pub struct GrepRequest {
    /// File or directory path to search
    pub path: String,
    /// Regular expression pattern
    pub pattern: String,
    /// Whether to search recursively
    #[serde(default)]
    pub recursive: bool,
    /// Whether to perform case-insensitive matching
    #[serde(default)]
    pub case_insensitive: bool,
    /// Whether to stream results (currently unsupported by ragfs server)
    #[serde(default)]
    pub stream: bool,
    /// Maximum number of matches to return
    #[serde(default)]
    pub node_limit: Option<usize>,
    /// Optional path prefix to exclude from search
    #[serde(default)]
    pub exclude_path: Option<String>,
    /// Optional maximum depth relative to query root
    #[serde(default)]
    pub level_limit: Option<usize>,
}

/// Health check response
#[derive(Debug, Serialize)]
pub struct HealthResponse {
    /// Health status
    pub status: String,
    /// Server version
    pub version: String,
}

/// Mount info response
#[derive(Debug, Serialize)]
pub struct MountInfo {
    /// Mount path
    pub path: String,
    /// Plugin name
    pub plugin: String,
}

// ============================================================================
// File Operations Handlers
// ============================================================================

/// GET /api/v1/files - Read file
pub async fn read_file(State(state): State<AppState>, Query(query): Query<FileQuery>) -> Response {
    match state.fs.read(&query.path, query.offset, query.size).await {
        Ok(data) => (StatusCode::OK, data).into_response(),
        Err(e) => (
            StatusCode::NOT_FOUND,
            Json(ApiResponse::<()>::error(e.to_string())),
        )
            .into_response(),
    }
}

/// PUT /api/v1/files - Write file
pub async fn write_file(
    State(state): State<AppState>,
    Query(query): Query<FileQuery>,
    body: bytes::Bytes,
) -> Response {
    match state
        .fs
        .write(&query.path, &body, query.offset, WriteFlag::None)
        .await
    {
        Ok(written) => (
            StatusCode::OK,
            Json(ApiResponse::success(serde_json::json!({
                "bytes_written": written
            }))),
        )
            .into_response(),
        Err(e) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ApiResponse::<()>::error(e.to_string())),
        )
            .into_response(),
    }
}

/// POST /api/v1/files - Create file
pub async fn create_file(
    State(state): State<AppState>,
    Query(query): Query<FileQuery>,
) -> Response {
    match state.fs.create(&query.path).await {
        Ok(_) => (
            StatusCode::CREATED,
            Json(ApiResponse::success(serde_json::json!({
                "path": query.path
            }))),
        )
            .into_response(),
        Err(e) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ApiResponse::<()>::error(e.to_string())),
        )
            .into_response(),
    }
}

/// DELETE /api/v1/files - Delete file
pub async fn delete_file(
    State(state): State<AppState>,
    Query(query): Query<FileQuery>,
) -> Response {
    match state.fs.remove(&query.path).await {
        Ok(_) => (
            StatusCode::OK,
            Json(ApiResponse::success(serde_json::json!({
                "path": query.path
            }))),
        )
            .into_response(),
        Err(e) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ApiResponse::<()>::error(e.to_string())),
        )
            .into_response(),
    }
}

/// GET /api/v1/stat - Get file metadata
pub async fn stat_file(State(state): State<AppState>, Query(query): Query<FileQuery>) -> Response {
    match state.fs.stat(&query.path).await {
        Ok(info) => (StatusCode::OK, Json(ApiResponse::success(info))).into_response(),
        Err(e) => (
            StatusCode::NOT_FOUND,
            Json(ApiResponse::<()>::error(e.to_string())),
        )
            .into_response(),
    }
}

// ============================================================================
// Directory Operations Handlers
// ============================================================================

/// GET /api/v1/directories - List directory
pub async fn list_directory(
    State(state): State<AppState>,
    Query(query): Query<DirQuery>,
) -> Response {
    match state.fs.read_dir(&query.path).await {
        Ok(entries) => (StatusCode::OK, Json(ApiResponse::success(entries))).into_response(),
        Err(e) => (
            StatusCode::NOT_FOUND,
            Json(ApiResponse::<()>::error(e.to_string())),
        )
            .into_response(),
    }
}

/// POST /api/v1/directories - Create directory
pub async fn create_directory(
    State(state): State<AppState>,
    Query(query): Query<DirQuery>,
) -> Response {
    match state.fs.mkdir(&query.path, 0o755).await {
        Ok(_) => (
            StatusCode::CREATED,
            Json(ApiResponse::success(serde_json::json!({
                "path": query.path
            }))),
        )
            .into_response(),
        Err(e) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ApiResponse::<()>::error(e.to_string())),
        )
            .into_response(),
    }
}

/// POST /api/v1/directories/ensure-parent - Ensure parent directories exist
pub async fn ensure_parent_dirs(
    State(state): State<AppState>,
    Query(query): Query<DirQuery>,
) -> Response {
    let mode = query.mode.and_then(|m| u32::from_str_radix(&m, 8).ok()).unwrap_or(0o755);
    match state.fs.ensure_parent_dirs(&query.path, mode).await {
        Ok(_) => (
            StatusCode::OK,
            Json(ApiResponse::success(serde_json::json!({
                "path": query.path
            }))),
        )
            .into_response(),
        Err(e) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ApiResponse::<()>::error(e.to_string())),
        )
            .into_response(),
    }
}

// ============================================================================
// Mount Management Handlers
// ============================================================================

/// GET /api/v1/mounts - List all mounts
pub async fn list_mounts(State(state): State<AppState>) -> Response {
    let mounts = state.fs.list_mounts().await;
    let mount_infos: Vec<MountInfo> = mounts
        .into_iter()
        .map(|(path, plugin)| MountInfo { path, plugin })
        .collect();

    (StatusCode::OK, Json(ApiResponse::success(mount_infos))).into_response()
}

/// POST /api/v1/mount - Mount a filesystem
pub async fn mount_filesystem(
    State(state): State<AppState>,
    Json(req): Json<MountRequest>,
) -> Response {
    // Convert JSON params to ConfigValue
    let params = req
        .params
        .into_iter()
        .map(|(k, v)| {
            let config_value = match v {
                serde_json::Value::String(s) => crate::core::ConfigValue::String(s),
                serde_json::Value::Number(n) => {
                    if let Some(i) = n.as_i64() {
                        crate::core::ConfigValue::Int(i)
                    } else {
                        crate::core::ConfigValue::String(n.to_string())
                    }
                }
                serde_json::Value::Bool(b) => crate::core::ConfigValue::Bool(b),
                serde_json::Value::Array(arr) => {
                    let strings: Vec<String> = arr
                        .into_iter()
                        .filter_map(|v| v.as_str().map(|s| s.to_string()))
                        .collect();
                    crate::core::ConfigValue::StringList(strings)
                }
                _ => crate::core::ConfigValue::String(v.to_string()),
            };
            (k, config_value)
        })
        .collect();

    let config = PluginConfig {
        name: req.plugin.clone(),
        mount_path: req.path.clone(),
        params,
    };

    match state.fs.mount(config).await {
        Ok(_) => (
            StatusCode::OK,
            Json(ApiResponse::success(serde_json::json!({
                "plugin": req.plugin,
                "path": req.path
            }))),
        )
            .into_response(),
        Err(e) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ApiResponse::<()>::error(e.to_string())),
        )
            .into_response(),
    }
}

/// POST /api/v1/unmount - Unmount a filesystem
pub async fn unmount_filesystem(
    State(state): State<AppState>,
    Json(req): Json<UnmountRequest>,
) -> Response {
    match state.fs.unmount(&req.path).await {
        Ok(_) => (
            StatusCode::OK,
            Json(ApiResponse::success(serde_json::json!({
                "path": req.path
            }))),
        )
            .into_response(),
        Err(e) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ApiResponse::<()>::error(e.to_string())),
        )
            .into_response(),
    }
}

// ============================================================================
// Health Check Handler
// ============================================================================

/// POST /api/v1/grep - Search file content
pub async fn grep_content(State(state): State<AppState>, Json(req): Json<GrepRequest>) -> Response {
    if req.stream {
        return (
            StatusCode::NOT_IMPLEMENTED,
            Json(ApiResponse::<()>::error(
                "streaming grep is not supported by the ragfs HTTP server",
            )),
        )
            .into_response();
    }

    match state
        .fs
        .grep(
            &req.path,
            &req.pattern,
            req.recursive,
            req.case_insensitive,
            req.node_limit,
            req.exclude_path.as_deref(),
            req.level_limit,
        )
        .await
    {
        Ok(result) => (StatusCode::OK, Json(ApiResponse::success(result))).into_response(),
        Err(e) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ApiResponse::<GrepResult>::error(e.to_string())),
        )
            .into_response(),
    }
}

/// GET /api/v1/health - Health check
pub async fn health_check() -> Response {
    let response = HealthResponse {
        status: "healthy".to_string(),
        version: crate::VERSION.to_string(),
    };

    (StatusCode::OK, Json(ApiResponse::success(response))).into_response()
}

// ============================================================================
// Statistics Handlers
// ============================================================================

/// GET /api/v1/stats - Get filesystem statistics
pub async fn get_stats(State(state): State<AppState>, Query(query): Query<StatsQuery>) -> Response {
    if let Some(path) = query.path {
        // Get stats for a specific mount
        match state.fs.get_mount_stats(&path).await {
            Ok(stats) => {
                let mounts = state.fs.list_mounts().await;
                let plugin_name = mounts
                    .into_iter()
                    .find(|(p, _)| p == &path)
                    .map(|(_, plugin)| plugin)
                    .unwrap_or_default();

                let mount_stats = MountStats {
                    path,
                    plugin: plugin_name,
                    stats,
                };

                (StatusCode::OK, Json(ApiResponse::success(mount_stats))).into_response()
            }
            Err(e) => (
                StatusCode::NOT_FOUND,
                Json(ApiResponse::<()>::error(e.to_string())),
            )
                .into_response(),
        }
    } else {
        // Get stats for all mounts
        let all_stats = state.fs.get_all_stats().await;
        let mounts: Vec<MountStats> = all_stats
            .into_iter()
            .map(|(path, (plugin, stats))| MountStats { path, plugin, stats })
            .collect();

        let response = AllStatsResponse { mounts };
        (StatusCode::OK, Json(ApiResponse::success(response))).into_response()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use axum::extract::State;
    use std::collections::HashMap;

    use crate::plugins::LocalFSPlugin;

    #[tokio::test]
    async fn test_grep_content_forwards_exclude_path_and_level_limit() {
        let fs = Arc::new(MountableFS::new());
        fs.register_plugin(LocalFSPlugin::new()).await;

        let temp = tempfile::TempDir::new().unwrap();
        std::fs::create_dir_all(temp.path().join("excluded")).unwrap();
        std::fs::create_dir_all(temp.path().join("ok")).unwrap();
        std::fs::write(temp.path().join("excluded/x.txt"), "hit\n").unwrap();
        std::fs::write(temp.path().join("ok/y.txt"), "hit\n").unwrap();

        fs.mount(PluginConfig {
            name: "localfs".to_string(),
            mount_path: "/local".to_string(),
            params: HashMap::from([(
                "local_dir".to_string(),
                crate::core::ConfigValue::String(temp.path().to_string_lossy().to_string()),
            )]),
        })
        .await
        .unwrap();

        let response = grep_content(
            State(AppState { fs: fs.clone() }),
            Json(GrepRequest {
                path: "/local".to_string(),
                pattern: "hit".to_string(),
                recursive: true,
                case_insensitive: false,
                stream: false,
                node_limit: Some(1),
                exclude_path: Some("/local/excluded".to_string()),
                level_limit: Some(5),
            }),
        )
        .await;

        let body = axum::body::to_bytes(response.into_body(), usize::MAX)
            .await
            .unwrap();
        let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();

        assert_eq!(parsed["success"], true);
        assert_eq!(parsed["data"]["count"], 1);
        assert_eq!(parsed["data"]["matches"][0]["file"], "ok/y.txt");
    }
}
