//! Cedar Policy-Based Authorization
//!
//! Integrates Cedar policy engine for fine-grained access control to secrets.
//! Enforces workspace isolation, MFA requirements, and role-based policies.

use serde::{Deserialize, Serialize};
use tracing::{debug, warn};
use uuid::Uuid;

use crate::error::{auth, ControlCenterError, Result};

/// Cedar authorization decision
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AuthorizationDecision {
    /// Action is allowed
    Allow,
    /// Action is denied
    Deny,
}

impl std::fmt::Display for AuthorizationDecision {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Allow => write!(f, "Allow"),
            Self::Deny => write!(f, "Deny"),
        }
    }
}

/// Secret access context for authorization
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecretAccessContext {
    /// Workspace ID where secret resides
    pub workspace_id: String,
    /// Secret domain (postgres, redis, web-api, ssh, etc.)
    pub domain: String,
    /// Secret type (database, application, ssh, provider)
    pub secret_type: String,
    /// Secret tags for tag-based access control
    pub tags: Vec<String>,
    /// Whether secret is expired
    pub is_expired: bool,
    /// TTL in hours (0 = permanent)
    pub ttl_hours: i64,
    /// Whether auto-rotation is enabled
    pub auto_rotate: bool,
}

/// Authorization request for secret operations
#[derive(Debug, Clone)]
pub struct SecretAuthorizationRequest {
    /// Principal user ID
    pub user_id: Uuid,
    /// User's roles
    pub roles: Vec<String>,
    /// User's team memberships
    pub teams: Vec<String>,
    /// Secret access context
    pub secret_context: SecretAccessContext,
    /// Action being performed (access, create, update, delete, rotate, renew)
    pub action: String,
    /// MFA verified for this user
    pub mfa_verified: bool,
    /// User's IP address
    pub ip_address: String,
    /// Timestamp of request (ISO 8601)
    pub time: String,
    /// Approval ID if applicable
    pub approval_id: Option<String>,
}

/// Cedar authorization engine for secrets
///
/// Cached policy decisions (in production, use Cedar engine directly).
/// For now, we implement simplified rules that match the Cedar policies.
pub struct SecretCedarAuthz {}

impl SecretCedarAuthz {
    /// Create new Cedar authorizer
    pub fn new() -> Self {
        Self {}
    }

    /// Check if action is authorized on secret
    ///
    /// Returns:
    /// - Ok(()) if authorized
    /// - Err(ControlCenterError) if denied
    pub async fn authorize(&self, request: &SecretAuthorizationRequest) -> Result<()> {
        debug!(
            user_id = %request.user_id,
            action = %request.action,
            workspace = %request.secret_context.workspace_id,
            "Authorizing secret access"
        );

        let decision = self.evaluate_policies(request).await?;

        match decision {
            AuthorizationDecision::Allow => {
                debug!(
                    user_id = %request.user_id,
                    action = %request.action,
                    workspace = %request.secret_context.workspace_id,
                    "Secret authorization granted"
                );
                Ok(())
            }
            AuthorizationDecision::Deny => {
                warn!(
                    user_id = %request.user_id,
                    action = %request.action,
                    workspace = %request.secret_context.workspace_id,
                    "Secret authorization denied"
                );
                Err(ControlCenterError::Auth(auth::forbidden(
                    "Secret access denied by policy",
                )))
            }
        }
    }

    /// Evaluate Cedar policies for secret access
    async fn evaluate_policies(
        &self,
        request: &SecretAuthorizationRequest,
    ) -> Result<AuthorizationDecision> {
        // Phase 1: Development environment - relaxed access
        if request.is_in_development() {
            return self.evaluate_development_policies(request);
        }

        // Phase 1: Production environment - strict policies
        if request.is_in_production() {
            return self.evaluate_production_policies(request);
        }

        // Default: allow if no explicit policy applies
        Ok(AuthorizationDecision::Allow)
    }

    /// Evaluate development environment policies
    fn evaluate_development_policies(
        &self,
        request: &SecretAuthorizationRequest,
    ) -> Result<AuthorizationDecision> {
        // Developers can access development secrets
        if request.is_developer() {
            match request.action.as_str() {
                "access" | "read" | "create" | "update" | "rotate" => {
                    return Ok(AuthorizationDecision::Allow)
                }
                _ => {}
            }
        }

        Ok(AuthorizationDecision::Deny)
    }

