use std::net::SocketAddr;
use std::sync::Arc;

use axum::{
    extract::{ConnectInfo, Request, State},
    http::{header::AUTHORIZATION, StatusCode},
    middleware::Next,
    response::Response,
};
use tracing::{debug, warn};
use uuid::Uuid;

use crate::error::{auth, http, ControlCenterError, Result};
use crate::services::JwtService;

/// Authentication middleware that validates JWT tokens
pub async fn auth_middleware(
    State(jwt_service): State<Arc<JwtService>>,
    mut request: Request,
    next: Next,
) -> std::result::Result<Response, StatusCode> {
    // Extract Authorization header
    let auth_header = request
        .headers()
        .get(AUTHORIZATION)
        .and_then(|header| header.to_str().ok());

    let token = match auth_header {
        Some(header) if header.starts_with("Bearer ") => {
            header.strip_prefix("Bearer ").unwrap_or("")
        }
        _ => {
            debug!("Missing or invalid Authorization header");
            return Err(StatusCode::UNAUTHORIZED);
        }
    };

    // Verify the JWT token
    match jwt_service.verify_access_token(token) {
        Ok(token_data) => {
            // Extract IP address from ConnectInfo or X-Forwarded-For header (Phase 2)
            let ip_address = extract_ip_address(&request);

            // Extract approval ID from headers if present (Phase 2)
            let approval_id = request
                .headers()
                .get("X-Approval-ID")
                .and_then(|header| header.to_str().ok())
                .map(|s| s.to_string());

            // Add user information to request extensions for use in handlers
            let user_context = UserContext {
                user_id: Uuid::parse_str(&token_data.claims.sub)
                    .map_err(|_| StatusCode::UNAUTHORIZED)?,
                session_id: Uuid::parse_str(&token_data.claims.session_id)
                    .map_err(|_| StatusCode::UNAUTHORIZED)?,
                roles: token_data.claims.roles.clone(),
                mfa_verified: token_data.claims.mfa_verified.unwrap_or(false),
                ip_address,
                approval_id,
            };

            request.extensions_mut().insert(user_context);
            debug!(
                "Authentication successful for user: {}",
                token_data.claims.sub
            );

            Ok(next.run(request).await)
        }
        Err(e) => {
            warn!("Token verification failed: {}", e);
            Err(StatusCode::UNAUTHORIZED)
        }
    }
}

/// Solo mode authentication bypass middleware.
///
/// Injects a fixed local-admin `UserContext` for every request without
/// validating any JWT. Applied only when the service starts with `--mode solo`.
/// All handlers receive the same context — roles = ["admin"], mfa_verified =
/// true.
pub async fn solo_auth_middleware(
    mut request: Request,
    next: Next,
) -> std::result::Result<Response, StatusCode> {
    let user_context = UserContext {
        user_id: Uuid::nil(),
        session_id: Uuid::nil(),
        roles: vec!["admin".to_string()],
        mfa_verified: true,
        ip_address: Some("127.0.0.1".to_string()),
        approval_id: None,
    };
    request.extensions_mut().insert(user_context);
    Ok(next.run(request).await)
}

/// Optional authentication middleware that allows unauthenticated requests
pub async fn optional_auth_middleware(
    State(jwt_service): State<Arc<JwtService>>,
    mut request: Request,
    next: Next,
) -> Response {
    // Extract Authorization header
    let auth_header = request
        .headers()
        .get(AUTHORIZATION)
        .and_then(|header| header.to_str().ok());

    if let Some(header) = auth_header {
        if let Some(token) = header.strip_prefix("Bearer ") {
            // Try to verify the token, but don't fail if it's invalid
            if let Ok(token_data) = jwt_service.verify_access_token(token) {
                if let (Ok(user_id), Ok(session_id)) = (
                    Uuid::parse_str(&token_data.claims.sub),
                    Uuid::parse_str(&token_data.claims.session_id),
                ) {
                    // Extract IP and approval ID (Phase 2)
                    let ip_address = extract_ip_address(&request);
                    let approval_id = request
                        .headers()
                        .get("X-Approval-ID")
                        .and_then(|header| header.to_str().ok())
                        .map(|s| s.to_string());

                    let user_context = UserContext {
                        user_id,
                        session_id,
                        roles: token_data.claims.roles.clone(),
                        mfa_verified: token_data.claims.mfa_verified.unwrap_or(false),
                        ip_address,
                        approval_id,
                    };

                    request.extensions_mut().insert(user_context);
                    debug!(
                        "Optional authentication successful for user: {}",
                        token_data.claims.sub
                    );
                }
            }
        }
    }

    next.run(request).await
}

