//! Audit logging middleware for HTTP requests
//!
//! Automatically logs all API requests with full context.

use std::sync::Arc;
use std::time::Instant;

use axum::{extract::Request, middleware::Next, response::Response};
use tracing::{debug, warn};

use crate::audit::{ActionInfo, ActionType, AuditEvent, AuditLogger, AuthorizationInfo, UserInfo};

/// Audit middleware state
#[derive(Clone)]
pub struct AuditMiddleware {
    logger: Arc<AuditLogger>,
}

impl AuditMiddleware {
    /// Create new audit middleware
    pub fn new(logger: Arc<AuditLogger>) -> Self {
        Self { logger }
    }

    /// Create middleware function (use with .layer())
    pub fn into_middleware(
        self,
    ) -> impl Fn(
        Request,
        Next,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Response> + Send>>
           + Clone
           + Send
           + 'static {
        move |req, next| {
            let middleware = self.clone();
            Box::pin(async move { middleware.handle(req, next).await })
        }
    }

    /// Handle request with audit logging
    pub async fn handle(&self, req: Request, next: Next) -> Response {
        let start = Instant::now();
        let method = req.method().clone();
        let uri = req.uri().clone();
        let path = uri.path().to_string();

        // Extract user information from headers
        let headers = req.headers();
        let user_id = headers
            .get("X-User-ID")
            .and_then(|v| v.to_str().ok())
            .unwrap_or("anonymous")
            .to_string();

        let user_name = headers
            .get("X-User-Name")
            .and_then(|v| v.to_str().ok())
            .unwrap_or("Anonymous User")
            .to_string();

        let user_ip = headers
            .get("X-Forwarded-For")
            .and_then(|v| v.to_str().ok())
            .or_else(|| headers.get("X-Real-IP").and_then(|v| v.to_str().ok()))
            .unwrap_or("unknown")
            .to_string();

        let user_agent = headers
            .get("User-Agent")
            .and_then(|v| v.to_str().ok())
            .unwrap_or("unknown")
            .to_string();

        let session_id = headers
            .get("X-Session-ID")
            .and_then(|v| v.to_str().ok())
            .map(|s| s.to_string());

        // Extract authorization information
        let token_id = headers
            .get("Authorization")
            .and_then(|v| v.to_str().ok())
            .and_then(|s| s.strip_prefix("Bearer "))
            .unwrap_or("no-token")
            .to_string();

        let cedar_policy = headers
            .get("X-Cedar-Policy")
            .and_then(|v| v.to_str().ok())
            .unwrap_or("default")
            .to_string();

        let mfa_verified = headers
            .get("X-MFA-Verified")
            .and_then(|v| v.to_str().ok())
            .map(|s| s == "true")
            .unwrap_or(false);

        let permissions = headers
            .get("X-Permissions")
            .and_then(|v| v.to_str().ok())
            .map(|s| s.split(',').map(|p| p.trim().to_string()).collect())
            .unwrap_or_else(Vec::new);

        // Determine action type from path and method
        let action_type = determine_action_type(method.as_ref(), &path);

        // Determine resource and workspace from path
        let workspace_from_header = headers
            .get("X-Workspace-ID")
            .and_then(|v| v.to_str().ok())
            .map(|s| s.to_string());
        let (resource, workspace) = extract_resource_info(&path, workspace_from_header);

        // Create user info
        let user = UserInfo {
            id: user_id,
            name: user_name,
            ip: user_ip,
            user_agent,
            email: None, // Not included in headers for security
            session_id,
        };

        // Create action info
        let action = ActionInfo {
            action_type,
            resource,
            workspace,
            parameters: serde_json::json!({
                "method": method.to_string(),
                "path": path,
                "query": uri.query().unwrap_or(""),
            }),
            tags: Default::default(),
        };

        // Create authorization info
        let authorization = AuthorizationInfo {
            token_id,
            cedar_policy,
            mfa_verified,
            permissions,
            evaluation_duration_us: None,
        };

        // Create initial audit event
        let mut event = AuditEvent::new(user, action, authorization);

        // Process request
        let response = next.run(req).await;

        // Calculate duration
        let duration = start.elapsed();
        let duration_ms = duration.as_millis() as u64;

        // Determine result based on status code
        let status = response.status();
        event = if status.is_success() {
            event.complete(duration_ms)
        } else if status.is_client_error() {
            event.fail(duration_ms, format!("Client error: {}", status))
        } else if status.is_server_error() {
            event.error(duration_ms, format!("Server error: {}", status))
        } else {
            event.complete(duration_ms)
        };

        // Add response metadata
        event = event.add_metadata("status_code".to_string(), status.as_u16().to_string());
        event = event.add_metadata("method".to_string(), method.to_string());
        event = event.add_metadata("path".to_string(), path.clone());

        // Log the event (non-blocking)
        let logger = self.logger.clone();
        tokio::spawn(async move {
            if let Err(e) = logger.log(event).await {
                warn!("Failed to log audit event: {}", e);
            }
        });

        debug!(
            method = %method,
            path = %path,
            status = %status,
            duration_ms = %duration_ms,
            "API request audited"
        );

        response
    }
}

