//! Secret Rotation Scheduler - Phase 3.1
//!
//! Manages automatic rotation of permanent secrets on scheduled intervals.
//! Supports configurable rotation policies with notifications and rollback.

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

use chrono::{Duration, Utc};
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
use tracing::{debug, info, warn};

use crate::error::{http, Result};

/// Rotation policy for a secret type
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RotationPolicy {
    /// Secret type (database, application, ssh, provider)
    pub secret_type: String,

    /// Rotation interval in days
    pub interval_days: i32,

    /// Enable automatic rotation
    pub auto_rotate: bool,

    /// Environment (dev, staging, prod)
    pub environment: String,

    /// TTL for rotated secrets (in seconds)
    pub ttl_seconds: Option<i64>,

    /// Require approval before rotation
    pub require_approval: bool,

    /// Notification channels (email, slack, webhook)
    pub notification_channels: Vec<String>,
}

/// Rotation schedule entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RotationSchedule {
    /// Unique schedule ID
    pub id: String,

    /// Secret path (workspace/domain/name)
    pub secret_path: String,

    /// Rotation policy applied
    pub policy: RotationPolicy,

    /// Next scheduled rotation time
    pub next_rotation: String,

    /// Last rotation time
    pub last_rotation: Option<String>,

    /// Rotation status (pending, in_progress, completed, failed)
    pub status: RotationStatus,

    /// Number of consecutive failures
    pub failure_count: i32,

    /// Max consecutive failures before disabling auto-rotation
    pub max_failures: i32,
}

/// Rotation status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RotationStatus {
    Pending,
    InProgress,
    Completed,
    Failed,
    Disabled,
}

/// Secret Rotation Scheduler Service
pub struct RotationScheduler {
    /// Rotation schedules keyed by secret path
    schedules: Arc<RwLock<HashMap<String, RotationSchedule>>>,

    /// Default rotation policies
    default_policies: Arc<RwLock<HashMap<String, RotationPolicy>>>,
}

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

impl RotationScheduler {
    /// Create new rotation scheduler with default policies
    pub fn new() -> Self {
        let default_policies = Arc::new(RwLock::new(Self::default_rotation_policies()));

        Self {
            schedules: Arc::new(RwLock::new(HashMap::new())),
            default_policies,
        }
    }

    /// Default rotation policies for different secret types
    fn default_rotation_policies() -> HashMap<String, RotationPolicy> {
        let mut policies = HashMap::new();

        // Database credentials: 90 days in production, 30 days in development
        policies.insert(
            "database:prod".to_string(),
            RotationPolicy {
                secret_type: "database".to_string(),
                interval_days: 90,
                auto_rotate: true,
                environment: "prod".to_string(),
                ttl_seconds: None,
                require_approval: true,
                notification_channels: vec!["email".to_string(), "slack".to_string()],
            },
        );

        policies.insert(
            "database:dev".to_string(),
            RotationPolicy {
                secret_type: "database".to_string(),
                interval_days: 30,
                auto_rotate: true,
                environment: "dev".to_string(),
                ttl_seconds: None,
                require_approval: false,
                notification_channels: vec!["email".to_string()],
            },
        );

        // Application secrets: 60 days in production, 14 days in development
        policies.insert(
            "application:prod".to_string(),
            RotationPolicy {
                secret_type: "application".to_string(),
                interval_days: 60,
                auto_rotate: true,
                environment: "prod".to_string(),
                ttl_seconds: None,
                require_approval: true,
                notification_channels: vec!["email".to_string(), "slack".to_string()],
            },
        );

        policies.insert(
            "application:dev".to_string(),
            RotationPolicy {
                secret_type: "application".to_string(),
                interval_days: 14,
                auto_rotate: true,
                environment: "dev".to_string(),
                ttl_seconds: None,
                require_approval: false,
                notification_channels: vec!["email".to_string()],
            },
        );

        // SSH keys: 365 days in production (infrastructure keys are long-lived)
        policies.insert(
            "ssh:prod".to_string(),
            RotationPolicy {
                secret_type: "ssh".to_string(),
                interval_days: 365,
                auto_rotate: true,
                environment: "prod".to_string(),
                ttl_seconds: None,
                require_approval: true,
                notification_channels: vec!["email".to_string(), "slack".to_string()],
            },
        );

        policies.insert(
            "ssh:dev".to_string(),
            RotationPolicy {
                secret_type: "ssh".to_string(),
                interval_days: 90,
                auto_rotate: true,
                environment: "dev".to_string(),
                ttl_seconds: None,
                require_approval: false,
                notification_channels: vec!["email".to_string()],
            },
        );

        // Provider credentials: 180 days in production
        policies.insert(
            "provider:prod".to_string(),
            RotationPolicy {
                secret_type: "provider".to_string(),
                interval_days: 180,
                auto_rotate: true,
                environment: "prod".to_string(),
                ttl_seconds: None,
                require_approval: true,
                notification_channels: vec!["email".to_string(), "slack".to_string()],
            },
        );

        policies.insert(
            "provider:dev".to_string(),
            RotationPolicy {
                secret_type: "provider".to_string(),
                interval_days: 30,
                auto_rotate: true,
                environment: "dev".to_string(),
                ttl_seconds: None,
                require_approval: false,
                notification_channels: vec!["email".to_string()],
            },
        );

        policies
    }

