//! Cedar Policy Engine Implementation
//!
//! Core policy evaluation engine with context injection, hooks, and caching.

use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Arc;

use cedar_policy::{
    Authorizer, Context, Decision, Entities, EntityId, EntityTypeName, EntityUid, Policy, PolicyId,
    PolicySet, Request, Response,
};
use tokio::sync::RwLock;
use tracing::{debug, error, info, warn};
use uuid::Uuid;

use super::{
    Action, PolicyDecision, PolicyMetadata, PolicyRequestContext, PolicyResult, Principal, Resource,
};
use crate::config::ControlCenterConfig;
use crate::error::{infrastructure, policy, ControlCenterError, Result};
use crate::policies::{ContextBuilder, PolicyHookManager, PolicyValidator};
use crate::storage::PolicyStorage;

/// Main Policy Engine
pub struct PolicyEngine {
    config: ControlCenterConfig,
    authorizer: Authorizer,
    policy_set: Arc<RwLock<PolicySet>>,
    policy_cache: Arc<RwLock<HashMap<String, (PolicySet, chrono::DateTime<chrono::Utc>)>>>,
    storage: Arc<dyn PolicyStorage>,
    hook_manager: PolicyHookManager,
    validator: PolicyValidator,
    context_builder: ContextBuilder,
}

impl PolicyEngine {
    /// Create new policy engine instance
    pub async fn new(config: ControlCenterConfig) -> Result<Self> {
        let authorizer = Authorizer::new();
        let policy_set = Arc::new(RwLock::new(PolicySet::new()));
        let policy_cache = Arc::new(RwLock::new(HashMap::new()));

        // Initialize storage backend
        // Convert to simple Config for storage initialization
        use crate::simple_config::{Config, DatabaseConfig};
        let simple_config = Config {
            server: crate::simple_config::ServerConfig {
                host: "127.0.0.1".to_string(),
                port: 8080,
                workers: Some(4),
                keep_alive: None,
                max_connections: None,
            },
            database: DatabaseConfig {
                url: "ws://localhost:8000".to_string(),
                namespace: "provisioning".to_string(),
                database: "platform".to_string(),
                username: None,
                password: None,
            },
            jwt: Default::default(),
            rate_limiting: Default::default(),
            cors: Default::default(),
            security: Default::default(),
            logging: Default::default(),
        };
        let storage = crate::storage::create_policy_storage(&simple_config).await?;

        // Initialize managers
        let hook_manager = PolicyHookManager::new(&config.policies.hooks);
        let validator = PolicyValidator::new(&config.policies);
        let context_builder = ContextBuilder::new();

        let mut engine = Self {
            config,
            authorizer,
            policy_set,
            policy_cache,
            storage,
            hook_manager,
            validator,
            context_builder,
        };

        // Load policies from storage
        engine.load_policies().await?;

        info!("Policy engine initialized successfully");
        Ok(engine)
    }

    /// Evaluate policy for given request context
    pub async fn evaluate(&self, request_context: &PolicyRequestContext) -> Result<PolicyResult> {
        debug!("Evaluating policy for request: {:?}", request_context);

        // Execute pre-execution hooks
        self.hook_manager.execute_pre_hooks(request_context).await?;

        // Build Cedar request
        let cedar_request = self.build_cedar_request(request_context).await?;

        // Get current policy set
        let policy_set = self.get_policy_set().await?;

        // Perform authorization
        let response = self.authorizer.is_authorized(
            &cedar_request,
            &policy_set,
            &self.build_entities(request_context).await?,
        );

        // Convert response to our format
        let mut result = self.convert_response(response, request_context).await?;

        // Execute post-execution hooks
        self.hook_manager
            .execute_post_hooks(request_context, &result)
            .await?;

        // Handle policy violations
        if matches!(result.decision, PolicyDecision::Deny) {
            self.hook_manager
                .execute_violation_hooks(request_context, &result)
                .await?;
        }

        debug!("Policy evaluation result: {:?}", result.decision);
        Ok(result)
    }

    /// Load policies from configured directory and storage
    pub async fn load_policies(&mut self) -> Result<()> {
        info!(
            "Loading policies from directory: {:?}",
            self.config.policies.policy_dir
        );

        let mut policy_set = PolicySet::new();
        let mut loaded_count = 0;

        // Load from file system
        if self.config.policies.policy_dir.exists() {
            loaded_count += self
                .load_policies_from_directory(&mut policy_set, &self.config.policies.policy_dir)
                .await?;
        }

        // Load from storage backend
        let stored_policies = self.storage.list_policies().await?;
        for metadata in stored_policies {
            if !metadata.enabled {
                continue;
            }

            let policy_content = match self.storage.get_policy(&metadata.id).await {
                Ok(content) => content,
                Err(_) => continue,
            };

            match Policy::parse(Some(PolicyId::new(metadata.id.clone())), &policy_content) {
                Ok(policy) => {
                    policy_set.add(policy).map_err(|e| {
                        ControlCenterError::Policy(policy::PolicyError::Parsing(e.to_string()))
                    })?;
                    loaded_count += 1;
                }
                Err(e) => {
                    error!("Failed to parse policy {}: {}", metadata.id, e);
                }
            }
        }

        // Update policy set
        *self.policy_set.write().await = policy_set;

        info!("Loaded {} policies successfully", loaded_count);
        Ok(())
    }