/// Extract IP address from request
///
/// Tries multiple sources:
/// 1. X-Forwarded-For header (for proxied requests)
/// 2. X-Real-IP header (for reverse proxies)
/// 3. ConnectInfo socket address
/// 4. Default to 127.0.0.1 if extraction fails
fn extract_ip_address(request: &Request) -> Option<String> {
    // Try X-Forwarded-For header first (proxy)
    if let Some(forwarded) = request
        .headers()
        .get("X-Forwarded-For")
        .and_then(|h| h.to_str().ok())
    {
        // X-Forwarded-For can have multiple IPs, get the first one
        if let Some(ip) = forwarded.split(',').next() {
            return Some(ip.trim().to_string());
        }
    }

    // Try X-Real-IP header (reverse proxy)
    if let Some(real_ip) = request
        .headers()
        .get("X-Real-IP")
        .and_then(|h| h.to_str().ok())
    {
        return Some(real_ip.to_string());
    }

    // Try to get from ConnectInfo in extensions (local connection)
    if let Some(ConnectInfo(addr)) = request.extensions().get::<ConnectInfo<SocketAddr>>() {
        return Some(addr.ip().to_string());
    }

    // Default to loopback if we can't determine
    Some("127.0.0.1".to_string())
}

/// Role-based authorization middleware
pub fn require_role(
    required_role: &'static str,
) -> impl Fn(
    Request,
    Next,
) -> std::pin::Pin<
    Box<dyn std::future::Future<Output = std::result::Result<Response, StatusCode>> + Send>,
> + Clone {
    move |request: Request, next: Next| {
        Box::pin(async move {
            let user_context = request
                .extensions()
                .get::<UserContext>()
                .ok_or(StatusCode::UNAUTHORIZED)?;

            if !user_context.has_role(required_role) {
                warn!(
                    "User {} lacks required role: {}",
                    user_context.user_id, required_role
                );
                return Err(StatusCode::FORBIDDEN);
            }

            debug!(
                "Role authorization successful for user: {} (role: {})",
                user_context.user_id, required_role
            );

            Ok(next.run(request).await)
        })
    }
}

/// Permission-based authorization middleware
pub fn require_permission(
    required_permission: &'static str,
) -> impl Fn(
    Request,
    Next,
) -> std::pin::Pin<
    Box<dyn std::future::Future<Output = std::result::Result<Response, StatusCode>> + Send>,
> + Clone {
    move |request: Request, next: Next| {
        Box::pin(async move {
            let user_context = request
                .extensions()
                .get::<UserContext>()
                .ok_or(StatusCode::UNAUTHORIZED)?;

            // Note: In a real implementation, you would check permissions from the database
            // This is a simplified version that assumes permissions are embedded in roles
            if !user_context.has_permission(required_permission) {
                warn!(
                    "User {} lacks required permission: {}",
                    user_context.user_id, required_permission
                );
                return Err(StatusCode::FORBIDDEN);
            }

            debug!(
                "Permission authorization successful for user: {} (permission: {})",
                user_context.user_id, required_permission
            );

            Ok(next.run(request).await)
        })
    }
}

/// User context extracted from JWT token
#[derive(Debug, Clone)]
pub struct UserContext {
    pub user_id: Uuid,
    pub session_id: Uuid,
    pub roles: Vec<String>,
    pub mfa_verified: bool,
    /// IP address of the client (added in Phase 2)
    pub ip_address: Option<String>,
    /// Approval ID for requests requiring approval (added in Phase 2)
    pub approval_id: Option<String>,
}

/// Alias for UserContext (used in secrets management)
pub type SecurityContext = UserContext;

impl UserContext {
    /// Check if user has a specific role
    pub fn has_role(&self, role: &str) -> bool {
        self.roles.contains(&role.to_string())
    }