    /// Evaluate production environment policies
    fn evaluate_production_policies(
        &self,
        request: &SecretAuthorizationRequest,
    ) -> Result<AuthorizationDecision> {
        // Check action-specific rules
        match request.action.as_str() {
            "access" => self.check_access_policy(request),
            "create" => self.check_create_policy(request),
            "update" => self.check_update_policy(request),
            "delete" => self.check_delete_policy(request),
            "rotate" => self.check_rotate_policy(request),
            "renew" => self.check_renew_policy(request),
            "read" | "list" => Ok(AuthorizationDecision::Allow),
            _ => Ok(AuthorizationDecision::Deny),
        }
    }

    /// Check access policy (retrieve secret value)
    fn check_access_policy(
        &self,
        request: &SecretAuthorizationRequest,
    ) -> Result<AuthorizationDecision> {
        // Production secret access requires MFA
        if request.secret_context.is_expired {
            return Ok(AuthorizationDecision::Deny);
        }

        // MFA required for production access
        if !request.mfa_verified {
            return Ok(AuthorizationDecision::Deny);
        }

        // Check domain-based access
        if self.check_domain_access(request) {
            return Ok(AuthorizationDecision::Allow);
        }

        // Check tag-based access
        if self.check_tag_access(request) {
            return Ok(AuthorizationDecision::Allow);
        }

        // Check workspace membership
        if self.check_workspace_membership(request) {
            return Ok(AuthorizationDecision::Allow);
        }

        Ok(AuthorizationDecision::Deny)
    }

    /// Check creation policy
    fn check_create_policy(
        &self,
        request: &SecretAuthorizationRequest,
    ) -> Result<AuthorizationDecision> {
        // Production creation requires MFA and approval
        if !request.mfa_verified || request.approval_id.is_none() {
            return Ok(AuthorizationDecision::Deny);
        }

        // Check TTL limits (max 7 days = 168 hours in production)
        if request.secret_context.ttl_hours > 168 {
            return Ok(AuthorizationDecision::Deny);
        }

        Ok(AuthorizationDecision::Allow)
    }

    /// Check update policy
    fn check_update_policy(
        &self,
        request: &SecretAuthorizationRequest,
    ) -> Result<AuthorizationDecision> {
        // Production updates require MFA
        if !request.mfa_verified {
            return Ok(AuthorizationDecision::Deny);
        }

        Ok(AuthorizationDecision::Allow)
    }

    /// Check delete policy
    fn check_delete_policy(
        &self,
        request: &SecretAuthorizationRequest,
    ) -> Result<AuthorizationDecision> {
        // Only admins can delete production secrets
        if !request.has_role("admin") {
            return Ok(AuthorizationDecision::Deny);
        }

        // Require MFA and approval
        if !request.mfa_verified || request.approval_id.is_none() {
            return Ok(AuthorizationDecision::Deny);
        }

        Ok(AuthorizationDecision::Allow)
    }

    /// Check rotate policy
    fn check_rotate_policy(
        &self,
        request: &SecretAuthorizationRequest,
    ) -> Result<AuthorizationDecision> {
        // Auto-rotated secrets can be rotated by automation
        if request.secret_context.auto_rotate && request.is_automation() {
            return Ok(AuthorizationDecision::Allow);
        }

        // Manual rotation requires approval and MFA
        if !request.mfa_verified || request.approval_id.is_none() {
            return Ok(AuthorizationDecision::Deny);
        }

        Ok(AuthorizationDecision::Allow)
    }

    /// Check renew policy
    fn check_renew_policy(
        &self,
        request: &SecretAuthorizationRequest,
    ) -> Result<AuthorizationDecision> {
        // Renewal requires MFA for production
        if !request.mfa_verified {
            return Ok(AuthorizationDecision::Deny);
        }

        Ok(AuthorizationDecision::Allow)
    }

    /// Check domain-based access control
    fn check_domain_access(&self, request: &SecretAuthorizationRequest) -> bool {
        let domain = &request.secret_context.domain;

        // Database access control
        if matches!(
            domain.as_str(),
            "postgres" | "mysql" | "redis" | "mongodb" | "elasticsearch"
        ) {
            return request.has_role("database_admin");
        }

        // SSH access control
        if domain == "ssh" {
            return request.has_role("infrastructure");
        }

        // Application secrets
        if matches!(
            domain.as_str(),
            "web-api" | "backend" | "mobile-api" | "integration-api"
        ) {
            return request.is_app_developer();
        }

        false
    }

