use axum::{
    extract::{Request, State},
    http::{HeaderMap, StatusCode},
    middleware::Next,
    response::{IntoResponse, Response},
};
use rustelo_core_lib::auth::{AccessContext, AccessResult, ResourceType, User};
use std::collections::HashMap;
use std::sync::Arc;
use uuid::Uuid;

use super::{
    middleware::{extract_auth_context_from_request, AuthContext},
    rbac_service::RBACService,
};

/// RBAC middleware context that gets added to request extensions
#[derive(Debug, Clone)]
pub struct RBACContext {
    pub access_granted: bool,
    pub access_result: AccessResult,
    pub resource_type: ResourceType,
    pub resource_name: String,
    pub action: String,
    pub user: Option<User>,
}

impl RBACContext {
    pub fn new(
        access_result: AccessResult,
        resource_type: ResourceType,
        resource_name: String,
        action: String,
        user: Option<User>,
    ) -> Self {
        let access_granted = matches!(access_result, AccessResult::Allow);

        Self {
            access_granted,
            access_result,
            resource_type,
            resource_name,
            action,
            user,
        }
    }

    pub fn is_access_granted(&self) -> bool {
        self.access_granted
    }

    pub fn requires_additional_auth(&self) -> bool {
        matches!(self.access_result, AccessResult::RequireAdditionalAuth)
    }
}

/// Generic RBAC middleware that can be configured for different resource types
pub async fn rbac_middleware(
    State(rbac_service): State<Arc<RBACService>>,
    mut request: Request,
    next: Next,
) -> Response {
    // Extract authentication context
    let auth_context = match extract_auth_context_from_request(&request) {
        Ok(ctx) => ctx,
        Err(_) => {
            return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response();
        }
    };

    // Try to extract resource information from request
    let (resource_type, resource_name, action) = match extract_resource_info(&request) {
        Ok(info) => info,
        Err(_) => {
            // If we can't extract resource info, allow the request to proceed
            // This is for endpoints that don't require RBAC
            return next.run(request).await;
        }
    };

    // Create access context
    let access_context = AccessContext {
        user: auth_context.user.clone(),
        resource_type: resource_type.clone(),
        resource_name: resource_name.clone(),
        action: action.clone(),
        additional_context: extract_additional_context(&request),
    };

    // Check access
    let access_result = match rbac_service.check_access(&access_context).await {
        Ok(result) => result,
        Err(_) => {
            return (StatusCode::INTERNAL_SERVER_ERROR, "Access check failed").into_response();
        }
    };

    // Create RBAC context
    let rbac_context = RBACContext::new(
        access_result,
        resource_type,
        resource_name,
        action,
        auth_context.user.clone(),
    );

    // Add RBAC context to request extensions
    request.extensions_mut().insert(rbac_context.clone());

    // Handle access result
    match rbac_context.access_result {
        AccessResult::Allow => next.run(request).await,
        AccessResult::Deny => (StatusCode::FORBIDDEN, "Access denied").into_response(),
        AccessResult::RequireAdditionalAuth => (
            StatusCode::UNAUTHORIZED,
            "Additional authentication required",
        )
            .into_response(),
    }
}

/// Database-specific RBAC middleware
pub fn require_database_access(
    database_name: String,
    action: String,
) -> impl Fn(Request, Next) -> std::pin::Pin<Box<dyn std::future::Future<Output = Response> + Send>>
       + Clone {
    move |mut request: Request, next: Next| {
        let database_name = database_name.clone();
        let action = action.clone();

        Box::pin(async move {
            // Extract RBAC service from state
            let rbac_service = match request.extensions().get::<Arc<RBACService>>() {
                Some(service) => service.clone(),
                None => {
                    return (StatusCode::INTERNAL_SERVER_ERROR, "RBAC service not found")
                        .into_response();
                }
            };

            // Extract authentication context
            let auth_context = match extract_auth_context_from_request(&request) {
                Ok(ctx) => ctx,
                Err(_) => {
                    return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
                        .into_response();
                }
            };

            let user = match &auth_context.user {
                Some(u) => u,
                None => {
                    return (StatusCode::UNAUTHORIZED, "Authentication required").into_response();
                }
            };

            // Check database access
            match rbac_service
                .check_database_access(user, &database_name, &action)
                .await
            {
                Ok(AccessResult::Allow) => {
                    let rbac_context = RBACContext::new(
                        AccessResult::Allow,
                        ResourceType::Database,
                        database_name,
                        action,
                        Some(user.clone()),
                    );
                    request.extensions_mut().insert(rbac_context);
                    next.run(request).await
                }
                Ok(AccessResult::Deny) => {
                    (StatusCode::FORBIDDEN, "Database access denied").into_response()
                }
                Ok(AccessResult::RequireAdditionalAuth) => (
                    StatusCode::UNAUTHORIZED,
                    "Additional authentication required",
                )
                    .into_response(),
                Err(_) => (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    "Database access check failed",
                )
                    .into_response(),
            }
        })
    }
}

