//! Security context builder for request-level security information
//!
//! Builds complete security context from incoming HTTP requests, integrating:
//! - JWT token validation
//! - User authentication
//! - MFA verification status
//! - IP address and user agent tracking
//! - Permissions and workspace information

use std::net::IpAddr;
use std::str::FromStr;

use axum::{body::Body, extract::Request, http::header};
use tracing::debug;

use crate::security::ValidatedToken;

/// Complete security context for an authenticated request
#[derive(Debug, Clone)]
pub struct SecurityContext {
    /// Authenticated user ID
    pub user_id: String,

    /// Validated JWT token claims
    pub token: ValidatedToken,

    /// Whether MFA has been verified for this session
    pub mfa_verified: bool,

    /// Client IP address
    pub ip_address: IpAddr,

    /// User agent string
    pub user_agent: Option<String>,

    /// Permissions list (from token claims)
    pub permissions: Vec<String>,

    /// Workspace identifier
    pub workspace: String,

    /// Request ID for tracing
    pub request_id: String,

    /// Session ID (if available)
    pub session_id: Option<String>,
}

impl SecurityContext {
    /// Build security context from validated token and request
    pub fn from_token_and_request<B: Send>(token: ValidatedToken, req: &Request<B>) -> Self {
        let mfa_verified = extract_mfa_status(req);
        let ip_address = extract_ip_address(req);
        let user_agent = extract_user_agent(req);
        let request_id = extract_request_id(req);
        let session_id = extract_session_id(req);

        // Extract permissions from token metadata
        // Metadata is HashMap<String, String>, so permissions might be comma-separated
        let permissions = token
            .claims
            .metadata
            .as_ref()
            .and_then(|m| m.get("permissions"))
            .map(|p| {
                p.split(',')
                    .map(|s| s.trim().to_string())
                    .filter(|s| !s.is_empty())
                    .collect()
            })
            .unwrap_or_default();

        let workspace = token.claims.workspace.clone();

        Self {
            user_id: token.claims.sub.clone(),
            token,
            mfa_verified,
            ip_address,
            user_agent,
            permissions,
            workspace,
            request_id,
            session_id,
        }
    }

    /// Check if user has specific permission
    pub fn has_permission(&self, permission: &str) -> bool {
        self.permissions.iter().any(|p| p == permission)
    }

    /// Check if user has any of the specified permissions
    pub fn has_any_permission(&self, permissions: &[&str]) -> bool {
        permissions.iter().any(|p| self.has_permission(p))
    }

    /// Check if user has all of the specified permissions
    pub fn has_all_permissions(&self, permissions: &[&str]) -> bool {
        permissions.iter().all(|p| self.has_permission(p))
    }

    /// Check if MFA is verified (required for sensitive operations)
    pub fn is_mfa_verified(&self) -> bool {
        self.mfa_verified
    }

    /// Get remaining token validity in seconds
    pub fn remaining_validity(&self) -> i64 {
        self.token.remaining_validity
    }
}

/// Extract MFA verification status from request headers
fn extract_mfa_status<B: Send>(req: &Request<B>) -> bool {
    req.headers()
        .get("X-MFA-Verified")
        .and_then(|h| h.to_str().ok())
        .map(|s| s.eq_ignore_ascii_case("true"))
        .unwrap_or(false)
}

/// Extract client IP address from request headers or connection info
fn extract_ip_address<B: Send>(req: &Request<B>) -> IpAddr {
    // Try X-Forwarded-For first (proxy/load balancer)
    if let Some(forwarded) = req
        .headers()
        .get("X-Forwarded-For")
        .and_then(|h| h.to_str().ok())
    {
        if let Some(first_ip) = forwarded.split(',').next() {
            if let Ok(ip) = IpAddr::from_str(first_ip.trim()) {
                debug!("Extracted IP from X-Forwarded-For: {}", ip);
                return ip;
            }
        }
    }

    // Try X-Real-IP next
    if let Some(real_ip) = req.headers().get("X-Real-IP").and_then(|h| h.to_str().ok()) {
        if let Ok(ip) = IpAddr::from_str(real_ip.trim()) {
            debug!("Extracted IP from X-Real-IP: {}", ip);
            return ip;
        }
    }

    // Default to localhost if no IP found
    debug!("No IP found in headers, using localhost");
    IpAddr::from_str("127.0.0.1").unwrap()
}