/// Determine action type from HTTP method and path
fn determine_action_type(method: &str, path: &str) -> ActionType {
    // Server operations
    if path.starts_with("/workflows/servers/") || path.contains("/server") {
        return match method {
            "POST" => ActionType::ServerCreate,
            "DELETE" => ActionType::ServerDelete,
            "PUT" | "PATCH" => ActionType::ServerUpdate,
            "GET" => ActionType::ServerList,
            _ => ActionType::Unknown,
        };
    }

    // Task service operations
    if path.starts_with("/workflows/taskserv/") || path.contains("/taskserv") {
        return match method {
            "POST" if path.contains("/create") => ActionType::TaskservCreate,
            "DELETE" => ActionType::TaskservDelete,
            "PUT" | "PATCH" => ActionType::TaskservUpdate,
            "POST" if path.contains("/generate") => ActionType::TaskservGenerate,
            "GET" => ActionType::TaskservList,
            _ => ActionType::Unknown,
        };
    }

    // Cluster operations
    if path.starts_with("/workflows/cluster/") || path.contains("/cluster") {
        return match method {
            "POST" => ActionType::ClusterCreate,
            "DELETE" => ActionType::ClusterDelete,
            "PUT" | "PATCH" => ActionType::ClusterUpdate,
            "GET" => ActionType::ClusterList,
            _ => ActionType::Unknown,
        };
    }

    // Workflow operations
    if path.contains("/workflows") || path.contains("/batch") {
        return match method {
            "POST" if path.contains("/submit") || path.contains("/execute") => {
                if path.contains("/batch") {
                    ActionType::BatchSubmit
                } else {
                    ActionType::WorkflowSubmit
                }
            }
            "POST" if path.contains("/cancel") => {
                if path.contains("/batch") {
                    ActionType::BatchCancel
                } else {
                    ActionType::WorkflowCancel
                }
            }
            "POST" if path.contains("/rollback") => ActionType::WorkflowRollback,
            "GET" => {
                if path.contains("/batch") {
                    ActionType::BatchList
                } else {
                    ActionType::WorkflowList
                }
            }
            _ => ActionType::Unknown,
        };
    }

    // Configuration operations
    if path.contains("/config") {
        return match method {
            "GET" => ActionType::ConfigRead,
            "POST" | "PUT" => ActionType::ConfigWrite,
            "DELETE" => ActionType::ConfigDelete,
            _ => ActionType::Unknown,
        };
    }

    // Workspace operations
    if path.contains("/workspaces") || path.contains("/workspace") {
        return match method {
            "POST" if path.contains("/create") => ActionType::WorkspaceCreate,
            "POST" if path.contains("/switch") => ActionType::WorkspaceSwitch,
            "POST" if path.contains("/sync") => ActionType::WorkspaceSync,
            "PUT" | "PATCH" => ActionType::WorkspaceUpdate,
            "DELETE" => ActionType::WorkspaceDelete,
            "GET" => ActionType::WorkspaceList,
            _ => ActionType::Unknown,
        };
    }

    // System operations
    if path.contains("/health") {
        return ActionType::SystemHealthCheck;
    }

    if path.contains("/metrics") {
        return ActionType::SystemMetrics;
    }

    ActionType::Unknown
}

/// Extract resource and workspace information from path
fn extract_resource_info(path: &str, workspace_header: Option<String>) -> (String, String) {
    // Try to extract resource ID from path
    let parts: Vec<&str> = path.trim_matches('/').split('/').collect();

    let resource = if parts.len() > 1 {
        // If path has format /resource/{id}, extract the ID
        if let Some(id) = parts.last() {
            if !id.is_empty() && !id.starts_with('{') {
                id.to_string()
            } else {
                "*".to_string()
            }
        } else {
            "*".to_string()
        }
    } else {
        "*".to_string()
    };

    // Try to extract workspace from header first, then from path
    let workspace = if let Some(ws) = workspace_header {
        ws
    } else if path.contains("/workspaces/") {
        // Extract workspace name from path like /workspaces/{name}/...
        if let Some(idx) = path.find("/workspaces/") {
            let after_prefix = &path[idx + "/workspaces/".len()..];
            if let Some(end_idx) = after_prefix.find('/') {
                after_prefix[..end_idx].to_string()
            } else {
                after_prefix.to_string()
            }
        } else {
            "default".to_string()
        }
    } else {
        "default".to_string()
    };

    (resource, workspace)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_determine_action_type() {
        assert_eq!(
            determine_action_type("POST", "/workflows/servers/create"),
            ActionType::ServerCreate
        );

        assert_eq!(
            determine_action_type("GET", "/workflows/servers"),
            ActionType::ServerList
        );

        assert_eq!(
            determine_action_type("DELETE", "/workflows/taskserv/kubernetes"),
            ActionType::TaskservDelete
        );

        assert_eq!(
            determine_action_type("POST", "/batch/execute"),
            ActionType::BatchSubmit
        );

        assert_eq!(
            determine_action_type("GET", "/health"),
            ActionType::SystemHealthCheck
        );
    }

    #[test]
    fn test_extract_resource_info() {
        let (resource, workspace) = extract_resource_info("/workflows/servers/server-123", None);
        assert_eq!(resource, "server-123");
        assert_eq!(workspace, "default");

        let (resource, workspace) = extract_resource_info("/workflows/servers", None);
        assert_eq!(resource, "servers");
        assert_eq!(workspace, "default");
    }

    #[test]
    fn test_workspace_action_types() {
        assert_eq!(
            determine_action_type("POST", "/workspaces/prod/create"),
            ActionType::WorkspaceCreate
        );

        assert_eq!(
            determine_action_type("POST", "/workspaces/prod/switch"),
            ActionType::WorkspaceSwitch
        );

        assert_eq!(
            determine_action_type("DELETE", "/workspaces/prod"),
            ActionType::WorkspaceDelete
        );

        assert_eq!(
            determine_action_type("GET", "/workspaces"),
            ActionType::WorkspaceList
        );
    }

    #[test]
    fn test_workspace_info_extraction() {
        let (resource, workspace) = extract_resource_info("/workspaces/prod/config", None);
        assert_eq!(workspace, "prod");

        let (resource, workspace) =
            extract_resource_info("/workspaces/staging/create", Some("prod".to_string()));
        assert_eq!(workspace, "prod"); // Header takes precedence
    }
}