/// File-specific RBAC middleware
pub fn require_file_access(
    file_path: String,
    action: String,
) -> impl Fn(Request, Next) -> std::pin::Pin<Box<dyn std::future::Future<Output = Response> + Send>>
       + Clone {
    move |mut request: Request, next: Next| {
        let file_path = file_path.clone();
        let action = action.clone();

        Box::pin(async move {
            // Extract RBAC service from state
            let rbac_service = match request.extensions().get::<Arc<RBACService>>() {
                Some(service) => service.clone(),
                None => {
                    return (StatusCode::INTERNAL_SERVER_ERROR, "RBAC service not found")
                        .into_response();
                }
            };

            // Extract authentication context
            let auth_context = match extract_auth_context_from_request(&request) {
                Ok(ctx) => ctx,
                Err(_) => {
                    return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
                        .into_response();
                }
            };

            let user = match &auth_context.user {
                Some(u) => u,
                None => {
                    return (StatusCode::UNAUTHORIZED, "Authentication required").into_response();
                }
            };

            // Check file access
            match rbac_service
                .check_file_access(user, &file_path, &action)
                .await
            {
                Ok(AccessResult::Allow) => {
                    let rbac_context = RBACContext::new(
                        AccessResult::Allow,
                        ResourceType::File,
                        file_path,
                        action,
                        Some(user.clone()),
                    );
                    request.extensions_mut().insert(rbac_context);
                    next.run(request).await
                }
                Ok(AccessResult::Deny) => {
                    (StatusCode::FORBIDDEN, "File access denied").into_response()
                }
                Ok(AccessResult::RequireAdditionalAuth) => (
                    StatusCode::UNAUTHORIZED,
                    "Additional authentication required",
                )
                    .into_response(),
                Err(_) => (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    "File access check failed",
                )
                    .into_response(),
            }
        })
    }
}

/// Content-specific RBAC middleware
pub fn require_content_access(
    content_id: String,
    action: String,
) -> impl Fn(Request, Next) -> std::pin::Pin<Box<dyn std::future::Future<Output = Response> + Send>>
       + Clone {
    move |mut request: Request, next: Next| {
        let content_id = content_id.clone();
        let action = action.clone();

        Box::pin(async move {
            // Extract RBAC service from state
            let rbac_service = match request.extensions().get::<Arc<RBACService>>() {
                Some(service) => service.clone(),
                None => {
                    return (StatusCode::INTERNAL_SERVER_ERROR, "RBAC service not found")
                        .into_response();
                }
            };

            // Extract authentication context
            let auth_context = match extract_auth_context_from_request(&request) {
                Ok(ctx) => ctx,
                Err(_) => {
                    return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
                        .into_response();
                }
            };

            let user = match &auth_context.user {
                Some(u) => u,
                None => {
                    return (StatusCode::UNAUTHORIZED, "Authentication required").into_response();
                }
            };

            // Check content access
            match rbac_service
                .check_content_access(user, &content_id, &action)
                .await
            {
                Ok(AccessResult::Allow) => {
                    let rbac_context = RBACContext::new(
                        AccessResult::Allow,
                        ResourceType::Content,
                        content_id,
                        action,
                        Some(user.clone()),
                    );
                    request.extensions_mut().insert(rbac_context);
                    next.run(request).await
                }
                Ok(AccessResult::Deny) => {
                    (StatusCode::FORBIDDEN, "Content access denied").into_response()
                }
                Ok(AccessResult::RequireAdditionalAuth) => (
                    StatusCode::UNAUTHORIZED,
                    "Additional authentication required",
                )
                    .into_response(),
                Err(_) => (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    "Content access check failed",
                )
                    .into_response(),
            }
        })
    }
}

