//! Policy Versioning and Rollback System
//!
//! Provides version control for Cedar policies with rollback capabilities.

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

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use tracing::{debug, error, info, warn};
use uuid::Uuid;

use crate::error::{policy, ControlCenterError, Result};
use crate::policies::PolicyMetadata;
use crate::storage::{
    PolicyStorage, PolicyVersion as StoragePolicyVersion, RollbackResult as StorageRollbackResult,
};

/// Policy version information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PolicyVersion {
    pub version_id: String,
    pub policy_id: String,
    pub version_number: u32,
    pub content: String,
    pub metadata: PolicyMetadata,
    pub created_at: DateTime<Utc>,
    pub created_by: String,
    pub change_summary: String,
    pub tags: Vec<String>,
    pub is_active: bool,
    pub parent_version_id: Option<String>,
    pub checksum: String,
}

/// Version change information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionChange {
    pub change_type: ChangeType,
    pub old_version: Option<u32>,
    pub new_version: u32,
    pub summary: String,
    pub details: Vec<String>,
    pub impact_analysis: ImpactAnalysis,
}

/// Types of policy changes
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChangeType {
    Create,
    Update,
    Delete,
    Restore,
    Rollback,
}

/// Impact analysis for version changes
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImpactAnalysis {
    pub affected_resources: Vec<String>,
    pub potential_access_changes: Vec<String>,
    pub compliance_impact: Vec<String>,
    pub risk_level: RiskLevel,
}

/// Risk levels for policy changes
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RiskLevel {
    Low,
    Medium,
    High,
    Critical,
}

/// Rollback operation result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RollbackResult {
    pub success: bool,
    pub rollback_id: String,
    pub from_version: u32,
    pub to_version: u32,
    pub changes_reverted: Vec<String>,
    pub warnings: Vec<String>,
    pub timestamp: DateTime<Utc>,
}

/// Convert storage PolicyVersion to versioning PolicyVersion
fn convert_storage_version(
    storage_version: StoragePolicyVersion,
    metadata: Option<PolicyMetadata>,
) -> PolicyVersion {
    let metadata = metadata.unwrap_or_else(|| PolicyMetadata {
        id: storage_version.policy_id.clone(),
        name: String::new(),
        description: String::new(),
        version: "1.0".to_string(),
        created_at: storage_version.created_at,
        updated_at: storage_version.created_at,
        created_by: "system".to_string(),
        tags: Vec::new(),
        category: crate::policies::PolicyCategory::Security,
        priority: 0,
        enabled: true,
    });

    PolicyVersion {
        version_id: storage_version.version_id,
        policy_id: storage_version.policy_id,
        version_number: storage_version.version_number,
        content: storage_version.content,
        metadata,
        created_at: storage_version.created_at,
        created_by: "system".to_string(), // Storage doesn't track this
        change_summary: String::new(),    // Storage doesn't track this
        tags: storage_version.tags,
        is_active: storage_version.is_active,
        parent_version_id: None, // Storage doesn't track this
        checksum: String::new(), // Storage doesn't track this
    }
}

/// Convert versioning PolicyVersion to storage PolicyVersion
fn convert_to_storage_version(version: &PolicyVersion) -> StoragePolicyVersion {
    StoragePolicyVersion {
        version_id: version.version_id.clone(),
        policy_id: version.policy_id.clone(),
        version_number: version.version_number,
        content: version.content.clone(),
        is_active: version.is_active,
        created_at: version.created_at,
        tags: version.tags.clone(),
    }
}

/// Convert storage RollbackResult to versioning RollbackResult
fn convert_storage_rollback(storage_result: StorageRollbackResult) -> RollbackResult {
    RollbackResult {
        success: storage_result.success,
        rollback_id: Uuid::new_v4().to_string(),
        from_version: storage_result.from_version,
        to_version: storage_result.to_version,
        changes_reverted: Vec::new(),
        warnings: Vec::new(),
        timestamp: storage_result.timestamp,
    }
}

/// Convert versioning RollbackResult to storage RollbackResult
fn convert_to_storage_rollback(result: &RollbackResult) -> StorageRollbackResult {
    StorageRollbackResult {
        success: result.success,
        from_version: result.from_version,
        to_version: result.to_version,
        timestamp: result.timestamp,
    }
}

