//! State persistence for infrastructure management
//!
//! This module provides functionality to persist and restore infrastructure state.

use crate::domain::InfrastructureState;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use thiserror::Error;
use tokio::fs;
use tracing::{debug, info};

/// Errors that can occur during state operations
#[derive(Error, Debug)]
pub enum StateError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("Serialization error: {0}")]
    Serialization(#[from] serde_json::Error),

    #[error("State file not found: {0}")]
    NotFound(String),
}

/// Metadata about the state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateMetadata {
    /// Version of the state file format
    pub version: String,
    /// Timestamp when state was saved
    pub timestamp: String,
    /// Hash of the infrastructure spec that created this state
    pub spec_hash: Option<String>,
}

/// Persisted state with metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistedState {
    /// Metadata about this state
    pub metadata: StateMetadata,
    /// The actual infrastructure state
    pub state: InfrastructureState,
}

impl PersistedState {
    /// Create new persisted state
    pub fn new(state: InfrastructureState) -> Self {
        Self {
            metadata: StateMetadata {
                version: "1.0".to_string(),
                timestamp: chrono::Utc::now().to_rfc3339(),
                spec_hash: None,
            },
            state,
        }
    }

    /// Create new persisted state with spec hash
    pub fn with_spec_hash(state: InfrastructureState, spec_hash: String) -> Self {
        let mut persisted = Self::new(state);
        persisted.metadata.spec_hash = Some(spec_hash);
        persisted
    }
}

/// State manager for persisting infrastructure state
pub struct StateManager {
    state_dir: PathBuf,
}

impl StateManager {
    /// Create a new state manager
    pub fn new<P: AsRef<Path>>(state_dir: P) -> Self {
        Self {
            state_dir: state_dir.as_ref().to_path_buf(),
        }
    }

    /// Get default state directory (current dir / .hetzner-state)
    pub fn default_state_dir() -> Self {
        Self::new(".hetzner-state")
    }

    /// Ensure state directory exists
    async fn ensure_state_dir(&self) -> Result<(), StateError> {
        if !self.state_dir.exists() {
            debug!("Creating state directory: {:?}", self.state_dir);
            fs::create_dir_all(&self.state_dir).await?;
        }
        Ok(())
    }

    /// Get path to state file
    fn state_file_path(&self, name: &str) -> PathBuf {
        self.state_dir.join(format!("{}.json", name))
    }

    /// Save infrastructure state
    pub async fn save_state(&self, name: &str, state: PersistedState) -> Result<(), StateError> {
        self.ensure_state_dir().await?;

        let path = self.state_file_path(name);
        info!("Saving state to: {:?}", path);

        let json = serde_json::to_string_pretty(&state)?;
        fs::write(&path, json).await?;

        info!("State saved successfully");
        Ok(())
    }

    /// Load infrastructure state
    pub async fn load_state(&self, name: &str) -> Result<PersistedState, StateError> {
        let path = self.state_file_path(name);

        if !path.exists() {
            return Err(StateError::NotFound(format!("{:?}", path)));
        }

        debug!("Loading state from: {:?}", path);
        let json = fs::read_to_string(&path).await?;
        let state: PersistedState = serde_json::from_str(&json)?;

        info!("State loaded successfully");
        Ok(state)
    }

    /// Check if state exists
    pub fn state_exists(&self, name: &str) -> bool {
        self.state_file_path(name).exists()
    }

    /// Delete state file
    pub async fn delete_state(&self, name: &str) -> Result<(), StateError> {
        let path = self.state_file_path(name);

        if path.exists() {
            debug!("Deleting state file: {:?}", path);
            fs::remove_file(&path).await?;
            info!("State deleted");
        }

        Ok(())
    }

    /// List all available states
    pub async fn list_states(&self) -> Result<Vec<String>, StateError> {
        if !self.state_dir.exists() {
            return Ok(Vec::new());
        }

        let mut states = Vec::new();
        let mut entries = fs::read_dir(&self.state_dir).await?;

        while let Some(entry) = entries.next_entry().await? {
            if let Some(name) = entry.file_name().to_str()
                && name.ends_with(".json")
            {
                states.push(name.trim_end_matches(".json").to_string());
            }
        }

        Ok(states)
    }

    /// Create backup of current state
    pub async fn backup_state(&self, name: &str) -> Result<(), StateError> {
        let path = self.state_file_path(name);

        if !path.exists() {
            return Err(StateError::NotFound(format!("{:?}", path)));
        }

        let backup_name = format!("{}.backup.{}", name, chrono::Utc::now().timestamp());
        let backup_path = self.state_file_path(&backup_name);

        debug!("Creating backup: {:?} -> {:?}", path, backup_path);
        fs::copy(&path, &backup_path).await?;

        info!("Backup created: {:?}", backup_path);
        Ok(())
    }
}

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

    #[tokio::test]
    async fn test_save_and_load_state() {
        let temp_dir = TempDir::new().unwrap();
        let manager = StateManager::new(temp_dir.path());

        let state = InfrastructureState {
            servers: Vec::new(),
            networks: Vec::new(),
            firewalls: Vec::new(),
            load_balancers: Vec::new(),
        };

        let persisted = PersistedState::new(state);

        // Save
        manager.save_state("test", persisted.clone()).await.unwrap();

        // Load
        let loaded = manager.load_state("test").await.unwrap();

        assert_eq!(loaded.metadata.version, "1.0");
        assert!(loaded.state.is_empty());
    }

    #[tokio::test]
    async fn test_state_not_found() {
        let temp_dir = TempDir::new().unwrap();
        let manager = StateManager::new(temp_dir.path());

        let result = manager.load_state("nonexistent").await;
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), StateError::NotFound(_)));
    }

    #[tokio::test]
    async fn test_list_states() {
        let temp_dir = TempDir::new().unwrap();
        let manager = StateManager::new(temp_dir.path());

        let state = PersistedState::new(InfrastructureState::new());

        manager.save_state("state1", state.clone()).await.unwrap();
        manager.save_state("state2", state.clone()).await.unwrap();

        let states = manager.list_states().await.unwrap();
        assert_eq!(states.len(), 2);
        assert!(states.contains(&"state1".to_string()));
        assert!(states.contains(&"state2".to_string()));
    }

    #[tokio::test]
    async fn test_delete_state() {
        let temp_dir = TempDir::new().unwrap();
        let manager = StateManager::new(temp_dir.path());

        let state = PersistedState::new(InfrastructureState::new());
        manager.save_state("test", state).await.unwrap();

        assert!(manager.state_exists("test"));

        manager.delete_state("test").await.unwrap();

        assert!(!manager.state_exists("test"));
    }

    #[tokio::test]
    async fn test_backup_state() {
        let temp_dir = TempDir::new().unwrap();
        let manager = StateManager::new(temp_dir.path());

        let state = PersistedState::new(InfrastructureState::new());
        manager.save_state("test", state).await.unwrap();

        manager.backup_state("test").await.unwrap();

        let states = manager.list_states().await.unwrap();
        // Should have original + backup
        assert!(states.len() >= 2);
    }
}