/// Category-based access middleware
pub fn require_category_access(
    required_categories: Vec<String>,
) -> impl Fn(Request, Next) -> std::pin::Pin<Box<dyn std::future::Future<Output = Response> + Send>>
       + Clone {
    move |request: Request, next: Next| {
        let required_categories = required_categories.clone();

        Box::pin(async move {
            // Extract authentication context
            let auth_context = match extract_auth_context_from_request(&request) {
                Ok(ctx) => ctx,
                Err(_) => {
                    return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
                        .into_response();
                }
            };

            let user = match &auth_context.user {
                Some(u) => u,
                None => {
                    return (StatusCode::UNAUTHORIZED, "Authentication required").into_response();
                }
            };

            // Check if user has any of the required categories
            if user.has_any_category(&required_categories) {
                next.run(request).await
            } else {
                (StatusCode::FORBIDDEN, "Insufficient category access").into_response()
            }
        })
    }
}

/// Tag-based access middleware
pub fn require_tag_access(
    required_tags: Vec<String>,
) -> impl Fn(Request, Next) -> std::pin::Pin<Box<dyn std::future::Future<Output = Response> + Send>>
       + Clone {
    move |request: Request, next: Next| {
        let required_tags = required_tags.clone();

        Box::pin(async move {
            // Extract authentication context
            let auth_context = match extract_auth_context_from_request(&request) {
                Ok(ctx) => ctx,
                Err(_) => {
                    return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
                        .into_response();
                }
            };

            let user = match &auth_context.user {
                Some(u) => u,
                None => {
                    return (StatusCode::UNAUTHORIZED, "Authentication required").into_response();
                }
            };

            // Check if user has any of the required tags
            if user.has_any_tag(&required_tags) {
                next.run(request).await
            } else {
                (StatusCode::FORBIDDEN, "Insufficient tag access").into_response()
            }
        })
    }
}

/// Extract resource information from request
fn extract_resource_info(
    request: &Request,
) -> Result<(ResourceType, String, String), &'static str> {
    let path = request.uri().path();
    let method = request.method().as_str();

    // Parse resource info from path and method
    // This is a simplified example - you'd customize this based on your API structure
    if path.starts_with("/api/database/") {
        let resource_name = path.strip_prefix("/api/database/").unwrap_or("unknown");
        let action = match method {
            "GET" => "read",
            "POST" | "PUT" | "PATCH" => "write",
            "DELETE" => "delete",
            _ => "unknown",
        };
        Ok((
            ResourceType::Database,
            resource_name.to_string(),
            action.to_string(),
        ))
    } else if path.starts_with("/api/files/") {
        let resource_name = path.strip_prefix("/api/files/").unwrap_or("unknown");
        let action = match method {
            "GET" => "read",
            "POST" | "PUT" | "PATCH" => "write",
            "DELETE" => "delete",
            _ => "unknown",
        };
        Ok((
            ResourceType::File,
            resource_name.to_string(),
            action.to_string(),
        ))
    } else if path.starts_with("/api/content/") {
        let resource_name = path.strip_prefix("/api/content/").unwrap_or("unknown");
        let action = match method {
            "GET" => "read",
            "POST" | "PUT" | "PATCH" => "write",
            "DELETE" => "delete",
            _ => "unknown",
        };
        Ok((
            ResourceType::Content,
            resource_name.to_string(),
            action.to_string(),
        ))
    } else {
        Err("Unable to extract resource info")
    }
}

