//! Enhanced authorization middleware with Cedar policy evaluation
//!
//! This middleware:
//! 1. Extracts security context from request
//! 2. Builds Cedar authorization request from HTTP method and path
//! 3. Evaluates Cedar policies with context (MFA, IP, time, workspace)
//! 4. Logs authorization decisions to audit log
//! 5. Allows or denies request based on policy evaluation

use std::collections::HashMap;
use std::sync::Arc;

use axum::{
    extract::{Request, State},
    http::{Method, StatusCode},
    middleware::Next,
    response::{IntoResponse, Response},
};
use chrono::Utc;
use tracing::{debug, warn};

use crate::audit::{ActionInfo, ActionType, AuditEvent, AuditLogger, AuthorizationInfo, UserInfo};
use crate::middleware::security_context::SecurityContext;
use crate::security::{
    Action, AuthorizationContext, AuthorizationRequest, CedarEngine, Principal, Resource,
};

/// Authorization middleware state
#[derive(Clone)]
pub struct AuthzMiddleware {
    /// Cedar authorization engine
    cedar_engine: Arc<CedarEngine>,

    /// Audit logger for authorization decisions
    audit_logger: Arc<AuditLogger>,

    /// Enable authorization (can be disabled for development)
    enabled: bool,
}

impl AuthzMiddleware {
    /// Create new authorization middleware
    pub fn new(cedar_engine: Arc<CedarEngine>, audit_logger: Arc<AuditLogger>) -> Self {
        Self {
            cedar_engine,
            audit_logger,
            enabled: true,
        }
    }

    /// Create authorization middleware with custom enabled flag
    pub fn with_enabled(
        cedar_engine: Arc<CedarEngine>,
        audit_logger: Arc<AuditLogger>,
        enabled: bool,
    ) -> Self {
        Self {
            cedar_engine,
            audit_logger,
            enabled,
        }
    }

    /// Create disabled authorization middleware (development only)
    pub fn disabled(audit_logger: Arc<AuditLogger>) -> Self {
        // Create minimal Cedar engine (won't be used when disabled)
        let cedar_engine = Arc::new(CedarEngine::new());

        Self {
            cedar_engine,
            audit_logger,
            enabled: false,
        }
    }

    /// Check if authorization is enabled
    pub fn is_enabled(&self) -> bool {
        self.enabled
    }
}

/// Authorization middleware handler
pub async fn authz_middleware(
    State(authz): State<Arc<AuthzMiddleware>>,
    request: Request,
    next: Next,
) -> Result<Response, AuthzError> {
    // Skip authorization if disabled
    if !authz.is_enabled() {
        debug!("Authorization disabled, skipping policy evaluation");
        return Ok(next.run(request).await);
    }

    // Get security context from extensions (injected by auth middleware)
    let security_context = request
        .extensions()
        .get::<SecurityContext>()
        .ok_or(AuthzError::MissingSecurityContext)?
        .clone();

    // Build Cedar authorization request
    let principal = Principal::User {
        id: security_context.user_id.clone(),
        email: format!("{}@unknown", security_context.user_id), // Would come from token metadata
        username: security_context.user_id.clone(),
        teams: vec![], // Would come from token metadata
    };

    let (resource, workspace) =
        extract_resource_from_path(request.uri().path(), &security_context.workspace);
    let action = extract_action_from_method(request.method());

    // Build authorization context with additional metadata
    let mut additional = HashMap::new();
    additional.insert("workspace".to_string(), serde_json::json!(workspace));

    let context = AuthorizationContext {
        mfa_verified: security_context.mfa_verified,
        ip_address: security_context.ip_address.to_string(),
        time: Utc::now().to_rfc3339(),
        approval_id: None,
        reason: None,
        force: false,
        additional,
    };

    debug!(
        user = %security_context.user_id,
        resource = ?resource,
        action = ?action,
        workspace = %security_context.workspace,
        mfa_verified = %security_context.mfa_verified,
        "Evaluating Cedar authorization policies"
    );

    // Evaluate Cedar policies
    let auth_request = AuthorizationRequest {
        principal,
        action: action.clone(),
        resource: resource.clone(),
        context,
    };

    let decision = authz
        .cedar_engine
        .authorize(&auth_request)
        .await
        .map_err(|e| AuthzError::EvaluationError(e.to_string()))?;

    // Check if access is allowed
    let allowed = matches!(
        decision.decision,
        crate::security::AuthorizationDecision::Allow
    );

    // Log authorization decision
    log_authorization_decision(
        &authz.audit_logger,
        &security_context,
        &action,
        &resource,
        &decision,
        request.method(),
        request.uri().path(),
    )
    .await;

    if !allowed {
        warn!(
            user = %security_context.user_id,
            resource = ?resource,
            action = ?action,
            "Authorization denied"
        );
        return Err(AuthzError::Forbidden("Policy denied access".to_string()));
    }

    debug!(
        user = %security_context.user_id,
        resource = ?resource,
        action = ?action,
        "Authorization granted"
    );

    // Continue to next middleware/handler
    Ok(next.run(request).await)
}