/// Policy version manager
pub struct PolicyVersionManager {
    storage: Arc<dyn PolicyStorage>,
    max_versions_per_policy: usize,
    retention_days: u64,
}

impl PolicyVersionManager {
    /// Create new version manager
    pub fn new(storage: Arc<dyn PolicyStorage>) -> Self {
        Self {
            storage,
            max_versions_per_policy: 100, // Keep last 100 versions
            retention_days: 365,          // Keep versions for 1 year
        }
    }

    /// Create new policy version
    pub async fn create_version(
        &self,
        policy_id: &str,
        content: &str,
        metadata: PolicyMetadata,
        created_by: &str,
        change_summary: &str,
    ) -> Result<PolicyVersion> {
        debug!("Creating new version for policy: {}", policy_id);

        // Get current version number
        let current_version = self.get_latest_version_number(policy_id).await?;
        let new_version_number = current_version + 1;

        // Create version
        let version = PolicyVersion {
            version_id: Uuid::new_v4().to_string(),
            policy_id: policy_id.to_string(),
            version_number: new_version_number,
            content: content.to_string(),
            metadata,
            created_at: Utc::now(),
            created_by: created_by.to_string(),
            change_summary: change_summary.to_string(),
            tags: Vec::new(),
            is_active: true,
            parent_version_id: self.get_current_version_id(policy_id).await?,
            checksum: self.calculate_checksum(content),
        };

        // Store version
        let storage_version = convert_to_storage_version(&version);
        self.storage.store_policy_version(&storage_version).await?;

        // Mark previous version as inactive
        if let Some(prev_version_id) = &version.parent_version_id {
            self.storage
                .deactivate_policy_version(prev_version_id)
                .await?;
        }

        // Cleanup old versions if needed
        self.cleanup_old_versions(policy_id).await?;

        info!(
            "Created policy version {} for policy {}",
            new_version_number, policy_id
        );
        Ok(version)
    }

    /// Get policy version by version number
    pub async fn get_version(
        &self,
        policy_id: &str,
        version_number: u32,
    ) -> Result<Option<PolicyVersion>> {
        let storage_version = self
            .storage
            .get_policy_version(policy_id, version_number)
            .await?;
        Ok(storage_version.map(|v| convert_storage_version(v, None)))
    }

    /// Get current active version
    pub async fn get_current_version(&self, policy_id: &str) -> Result<Option<PolicyVersion>> {
        let storage_version = self.storage.get_current_policy_version(policy_id).await?;
        Ok(storage_version.map(|v| convert_storage_version(v, None)))
    }

    /// List all versions for a policy
    pub async fn list_versions(&self, policy_id: &str) -> Result<Vec<PolicyVersion>> {
        let storage_versions = self.storage.list_policy_versions(policy_id).await?;
        Ok(storage_versions
            .into_iter()
            .map(|v| convert_storage_version(v, None))
            .collect())
    }

    /// Get version history with changes
    pub async fn get_version_history(&self, policy_id: &str) -> Result<Vec<VersionChange>> {
        let versions = self.list_versions(policy_id).await?;
        let mut changes = Vec::new();

        for (i, version) in versions.iter().enumerate() {
            let change_type = if i == 0 {
                ChangeType::Create
            } else {
                ChangeType::Update
            };

            let impact_analysis = self.analyze_version_impact(version).await?;

            changes.push(VersionChange {
                change_type,
                old_version: if i > 0 {
                    Some(versions[i - 1].version_number)
                } else {
                    None
                },
                new_version: version.version_number,
                summary: version.change_summary.clone(),
                details: self.extract_change_details(
                    &version.content,
                    versions
                        .get(i.saturating_sub(1))
                        .map(|v| v.content.as_str()),
                ),
                impact_analysis,
            });
        }

        Ok(changes)
    }