/// Extract additional context from request
fn extract_additional_context(request: &Request) -> HashMap<String, String> {
    let mut context = HashMap::new();

    // Add request method
    context.insert("method".to_string(), request.method().to_string());

    // Add request path
    context.insert("path".to_string(), request.uri().path().to_string());

    // Add query parameters
    if let Some(query) = request.uri().query() {
        context.insert("query".to_string(), query.to_string());
    }

    // Add user agent if available
    if let Some(user_agent) = request.headers().get("user-agent") {
        if let Ok(ua_str) = user_agent.to_str() {
            context.insert("user_agent".to_string(), ua_str.to_string());
        }
    }

    // Add IP address if available (from X-Forwarded-For or similar)
    if let Some(forwarded) = request.headers().get("x-forwarded-for") {
        if let Ok(ip_str) = forwarded.to_str() {
            context.insert("ip_address".to_string(), ip_str.to_string());
        }
    }

    context
}

/// Extract RBAC context from request extensions
pub fn extract_rbac_context_from_request(request: &Request) -> Result<&RBACContext, &'static str> {
    request
        .extensions()
        .get::<RBACContext>()
        .ok_or("RBAC context not found")
}

/// Helper macro for creating RBAC middleware
#[macro_export]
macro_rules! require_rbac {
    (database: $db_name:expr, action: $action:expr) => {
        axum::middleware::from_fn(require_database_access(
            $db_name.to_string(),
            $action.to_string(),
        ))
    };
    (file: $file_path:expr, action: $action:expr) => {
        axum::middleware::from_fn(require_file_access(
            $file_path.to_string(),
            $action.to_string(),
        ))
    };
    (content: $content_id:expr, action: $action:expr) => {
        axum::middleware::from_fn(require_content_access(
            $content_id.to_string(),
            $action.to_string(),
        ))
    };
    (categories: $categories:expr) => {
        axum::middleware::from_fn(require_category_access($categories))
    };
    (tags: $tags:expr) => {
        axum::middleware::from_fn(require_tag_access($tags))
    };
}

#[cfg(test)]
mod tests {
    use super::*;
    use axum::http::{Method, Uri};
    use rustelo_core_lib::auth::{Role, UserProfile};
    use std::collections::HashMap;

    fn create_test_user() -> User {
        User {
            id: Uuid::new_v4(),
            email: "test@example.com".to_string(),
            username: "testuser".to_string(),
            display_name: Some("Test User".to_string()),
            avatar_url: None,
            roles: vec![Role::User],
            is_active: true,
            email_verified: true,
            created_at: chrono::Utc::now(),
            updated_at: chrono::Utc::now(),
            last_login: None,
            two_factor_enabled: false,
            profile: UserProfile {
                first_name: None,
                last_name: None,
                bio: None,
                timezone: None,
                locale: None,
                preferences: HashMap::new(),
                categories: vec!["editor".to_string()],
                tags: vec!["internal".to_string()],
            },
        }
    }

    #[test]
    fn test_rbac_context_creation() {
        let user = create_test_user();
        let context = RBACContext::new(
            AccessResult::Allow,
            ResourceType::Database,
            "test_db".to_string(),
            "read".to_string(),
            Some(user.clone()),
        );

        assert!(context.is_access_granted());
        assert!(!context.requires_additional_auth());
        assert_eq!(context.resource_name, "test_db");
        assert_eq!(context.action, "read");
    }

    #[test]
    fn test_extract_resource_info() {
        let request = Request::builder()
            .method(Method::GET)
            .uri("/api/database/test_db")
            .body(())
            .unwrap();

        let (resource_type, resource_name, action) = extract_resource_info(&request).unwrap();
        assert_eq!(resource_type, ResourceType::Database);
        assert_eq!(resource_name, "test_db");
        assert_eq!(action, "read");
    }

    #[test]
    fn test_extract_additional_context() {
        let request = Request::builder()
            .method(Method::POST)
            .uri("/api/content/123?filter=active")
            .header("user-agent", "test-agent")
            .body(())
            .unwrap();

        let context = extract_additional_context(&request);
        assert_eq!(context.get("method"), Some(&"POST".to_string()));
        assert_eq!(context.get("path"), Some(&"/api/content/123".to_string()));
        assert_eq!(context.get("query"), Some(&"filter=active".to_string()));
        assert_eq!(context.get("user_agent"), Some(&"test-agent".to_string()));
    }
}