/// Extract user agent from request headers
fn extract_user_agent<B: Send>(req: &Request<B>) -> Option<String> {
    req.headers()
        .get(header::USER_AGENT)
        .and_then(|h| h.to_str().ok())
        .map(|s| s.to_string())
}

/// Extract or generate request ID for tracing
fn extract_request_id<B: Send>(req: &Request<B>) -> String {
    req.headers()
        .get("X-Request-ID")
        .and_then(|h| h.to_str().ok())
        .map(|s| s.to_string())
        .unwrap_or_else(|| uuid::Uuid::new_v4().to_string())
}

/// Extract session ID from request headers
fn extract_session_id<B: Send>(req: &Request<B>) -> Option<String> {
    req.headers()
        .get("X-Session-ID")
        .and_then(|h| h.to_str().ok())
        .map(|s| s.to_string())
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use axum::http::{HeaderValue, Request};
    use chrono::Utc;

    use super::*;
    use crate::security::{TokenClaims, TokenType};

    fn create_test_token() -> ValidatedToken {
        ValidatedToken {
            claims: TokenClaims {
                jti: "token123".to_string(),
                sub: "user456".to_string(),
                workspace: "workspace789".to_string(),
                permissions_hash: "perm_hash_xyz".to_string(),
                token_type: TokenType::Access,
                iat: Utc::now().timestamp(),
                exp: (Utc::now() + chrono::Duration::hours(1)).timestamp(),
                iss: "control-center".to_string(),
                aud: vec!["orchestrator".to_string()],
                metadata: Some({
                    let mut map = HashMap::new();
                    map.insert("permissions".to_string(), "read,write".to_string());
                    map
                }),
            },
            validated_at: Utc::now(),
            remaining_validity: 3600,
        }
    }

    #[test]
    fn test_security_context_from_token() {
        let token = create_test_token();
        let req = Request::builder()
            .header("X-MFA-Verified", "true")
            .header("X-Forwarded-For", "192.168.1.100")
            .header("User-Agent", "TestClient/1.0")
            .header("X-Request-ID", "req-123")
            .body(())
            .unwrap();

        let context = SecurityContext::from_token_and_request(token, &req);

        assert_eq!(context.user_id, "user456");
        assert_eq!(context.workspace, "workspace789");
        assert!(context.mfa_verified);
        assert_eq!(context.ip_address.to_string(), "192.168.1.100");
        assert_eq!(context.user_agent.as_deref(), Some("TestClient/1.0"));
        assert_eq!(context.request_id, "req-123");
    }

    #[test]
    fn test_permission_checks() {
        let token = create_test_token();
        let req = Request::builder().body(()).unwrap();

        let context = SecurityContext::from_token_and_request(token, &req);

        assert!(context.has_permission("read"));
        assert!(context.has_permission("write"));
        assert!(!context.has_permission("admin"));

        assert!(context.has_any_permission(&["read", "admin"]));
        assert!(!context.has_any_permission(&["admin", "delete"]));

        assert!(context.has_all_permissions(&["read", "write"]));
        assert!(!context.has_all_permissions(&["read", "write", "admin"]));
    }

    #[test]
    fn test_extract_ip_from_x_forwarded_for() {
        let req = Request::builder()
            .header("X-Forwarded-For", "10.0.0.1, 10.0.0.2")
            .body(())
            .unwrap();

        let ip = extract_ip_address(&req);
        assert_eq!(ip.to_string(), "10.0.0.1");
    }

    #[test]
    fn test_extract_ip_from_x_real_ip() {
        let req = Request::builder()
            .header("X-Real-IP", "172.16.0.1")
            .body(())
            .unwrap();

        let ip = extract_ip_address(&req);
        assert_eq!(ip.to_string(), "172.16.0.1");
    }

    #[test]
    fn test_extract_ip_default() {
        let req = Request::builder().body(()).unwrap();
        let ip = extract_ip_address(&req);
        assert_eq!(ip.to_string(), "127.0.0.1");
    }

    #[test]
    fn test_mfa_status_extraction() {
        let req_verified = Request::builder()
            .header("X-MFA-Verified", "true")
            .body(())
            .unwrap();
        assert!(extract_mfa_status(&req_verified));

        let req_not_verified = Request::builder()
            .header("X-MFA-Verified", "false")
            .body(())
            .unwrap();
        assert!(!extract_mfa_status(&req_not_verified));

        let req_missing = Request::builder().body(()).unwrap();
        assert!(!extract_mfa_status(&req_missing));
    }
}