    /// Create a rotation schedule for a secret
    pub async fn create_schedule(
        &self,
        secret_path: &str,
        secret_type: &str,
        environment: &str,
    ) -> Result<RotationSchedule> {
        let policy_key = format!("{}:{}", secret_type, environment);
        let default_policies = self.default_policies.read().await;

        let policy = default_policies.get(&policy_key).cloned().ok_or_else(|| {
            let msg = format!(
                "No rotation policy found for {}/{}",
                secret_type, environment
            );
            crate::error::ControlCenterError::Http(http::not_found(&msg))
        })?;

        let now = Utc::now();
        let next_rotation = (now + Duration::days(policy.interval_days as i64)).to_rfc3339();

        let schedule = RotationSchedule {
            id: uuid::Uuid::new_v4().to_string(),
            secret_path: secret_path.to_string(),
            policy,
            next_rotation,
            last_rotation: None,
            status: RotationStatus::Pending,
            failure_count: 0,
            max_failures: 3,
        };

        let mut schedules = self.schedules.write().await;
        schedules.insert(secret_path.to_string(), schedule.clone());

        info!(
            schedule_id = %schedule.id,
            secret = %secret_path,
            "Rotation schedule created"
        );

        Ok(schedule)
    }

    /// Get a rotation schedule
    pub async fn get_schedule(&self, secret_path: &str) -> Result<Option<RotationSchedule>> {
        let schedules = self.schedules.read().await;
        Ok(schedules.get(secret_path).cloned())
    }

    /// List all rotation schedules
    pub async fn list_schedules(&self) -> Result<Vec<RotationSchedule>> {
        let schedules = self.schedules.read().await;
        Ok(schedules.values().cloned().collect())
    }

    /// Get schedules due for rotation
    pub async fn get_due_schedules(&self) -> Result<Vec<RotationSchedule>> {
        let schedules = self.schedules.read().await;
        let now = Utc::now().to_rfc3339();

        let due: Vec<RotationSchedule> = schedules
            .values()
            .filter(|s| {
                s.policy.auto_rotate
                    && s.status == RotationStatus::Pending
                    && s.next_rotation <= now
                    && s.failure_count < s.max_failures
            })
            .cloned()
            .collect();

        debug!(count = %due.len(), "Found {} due schedules", due.len());
        Ok(due)
    }

    /// Mark rotation as in progress
    pub async fn mark_in_progress(&self, secret_path: &str) -> Result<()> {
        let mut schedules = self.schedules.write().await;

        if let Some(schedule) = schedules.get_mut(secret_path) {
            schedule.status = RotationStatus::InProgress;
            debug!(secret = %secret_path, "Marked rotation as in progress");
            Ok(())
        } else {
            let msg = format!("Schedule not found for {}", secret_path);
            Err(crate::error::ControlCenterError::Http(http::not_found(
                &msg,
            )))
        }
    }