    /// Rollback to specific version
    pub async fn rollback_to_version(
        &self,
        policy_id: &str,
        target_version: u32,
        rollback_by: &str,
        reason: &str,
    ) -> Result<RollbackResult> {
        info!(
            "Rolling back policy {} to version {}",
            policy_id, target_version
        );

        let current_version = self.get_current_version(policy_id).await?.ok_or_else(|| {
            ControlCenterError::Policy(policy::PolicyError::Evaluation(format!(
                "No current version found for policy: {}",
                policy_id
            )))
        })?;

        let target_version_data = self
            .get_version(policy_id, target_version)
            .await?
            .ok_or_else(|| {
                ControlCenterError::Policy(policy::PolicyError::Evaluation(format!(
                    "Target version {} not found for policy: {}",
                    target_version, policy_id
                )))
            })?;

        // Analyze rollback impact
        let impact = self
            .analyze_rollback_impact(&current_version, &target_version_data)
            .await?;

        let mut warnings = Vec::new();
        if matches!(impact.risk_level, RiskLevel::High | RiskLevel::Critical) {
            warnings.push("High risk rollback - significant access changes detected".to_string());
        }

        // Create rollback version
        let mut rollback_metadata = target_version_data.metadata.clone();
        rollback_metadata.updated_at = Utc::now();

        let rollback_version = self
            .create_version(
                policy_id,
                &target_version_data.content,
                rollback_metadata,
                rollback_by,
                &format!("Rollback to version {} - {}", target_version, reason),
            )
            .await?;

        // Record rollback operation
        let rollback_result = RollbackResult {
            success: true,
            rollback_id: Uuid::new_v4().to_string(),
            from_version: current_version.version_number,
            to_version: target_version,
            changes_reverted: self.extract_change_details(
                &current_version.content,
                Some(&target_version_data.content),
            ),
            warnings,
            timestamp: Utc::now(),
        };

        let storage_rollback = convert_to_storage_rollback(&rollback_result);
        self.storage
            .record_rollback_operation(policy_id, &storage_rollback)
            .await?;

        info!(
            "Successfully rolled back policy {} from version {} to version {}",
            policy_id, current_version.version_number, target_version
        );

        Ok(rollback_result)
    }

    /// Compare two policy versions
    pub async fn compare_versions(
        &self,
        policy_id: &str,
        version1: u32,
        version2: u32,
    ) -> Result<Vec<String>> {
        let v1 = self
            .get_version(policy_id, version1)
            .await?
            .ok_or_else(|| {
                ControlCenterError::Policy(policy::PolicyError::Evaluation(format!(
                    "Version {} not found",
                    version1
                )))
            })?;

        let v2 = self
            .get_version(policy_id, version2)
            .await?
            .ok_or_else(|| {
                ControlCenterError::Policy(policy::PolicyError::Evaluation(format!(
                    "Version {} not found",
                    version2
                )))
            })?;

        Ok(self.extract_change_details(&v1.content, Some(&v2.content)))
    }

    /// Tag a specific version
    pub async fn tag_version(&self, policy_id: &str, version_number: u32, tag: &str) -> Result<()> {
        self.storage
            .tag_policy_version(policy_id, version_number, tag)
            .await
    }

    /// Get versions by tag
    pub async fn get_versions_by_tag(
        &self,
        policy_id: &str,
        tag: &str,
    ) -> Result<Vec<PolicyVersion>> {
        let storage_versions = self
            .storage
            .get_policy_versions_by_tag(policy_id, tag)
            .await?;
        Ok(storage_versions
            .into_iter()
            .map(|v| convert_storage_version(v, None))
            .collect())
    }

    /// Get latest version number
    async fn get_latest_version_number(&self, policy_id: &str) -> Result<u32> {
        if let Some(current) = self.get_current_version(policy_id).await? {
            Ok(current.version_number)
        } else {
            Ok(0) // First version will be 1
        }
    }

    /// Get current version ID
    async fn get_current_version_id(&self, policy_id: &str) -> Result<Option<String>> {
        if let Some(current) = self.get_current_version(policy_id).await? {
            Ok(Some(current.version_id))
        } else {
            Ok(None)
        }
    }

    /// Calculate content checksum
    fn calculate_checksum(&self, content: &str) -> String {
        use sha2::{Digest, Sha256};
        let mut hasher = Sha256::new();
        hasher.update(content.as_bytes());
        format!("{:x}", hasher.finalize())
    }