    /// Load policies from directory recursively
    async fn load_policies_from_directory(
        &self,
        policy_set: &mut PolicySet,
        dir: &Path,
    ) -> Result<usize> {
        let mut count = 0;

        for entry in std::fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();

            if path.is_dir() {
                // Use Box::pin for recursive async calls to avoid infinite future size
                count += Box::pin(self.load_policies_from_directory(policy_set, &path)).await?;
            } else if path.extension().and_then(|s| s.to_str()) == Some("cedar") {
                count += self.load_policy_file(policy_set, &path).await?;
            }
        }

        Ok(count)
    }

    /// Load single policy file
    async fn load_policy_file(&self, policy_set: &mut PolicySet, path: &Path) -> Result<usize> {
        let content = tokio::fs::read_to_string(path).await?;

        // Validate policy content
        self.validator.validate_policy_content(&content)?;

        // Parse policy
        let policy_id = self.generate_policy_id(path);
        match Policy::parse(Some(policy_id.clone()), &content) {
            Ok(policy) => {
                policy_set.add(policy).map_err(|e| {
                    ControlCenterError::Policy(policy::PolicyError::Parsing(e.to_string()))
                })?;
                debug!("Loaded policy from file: {:?}", path);
                Ok(1)
            }
            Err(e) => {
                error!("Failed to parse policy file {:?}: {}", path, e);
                Err(ControlCenterError::Policy(policy::PolicyError::Parsing(
                    format!("Policy file {:?}: {}", path, e),
                )))
            }
        }
    }

    /// Generate policy ID from file path
    fn generate_policy_id(&self, path: &Path) -> PolicyId {
        let file_stem = path
            .file_stem()
            .and_then(|s| s.to_str())
            .unwrap_or("unknown");
        PolicyId::new(file_stem)
    }

    /// Build Cedar request from our request context
    async fn build_cedar_request(&self, context: &PolicyRequestContext) -> Result<Request> {
        let principal = EntityUid::from_type_name_and_id(
            EntityTypeName::from_str(&context.principal.entity_type).map_err(|e| {
                ControlCenterError::Policy(policy::PolicyError::Evaluation(format!(
                    "Invalid principal type: {}",
                    e
                )))
            })?,
            EntityId::new(context.principal.id.clone()),
        );

        let action = EntityUid::from_type_name_and_id(
            EntityTypeName::from_str(&context.action.entity_type).map_err(|e| {
                ControlCenterError::Policy(policy::PolicyError::Evaluation(format!(
                    "Invalid action type: {}",
                    e
                )))
            })?,
            EntityId::new(context.action.id.clone()),
        );

        let resource = EntityUid::from_type_name_and_id(
            EntityTypeName::from_str(&context.resource.entity_type).map_err(|e| {
                ControlCenterError::Policy(policy::PolicyError::Evaluation(format!(
                    "Invalid resource type: {}",
                    e
                )))
            })?,
            EntityId::new(context.resource.id.clone()),
        );

        // Build context from environment
        let context_map = self
            .context_builder
            .build_context(&context.environment)
            .await?;

        let context = Context::from_json_value(serde_json::Value::Object(context_map), None)
            .map_err(|e| {
                ControlCenterError::Policy(policy::PolicyError::Evaluation(format!(
                    "Context build error: {}",
                    e
                )))
            })?;

        let schema = self.build_schema().await?;
        Request::new(principal, action, resource, context, Some(&schema)).map_err(|e| {
            ControlCenterError::Policy(policy::PolicyError::Evaluation(format!(
                "Request validation error: {}",
                e
            )))
        })
    }

    /// Build entities for Cedar evaluation
    async fn build_entities(&self, context: &PolicyRequestContext) -> Result<Entities> {
        // This would be expanded to include actual entity data
        // For now, return empty entities
        Ok(Entities::empty())
    }

    /// Build schema for Cedar validation
    async fn build_schema(&self) -> Result<cedar_policy::Schema> {
        // Load schema from configuration
        let schema_path = &self.config.policies.schema_dir;
        if schema_path.exists() {
            // Implementation would load actual schema files from the directory
            // For Cedar 4.8, we'd need to parse JSON schema files and construct
            // Schema
        }

        // Cedar 4.8 schema construction via JSON
        // For now, use a minimal valid schema JSON
        let schema_json = serde_json::json!({
            "": {
                "entityTypes": {},
                "actions": {}
            }
        });

        cedar_policy::Schema::from_json_value(schema_json).map_err(|e| {
            ControlCenterError::Policy(policy::PolicyError::Evaluation(format!(
                "Failed to build schema: {}",
                e
            )))
        })
    }

    /// Convert Cedar response to our format
    async fn convert_response(
        &self,
        response: Response,
        context: &PolicyRequestContext,
    ) -> Result<PolicyResult> {
        let decision = PolicyDecision::from(response.decision());

        // In Cedar 4.8, diagnostics API is simplified
        // Return empty diagnostics for now as Diagnostics type doesn't expose iteration
        let errors = Vec::new();
        let warnings = Vec::new();

        // Extract policy ID if available - Cedar 4.8 API
        let policy_id = None;

        Ok(PolicyResult {
            decision,
            policy_id,
            errors,
            warnings,
            context: context.environment.clone(),
        })
    }

    /// Get current policy set
    async fn get_policy_set(&self) -> Result<PolicySet> {
        // Check cache first if enabled
        if self.config.policies.enable_caching {
            let cache = self.policy_cache.read().await;
            if let Some((cached_set, timestamp)) = cache.get("current") {
                let age = chrono::Utc::now().signed_duration_since(*timestamp);
                if age.num_seconds() < self.config.policies.cache_ttl_seconds as i64 {
                    return Ok(cached_set.clone());
                }
            }
        }

        // Return current policy set
        let policy_set = self.policy_set.read().await.clone();

        // Update cache if enabled
        if self.config.policies.enable_caching {
            let mut cache = self.policy_cache.write().await;
            cache.insert(
                "current".to_string(),
                (policy_set.clone(), chrono::Utc::now()),
            );
        }

        Ok(policy_set)
    }

    /// Validate policies in given path
    pub fn validate_policies<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        self.validator.validate_directory(path.as_ref())?;
        Ok(())
    }

    /// Test policy with given test data
    pub async fn test_policy<P: AsRef<Path>>(
        &self,
        policy_path: P,
        test_data_path: P,
    ) -> Result<()> {
        let policy_content = tokio::fs::read_to_string(policy_path.as_ref()).await?;
        let test_data: serde_json::Value =
            serde_json::from_str(&tokio::fs::read_to_string(test_data_path.as_ref()).await?)?;

        // Implementation would run test cases against policy
        info!(
            "Testing policy {:?} with test data {:?}",
            policy_path.as_ref(),
            test_data_path.as_ref()
        );

        // TODO: Implement actual test execution
        Ok(())
    }

    /// Analyze policy impact
    pub fn analyze_impact<P: AsRef<Path>>(&self, policy_path: P) -> Result<serde_json::Value> {
        // Implementation would analyze what would change if this policy was applied
        info!("Analyzing impact for policy {:?}", policy_path.as_ref());

        Ok(serde_json::json!({
            "affected_resources": [],
            "potential_denials": 0,
            "new_permissions": [],
            "removed_permissions": []
        }))
    }

    /// Check SOC2 compliance
    pub async fn check_soc2_compliance(&self) -> Result<serde_json::Value> {
        // Implementation would check policies against SOC2 requirements
        info!("Checking SOC2 compliance");

        Ok(serde_json::json!({
            "compliant": true,
            "violations": [],
            "recommendations": []
        }))
    }

    /// Check HIPAA compliance
    pub async fn check_hipaa_compliance(&self) -> Result<serde_json::Value> {
        // Implementation would check policies against HIPAA requirements
        info!("Checking HIPAA compliance");

        Ok(serde_json::json!({
            "compliant": true,
            "violations": [],
            "recommendations": []
        }))
    }

    /// Generate compliance report
    pub async fn generate_compliance_report(&self, format: &str) -> Result<String> {
        // Implementation would generate comprehensive compliance report
        info!("Generating compliance report in {} format", format);

        match format {
            "json" => Ok(serde_json::json!({
                "timestamp": chrono::Utc::now(),
                "soc2": self.check_soc2_compliance().await?,
                "hipaa": self.check_hipaa_compliance().await?
            })
            .to_string()),
            "html" => Ok("<html><body><h1>Compliance Report</h1></body></html>".to_string()),
            _ => Err(ControlCenterError::Infrastructure(
                infrastructure::InfrastructureError::Configuration(format!(
                    "Unsupported report format: {}",
                    format
                )),
            )),
        }
    }

    /// Build Axum server with policy endpoints
    pub async fn build_server(&self) -> Result<axum::Router> {
        use axum::{routing::get, Router};

        let router = Router::new()
            .route("/health", get(|| async { "OK" }))
            .route("/policies", get(|| async { "Policy endpoints" }));

        Ok(router)
    }
}