/// Extract resource identifier from request path
fn extract_resource_from_path(path: &str, workspace: &str) -> (Resource, String) {
    let parts: Vec<&str> = path.trim_matches('/').split('/').collect();

    // Extract resource type and ID from path
    // Format: /api/v1/{resource_type}/{resource_id}
    let (resource, resource_workspace) = if parts.len() >= 3 {
        let resource_type = parts[2]; // Skip "api" and "v1"
        let resource_id = if parts.len() > 3 {
            parts[3].to_string()
        } else {
            "*".to_string()
        };

        match resource_type {
            "servers" | "server" => (
                Resource::Server {
                    id: resource_id.clone(),
                    hostname: resource_id,
                    workspace: workspace.to_string(),
                    environment: "production".to_string(), // Would be extracted from path or config
                },
                workspace.to_string(),
            ),
            "taskserv" | "taskservs" => (
                Resource::Taskserv {
                    id: resource_id.clone(),
                    name: resource_id,
                    workspace: workspace.to_string(),
                    environment: "production".to_string(),
                },
                workspace.to_string(),
            ),
            "cluster" | "clusters" => (
                Resource::Cluster {
                    id: resource_id.clone(),
                    name: resource_id,
                    workspace: workspace.to_string(),
                    environment: "production".to_string(),
                    node_count: 3, // Default, would be loaded from metadata
                },
                workspace.to_string(),
            ),
            "workflows" | "workflow" | "batch" => (
                Resource::Workflow {
                    id: resource_id.clone(),
                    workflow_type: resource_type.to_string(),
                    workspace: workspace.to_string(),
                    environment: "production".to_string(),
                },
                workspace.to_string(),
            ),
            _ => (
                Resource::Workspace {
                    id: workspace.to_string(),
                    name: workspace.to_string(),
                    environment: "production".to_string(),
                    owner_id: "unknown".to_string(),
                },
                workspace.to_string(),
            ),
        }
    } else {
        (
            Resource::Workspace {
                id: workspace.to_string(),
                name: workspace.to_string(),
                environment: "production".to_string(),
                owner_id: "unknown".to_string(),
            },
            workspace.to_string(),
        )
    };

    (resource, resource_workspace)
}

/// Extract action from HTTP method
fn extract_action_from_method(method: &Method) -> Action {
    match method {
        &Method::GET => Action::Read,
        &Method::POST => Action::Create,
        &Method::PUT | &Method::PATCH => Action::Update,
        &Method::DELETE => Action::Delete,
        _ => Action::Unknown(method.to_string()),
    }
}