    /// Mark rotation as completed and schedule next rotation
    pub async fn mark_completed(&self, secret_path: &str) -> Result<()> {
        let mut schedules = self.schedules.write().await;

        if let Some(schedule) = schedules.get_mut(secret_path) {
            let now = Utc::now();
            schedule.status = RotationStatus::Completed;
            schedule.last_rotation = Some(now.to_rfc3339());
            schedule.failure_count = 0;

            // Schedule next rotation
            let next = (now + Duration::days(schedule.policy.interval_days as i64)).to_rfc3339();
            schedule.next_rotation = next.clone();

            let last_rot = schedule.last_rotation.clone();
            info!(
                secret = %secret_path,
                last_rotation = ?last_rot,
                next_rotation = %next,
                "Rotation completed"
            );

            Ok(())
        } else {
            let msg = format!("Schedule not found for {}", secret_path);
            Err(crate::error::ControlCenterError::Http(http::not_found(
                &msg,
            )))
        }
    }

    /// Mark rotation as failed and increment failure count
    pub async fn mark_failed(&self, secret_path: &str, error: &str) -> Result<()> {
        let mut schedules = self.schedules.write().await;

        if let Some(schedule) = schedules.get_mut(secret_path) {
            schedule.failure_count += 1;

            if schedule.failure_count >= schedule.max_failures {
                schedule.status = RotationStatus::Disabled;
                warn!(
                    secret = %secret_path,
                    failures = %schedule.failure_count,
                    "Rotation disabled due to repeated failures"
                );
            } else {
                schedule.status = RotationStatus::Pending;
                debug!(
                    secret = %secret_path,
                    failures = %schedule.failure_count,
                    error = %error,
                    "Rotation failed"
                );
            }

            Ok(())
        } else {
            let msg = format!("Schedule not found for {}", secret_path);
            Err(crate::error::ControlCenterError::Http(http::not_found(
                &msg,
            )))
        }
    }

    /// Set custom rotation policy
    pub async fn set_policy(&self, secret_path: &str, policy: RotationPolicy) -> Result<()> {
        let mut schedules = self.schedules.write().await;

        if let Some(schedule) = schedules.get_mut(secret_path) {
            schedule.policy = policy;
            info!(
                secret = %secret_path,
                "Rotation policy updated"
            );
            Ok(())
        } else {
            let msg = format!("Schedule not found for {}", secret_path);
            Err(crate::error::ControlCenterError::Http(http::not_found(
                &msg,
            )))
        }
    }

    /// Get statistics about rotation schedules
    pub async fn get_stats(&self) -> Result<RotationStats> {
        let schedules = self.schedules.read().await;

        let stats = RotationStats {
            total: schedules.len(),
            pending: schedules
                .values()
                .filter(|s| s.status == RotationStatus::Pending)
                .count(),
            in_progress: schedules
                .values()
                .filter(|s| s.status == RotationStatus::InProgress)
                .count(),
            completed: schedules
                .values()
                .filter(|s| s.status == RotationStatus::Completed)
                .count(),
            failed: schedules
                .values()
                .filter(|s| s.status == RotationStatus::Failed)
                .count(),
            disabled: schedules
                .values()
                .filter(|s| s.status == RotationStatus::Disabled)
                .count(),
        };

        Ok(stats)
    }
}

/// Rotation statistics
#[derive(Debug, Serialize, Deserialize)]
pub struct RotationStats {
    pub total: usize,
    pub pending: usize,
    pub in_progress: usize,
    pub completed: usize,
    pub failed: usize,
    pub disabled: usize,
}

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

    #[tokio::test]
    async fn test_rotation_scheduler_creation() {
        let scheduler = RotationScheduler::new();
        let schedule = scheduler
            .create_schedule("test/secret", "database", "prod")
            .await
            .expect("Should create schedule");

        assert_eq!(schedule.secret_path, "test/secret");
        assert_eq!(schedule.policy.secret_type, "database");
        assert_eq!(schedule.policy.interval_days, 90);
    }

    #[tokio::test]
    async fn test_get_due_schedules() {
        let scheduler = RotationScheduler::new();
        let mut schedule = scheduler
            .create_schedule("test/secret", "database", "prod")
            .await
            .expect("Should create schedule");

        // Move next_rotation to the past
        schedule.next_rotation = (Utc::now() - Duration::days(1)).to_rfc3339();
        let mut schedules = scheduler.schedules.write().await;
        schedules.insert("test/secret".to_string(), schedule);
        drop(schedules);

        let due = scheduler
            .get_due_schedules()
            .await
            .expect("Should get due schedules");

        assert_eq!(due.len(), 1);
    }
}