    /// Check tag-based access control
    fn check_tag_access(&self, request: &SecretAuthorizationRequest) -> bool {
        let tags = &request.secret_context.tags;

        // Critical secrets require security admin
        if tags.contains(&"critical".to_string()) {
            return request.has_role("security_admin");
        }

        // Legacy secrets restricted to legacy support team
        if tags.contains(&"legacy".to_string()) {
            return request.is_in_team("legacy_support");
        }

        // Deprecated secrets should be denied
        if tags.contains(&"deprecated".to_string()) {
            return false;
        }

        true
    }

    /// Check workspace membership
    fn check_workspace_membership(&self, _request: &SecretAuthorizationRequest) -> bool {
        // For Phase 1: Workspace filtering done at API layer
        // Cedar policy enforces defense-in-depth
        // Users are assumed to be workspace members if they reach this point
        true
    }
}

/// Extension methods for authorization context
impl SecretAuthorizationRequest {
    /// Check if user is a developer
    fn is_developer(&self) -> bool {
        self.is_in_team("developers")
    }

    /// Check if user is in automation team
    fn is_automation(&self) -> bool {
        self.is_in_team("automation")
    }

    /// Check if user is an app developer
    fn is_app_developer(&self) -> bool {
        self.is_in_team("app_developers")
    }

    /// Check if user is in specified team
    fn is_in_team(&self, team: &str) -> bool {
        self.teams.iter().any(|t| t == team)
    }

    /// Check if user has role
    fn has_role(&self, role: &str) -> bool {
        self.roles.iter().any(|r| r == role)
    }

    /// Check if user is in development environment
    fn is_in_development(&self) -> bool {
        self.secret_context.workspace_id.contains("dev")
            || self.secret_context.workspace_id == "development"
    }

    /// Check if user is in production environment
    fn is_in_production(&self) -> bool {
        self.secret_context.workspace_id.contains("prod")
            || self.secret_context.workspace_id == "production"
    }
}

impl Default for SecretCedarAuthz {
    fn default() -> Self {
        Self::new()
    }
}

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

    fn create_test_request(
        workspace: &str,
        action: &str,
        mfa: bool,
        roles: Vec<&str>,
    ) -> SecretAuthorizationRequest {
        SecretAuthorizationRequest {
            user_id: Uuid::new_v4(),
            roles: roles.iter().map(|s| s.to_string()).collect(),
            teams: vec![],
            secret_context: SecretAccessContext {
                workspace_id: workspace.to_string(),
                domain: "postgres".to_string(),
                secret_type: "database".to_string(),
                tags: vec![],
                is_expired: false,
                ttl_hours: 0,
                auto_rotate: false,
            },
            action: action.to_string(),
            mfa_verified: mfa,
            ip_address: "127.0.0.1".to_string(),
            time: chrono::Utc::now().to_rfc3339(),
            approval_id: Some("approval-123".to_string()),
        }
    }

    #[tokio::test]
    async fn test_dev_access_allowed() {
        let authz = SecretCedarAuthz::new();
        let mut request = create_test_request("development", "access", false, vec!["developer"]);
        request.teams = vec!["developers".to_string()];
        request.secret_context.workspace_id = "dev-workspace".to_string();

        let result = authz.evaluate_policies(&request).await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_prod_access_requires_mfa() {
        let authz = SecretCedarAuthz::new();
        let request = create_test_request("production", "access", false, vec!["user"]);

        let decision = authz
            .evaluate_production_policies(&request)
            .expect("decision should succeed");

        assert_eq!(decision, AuthorizationDecision::Deny);
    }

    #[tokio::test]
    async fn test_prod_access_with_mfa() {
        let authz = SecretCedarAuthz::new();
        let request = create_test_request("production", "access", true, vec!["user"]);

        let decision = authz
            .evaluate_production_policies(&request)
            .expect("decision should succeed");

        // Should allow (workspace membership check passes)
        assert_eq!(decision, AuthorizationDecision::Allow);
    }

    #[tokio::test]
    async fn test_admin_full_access() {
        let authz = SecretCedarAuthz::new();
        let request = create_test_request("production", "delete", true, vec!["admin"]);

        let decision = authz
            .check_delete_policy(&request)
            .expect("decision should succeed");
        assert_eq!(decision, AuthorizationDecision::Allow);
    }
}