/// Log authorization decision to audit log
async fn log_authorization_decision(
    audit_logger: &Arc<AuditLogger>,
    security_context: &SecurityContext,
    action: &Action,
    resource: &Resource,
    decision: &crate::security::AuthorizationResult,
    http_method: &Method,
    http_path: &str,
) {
    // Build action type from action and resource
    let action_type = map_action_to_audit_type(action, resource);

    // Build user info
    let user = UserInfo {
        id: security_context.user_id.clone(),
        name: security_context.user_id.clone(), // Would be loaded from user service
        ip: security_context.ip_address.to_string(),
        user_agent: security_context.user_agent.clone().unwrap_or_default(),
        email: None, // Would be loaded from user service
        session_id: security_context.session_id.clone(),
    };

    // Build action info
    let mut tags = std::collections::HashMap::new();
    tags.insert("workspace".to_string(), security_context.workspace.clone());
    tags.insert("mfa".to_string(), security_context.mfa_verified.to_string());

    let action_info = ActionInfo {
        action_type,
        resource: format!("{:?}", resource),
        workspace: security_context.workspace.clone(),
        parameters: serde_json::json!({
            "method": http_method.to_string(),
            "path": http_path,
            "cedar_action": format!("{:?}", action),
        }),
        tags,
    };

    // Build authorization info
    let authorization = AuthorizationInfo {
        token_id: security_context.token.claims.jti.clone(),
        cedar_policy: decision.policies.join(", "),
        mfa_verified: security_context.mfa_verified,
        permissions: security_context.permissions.clone(),
        evaluation_duration_us: None, // Not available in AuthorizationResult
    };

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

    // Mark as failed if not allowed
    let allowed = matches!(
        decision.decision,
        crate::security::AuthorizationDecision::Allow
    );
    if !allowed {
        event = event.fail(0, decision.diagnostics.join("; "));
    } else {
        event = event.complete(0);
    }

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

/// Map Cedar action and resource to audit action type
fn map_action_to_audit_type(action: &Action, resource: &Resource) -> ActionType {
    match (action, resource) {
        (Action::Create, Resource::Server { .. }) => ActionType::ServerCreate,
        (Action::Delete, Resource::Server { .. }) => ActionType::ServerDelete,
        (Action::Update, Resource::Server { .. }) => ActionType::ServerUpdate,
        (Action::Read | Action::List, Resource::Server { .. }) => ActionType::ServerList,

        (Action::Create, Resource::Taskserv { .. }) => ActionType::TaskservCreate,
        (Action::Delete, Resource::Taskserv { .. }) => ActionType::TaskservDelete,
        (Action::Update, Resource::Taskserv { .. }) => ActionType::TaskservUpdate,
        (Action::Read | Action::List, Resource::Taskserv { .. }) => ActionType::TaskservList,

        (Action::Create, Resource::Cluster { .. }) => ActionType::ClusterCreate,
        (Action::Delete, Resource::Cluster { .. }) => ActionType::ClusterDelete,
        (Action::Update, Resource::Cluster { .. }) => ActionType::ClusterUpdate,
        (Action::Read | Action::List, Resource::Cluster { .. }) => ActionType::ClusterList,

        (Action::Create | Action::Deploy, Resource::Workflow { .. }) => ActionType::WorkflowSubmit,
        (Action::Delete, Resource::Workflow { .. }) => ActionType::WorkflowCancel,
        (Action::Read | Action::List, Resource::Workflow { .. }) => ActionType::WorkflowList,
        (Action::Rollback, Resource::Workflow { .. }) => ActionType::WorkflowRollback,

        _ => ActionType::Unknown,
    }
}

/// Authorization error responses
#[derive(Debug)]
pub enum AuthzError {
    MissingSecurityContext,
    EvaluationError(String),
    Forbidden(String),
}

impl IntoResponse for AuthzError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            AuthzError::MissingSecurityContext => (
                StatusCode::INTERNAL_SERVER_ERROR,
                "Security context not found. Ensure authentication middleware runs first."
                    .to_string(),
            ),
            AuthzError::EvaluationError(e) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Authorization policy evaluation failed: {}", e),
            ),
            AuthzError::Forbidden(reason) => {
                (StatusCode::FORBIDDEN, format!("Access denied: {}", reason))
            }
        };

        (status, message).into_response()
    }
}

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

    #[test]
    fn test_extract_resource_from_path() {
        let (resource, _) = extract_resource_from_path("/api/v1/servers/srv-123", "default");
        assert!(matches!(resource, Resource::Server { .. }));

        let (resource, _) = extract_resource_from_path("/api/v1/taskserv/kubernetes", "default");
        assert!(matches!(resource, Resource::Taskserv { .. }));

        let (resource, _) = extract_resource_from_path("/api/v1/cluster/prod-cluster", "default");
        assert!(matches!(resource, Resource::Cluster { .. }));

        let (resource, _) = extract_resource_from_path("/api/v1/workflows/wf-456", "default");
        assert!(matches!(resource, Resource::Workflow { .. }));
    }

    #[test]
    fn test_extract_action_from_method() {
        assert!(matches!(
            extract_action_from_method(&Method::GET),
            Action::Read
        ));
        assert!(matches!(
            extract_action_from_method(&Method::POST),
            Action::Create
        ));
        assert!(matches!(
            extract_action_from_method(&Method::PUT),
            Action::Update
        ));
        assert!(matches!(
            extract_action_from_method(&Method::PATCH),
            Action::Update
        ));
        assert!(matches!(
            extract_action_from_method(&Method::DELETE),
            Action::Delete
        ));
    }

    #[test]
    fn test_map_action_to_audit_type() {
        let server_resource = Resource::Server {
            id: "srv-1".to_string(),
            hostname: "srv-1".to_string(),
            workspace: "default".to_string(),
            environment: "production".to_string(),
        };

        assert_eq!(
            map_action_to_audit_type(&Action::Create, &server_resource),
            ActionType::ServerCreate
        );

        let taskserv_resource = Resource::Taskserv {
            id: "k8s".to_string(),
            name: "kubernetes".to_string(),
            workspace: "default".to_string(),
            environment: "production".to_string(),
        };

        assert_eq!(
            map_action_to_audit_type(&Action::Delete, &taskserv_resource),
            ActionType::TaskservDelete
        );

        let cluster_resource = Resource::Cluster {
            id: "prod".to_string(),
            name: "prod-cluster".to_string(),
            workspace: "default".to_string(),
            environment: "production".to_string(),
            node_count: 3,
        };

        assert_eq!(
            map_action_to_audit_type(&Action::Update, &cluster_resource),
            ActionType::ClusterUpdate
        );
    }
}