    /// Analyze impact of version change
    async fn analyze_version_impact(&self, version: &PolicyVersion) -> Result<ImpactAnalysis> {
        // Simplified impact analysis - in production this would be more sophisticated
        let content = &version.content;

        let mut affected_resources = Vec::new();
        let mut potential_access_changes = Vec::new();
        let mut compliance_impact = Vec::new();

        // Analyze policy content for impact indicators
        if content.contains("permit") {
            potential_access_changes.push("New permissions granted".to_string());
        }
        if content.contains("forbid") {
            potential_access_changes.push("Access restrictions added".to_string());
        }
        if content.contains("*") {
            affected_resources.push("Wildcard resource access".to_string());
        }

        // Determine risk level
        let risk_level = if content.contains("forbid") && content.contains("*") {
            RiskLevel::High
        } else if potential_access_changes.len() > 2 {
            RiskLevel::Medium
        } else {
            RiskLevel::Low
        };

        Ok(ImpactAnalysis {
            affected_resources,
            potential_access_changes,
            compliance_impact,
            risk_level,
        })
    }

    /// Analyze rollback impact
    async fn analyze_rollback_impact(
        &self,
        current: &PolicyVersion,
        target: &PolicyVersion,
    ) -> Result<ImpactAnalysis> {
        let changes = self.extract_change_details(&current.content, Some(&target.content));

        let risk_level = if changes.len() > 5 {
            RiskLevel::High
        } else if changes.len() > 2 {
            RiskLevel::Medium
        } else {
            RiskLevel::Low
        };

        Ok(ImpactAnalysis {
            affected_resources: vec!["Policy rollback".to_string()],
            potential_access_changes: changes.clone(),
            compliance_impact: vec![],
            risk_level,
        })
    }

    /// Extract change details between versions
    fn extract_change_details(&self, content1: &str, content2: Option<&str>) -> Vec<String> {
        let mut changes = Vec::new();

        if let Some(content2) = content2 {
            // Simple diff - in production would use more sophisticated diff algorithm
            let lines1: Vec<&str> = content1.lines().collect();
            let lines2: Vec<&str> = content2.lines().collect();

            // Find added lines
            for line in lines1.iter() {
                if !lines2.contains(line) {
                    changes.push(format!("Added: {}", line));
                }
            }

            // Find removed lines
            for line in lines2.iter() {
                if !lines1.contains(line) {
                    changes.push(format!("Removed: {}", line));
                }
            }
        } else {
            changes.push("Initial version created".to_string());
        }

        changes
    }

    /// Cleanup old versions based on retention policy
    async fn cleanup_old_versions(&self, policy_id: &str) -> Result<()> {
        let versions = self.list_versions(policy_id).await?;

        // Keep only max_versions_per_policy latest versions
        if versions.len() > self.max_versions_per_policy {
            let versions_to_delete = &versions[self.max_versions_per_policy..];
            for version in versions_to_delete {
                if !version.is_active {
                    self.storage
                        .delete_policy_version(&version.version_id)
                        .await?;
                    debug!(
                        "Deleted old version {} for policy {}",
                        version.version_number, policy_id
                    );
                }
            }
        }

        // Delete versions older than retention period
        let cutoff_date = Utc::now() - chrono::Duration::days(self.retention_days as i64);
        for version in &versions {
            if version.created_at < cutoff_date && !version.is_active {
                self.storage
                    .delete_policy_version(&version.version_id)
                    .await?;
                debug!(
                    "Deleted expired version {} for policy {}",
                    version.version_number, policy_id
                );
            }
        }

        Ok(())
    }

    /// Get version statistics
    pub async fn get_version_statistics(&self, policy_id: &str) -> Result<serde_json::Value> {
        let versions = self.list_versions(policy_id).await?;

        let total_versions = versions.len();
        let active_version = versions
            .iter()
            .find(|v| v.is_active)
            .map(|v| v.version_number);
        let creation_dates: Vec<DateTime<Utc>> = versions.iter().map(|v| v.created_at).collect();
        let oldest_version = creation_dates.iter().min();
        let newest_version = creation_dates.iter().max();

        Ok(serde_json::json!({
            "policy_id": policy_id,
            "total_versions": total_versions,
            "active_version": active_version,
            "oldest_version": oldest_version,
            "newest_version": newest_version,
            "versions": versions.iter().map(|v| serde_json::json!({
                "version_number": v.version_number,
                "created_at": v.created_at,
                "created_by": v.created_by,
                "change_summary": v.change_summary,
                "is_active": v.is_active
            })).collect::<Vec<_>>()
        }))
    }
}