    /// Check if user has any of the specified roles
    pub fn has_any_role(&self, roles: &[&str]) -> bool {
        roles.iter().any(|role| self.has_role(role))
    }

    /// Check if user has admin role
    pub fn is_admin(&self) -> bool {
        self.has_role("admin")
    }

    /// Simple permission check based on role
    /// In a real implementation, this would check against a permission database
    pub fn has_permission(&self, permission: &str) -> bool {
        match permission {
            // Admin has all permissions
            _ if self.is_admin() => true,

            // User permissions
            "profile:read" | "profile:write" => self.has_role("user"),

            // Operator permissions
            "users:read" | "roles:read" | "permissions:read" | "system:read"
                if self.has_role("operator") =>
            {
                true
            }

            // Admin-only permissions
            "users:write" | "users:delete" | "roles:write" | "roles:delete" | "system:manage"
                if self.is_admin() =>
            {
                true
            }

            _ => false,
        }
    }
}

/// Extract user context from request extensions
pub trait RequestExt {
    fn user_context(&self) -> Option<&UserContext>;
    fn require_user_context(&self) -> Result<&UserContext>;
}

impl RequestExt for Request {
    fn user_context(&self) -> Option<&UserContext> {
        self.extensions().get::<UserContext>()
    }

    fn require_user_context(&self) -> Result<&UserContext> {
        self.user_context()
            .ok_or(ControlCenterError::Auth(auth::AuthError::Unauthorized(
                "No user context found".to_string(),
            )))
    }
}

#[cfg(test)]
mod tests {
    use axum::{
        body::Body,
        http::{HeaderValue, Request},
    };
    use uuid::Uuid;

    use super::*;
    use crate::services::JwtService;
    use crate::simple_config::JwtConfig;

    #[tokio::test]
    async fn test_user_context_permissions() {
        let user_context = UserContext {
            user_id: Uuid::new_v4(),
            session_id: Uuid::new_v4(),
            roles: vec!["user".to_string()],
            mfa_verified: false,
            ip_address: Some("192.168.1.1".to_string()),
            approval_id: None,
        };

        assert!(user_context.has_role("user"));
        assert!(!user_context.has_role("admin"));
        assert!(user_context.has_permission("profile:read"));
        assert!(user_context.has_permission("profile:write"));
        assert!(!user_context.has_permission("users:read"));

        let admin_context = UserContext {
            user_id: Uuid::new_v4(),
            session_id: Uuid::new_v4(),
            roles: vec!["admin".to_string()],
            mfa_verified: true,
            ip_address: Some("10.0.0.1".to_string()),
            approval_id: Some("approval-123".to_string()),
        };

        assert!(admin_context.is_admin());
        assert!(admin_context.has_permission("users:write"));
        assert!(admin_context.has_permission("system:manage"));
    }

    fn create_test_jwt_config() -> JwtConfig {
        use crate::services::jwt::generate_rsa_key_pair;
        let keys = generate_rsa_key_pair().expect("Failed to generate test keys");
        JwtConfig {
            issuer: "test-issuer".to_string(),
            audience: "test-audience".to_string(),
            access_token_expiration_hours: 1,
            refresh_token_expiration_hours: 24,
            private_key_pem: keys.private_key_pem,
            public_key_pem: keys.public_key_pem,
        }
    }

    #[tokio::test]
    async fn test_auth_header_parsing() {
        let jwt_service = Arc::new(
            JwtService::new(create_test_jwt_config())
                .expect("Failed to create JWT service for test"),
        );

        let user_id = Uuid::new_v4();
        let session_id = Uuid::new_v4();
        let roles = vec!["user".to_string()];

        let token = jwt_service
            .generate_access_token(user_id, session_id, roles)
            .expect("Failed to generate access token for test");

        // Test valid Bearer token
        let mut request = Request::builder()
            .header(AUTHORIZATION, format!("Bearer {}", token))
            .body(Body::empty())
            .expect("Failed to build request for test");

        // Verify token parsing would work
        let auth_header = request
            .headers()
            .get(AUTHORIZATION)
            .and_then(|header| header.to_str().ok());

        let extracted_token = auth_header.and_then(|header| header.strip_prefix("Bearer "));

        assert!(extracted_token.is_some());
        assert_eq!(extracted_token.unwrap(), token);
    }
}
