//! State persistence and management for release operations.
#![allow(dead_code)] // Public API - items may be used by external consumers

//!
//! This module provides robust state persistence with file locking,
//! corruption recovery, and atomic operations.

use crate::error::{Result, StateError};
use crate::state::ReleaseState;
use std::fs;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use sysinfo::{Pid, System};

/// State manager for persistent release state
#[derive(Debug)]
pub struct StateManager {
    /// Path to state file
    state_file_path: PathBuf,
    /// Path to lock file
    lock_file_path: PathBuf,
    /// Current lock handle
    lock_handle: Option<FileLock>,
    /// Configuration for state management
    config: StateConfig,
}

/// Configuration for state management
#[derive(Debug, Clone)]
pub struct StateConfig {
    /// Maximum age of state files before cleanup (in seconds)
    pub max_state_age_seconds: u64,
    /// Whether to compress state files
    pub compress_state: bool,
    /// Timeout for acquiring file locks (in milliseconds)
    pub lock_timeout_ms: u64,
    /// Whether to validate state on load
    pub validate_on_load: bool,
    /// Timeout for considering a lock stale (in seconds)
    pub stale_lock_timeout_seconds: u64,
}

impl Default for StateConfig {
    fn default() -> Self {
        Self {
            max_state_age_seconds: 86400 * 7, // 7 days
            compress_state: false,
            lock_timeout_ms: 5000, // 5 seconds
            validate_on_load: true,
            stale_lock_timeout_seconds: 3600, // 1 hour
        }
    }
}

/// File lock implementation with advisory lock handle
#[derive(Debug)]
struct FileLock {
    /// Path to lock file
    _lock_file: PathBuf,
    /// Process ID of the locking process
    _pid: u32,
    /// Timestamp when lock was acquired
    _acquired_at: SystemTime,
    /// Platform-specific lock storage
    /// On Unix: RAII Flock guard that auto-unlocks on drop
    /// On Windows: File handle with manual unlock tracking
    /// On other platforms: File handle (best-effort locking)
    #[cfg(unix)]
    _lock_guard: nix::fcntl::Flock<std::fs::File>,
    #[cfg(not(unix))]
    _lock_handle: std::fs::File,
    /// Track whether Windows lock is active (Windows only)
    #[cfg(windows)]
    _is_locked: bool,
}

impl Drop for FileLock {
    fn drop(&mut self) {
        #[cfg(windows)]
        {
            if self._is_locked {
                use std::os::windows::io::AsRawHandle;
                use windows::Win32::Foundation::HANDLE;
                use windows::Win32::Storage::FileSystem::UnlockFileEx;
                use windows::Win32::System::IO::OVERLAPPED;

                let raw_handle = self._lock_handle.as_raw_handle();
                let handle = HANDLE(raw_handle as isize);
                let mut overlapped = OVERLAPPED::default();

                // Explicitly unlock the file region
                // SAFETY:
                // - handle is valid because it comes from the owned File in _lock_handle
                // - overlapped is correctly initialized (same as LockFileEx call)
                // - We're unlocking the same region (offset 0, length u32::MAX)
                unsafe {
                    let _ = UnlockFileEx(
                        handle,
                        0,        // Reserved (must be 0)
                        u32::MAX, // nNumberOfBytesToUnlockLow (matches LockFileEx)
                        u32::MAX, // nNumberOfBytesToUnlockHigh (matches LockFileEx)
                        &mut overlapped,
                    );
                    // Note: We ignore errors here because:
                    // 1. Drop cannot return errors
                    // 2. The file handle will be closed anyway when _lock_handle drops
                    // 3. Explicit unlock is defense-in-depth
                }
            }
        }

        // File handle (_lock_handle or _lock_guard) drops automatically after this,
        // closing the underlying OS file descriptor/handle
    }
}

/// Error type for lock acquisition (Unix only)
#[cfg(unix)]
#[derive(Debug)]
enum LockError {
    /// Lock held by another process (file returned for retry)
    Busy(std::fs::File),
    /// Unrecoverable error
    Error(String),
}

/// Result of attempting to acquire platform lock (non-Unix platforms)
#[cfg(not(unix))]
#[derive(Debug)]
enum LockResult {
    /// Lock successfully acquired
    Acquired,
    /// Lock held by another process (retry possible)
    Busy,
    /// Unrecoverable error
    Error(String),
}

/// Result of state loading operation
#[derive(Debug)]
pub struct LoadStateResult {
    /// Loaded release state
    pub state: ReleaseState,
    /// Whether state was recovered from backup
    pub recovered_from_backup: bool,
    /// Any warnings during loading
    pub warnings: Vec<String>,
}

/// Result of state saving operation
#[derive(Debug)]
pub struct SaveStateResult {
    /// Whether save was successful
    pub success: bool,
    /// Size of saved state file in bytes
    pub file_size_bytes: u64,
    /// Duration of save operation
    pub save_duration: Duration,
}

impl StateManager {
    /// Create a new state manager
    pub fn new<P: AsRef<Path>>(state_file_path: P) -> Result<Self> {
        let state_file_path = state_file_path.as_ref().to_path_buf();
        let lock_file_path = state_file_path.with_extension("lock");

        Ok(Self {
            state_file_path,
            lock_file_path,
            lock_handle: None,
            config: StateConfig::default(),
        })
    }

    /// Create a state manager with custom configuration
    pub fn with_config<P: AsRef<Path>>(state_file_path: P, config: StateConfig) -> Result<Self> {
        let state_file_path = state_file_path.as_ref().to_path_buf();
        let lock_file_path = state_file_path.with_extension("lock");

        Ok(Self {
            state_file_path,
            lock_file_path,
            lock_handle: None,
            config,
        })
    }

    /// Save release state to file with optimistic concurrency control
    ///
    /// Uses version checking to detect concurrent modifications (TOCTTOU protection).
    /// This provides defense-in-depth even if file locks fail (see task 004).
    pub async fn save_state(&mut self, state: &mut ReleaseState) -> Result<SaveStateResult> {
        let start_time = SystemTime::now();

        // Acquire lock (primary defense)
        self.acquire_lock().await?;

        // TOCTTOU Protection: Read current version from disk (secondary defense)
        // This detects concurrent modifications even if lock acquisition had bugs
        let current_version = if self.state_file_path.exists() {
            match self.load_from_file(&self.state_file_path) {
                Ok(loaded) => loaded.save_version,
                Err(_) => {
                    // File exists but corrupted/unreadable - treat as version 0
                    // Allow overwrite since we can't read current version
                    0
                }
            }
        } else {
            // File doesn't exist yet - start at version 0
            0
        };

        // Verify we're not clobbering a newer version
        // Allow state.save_version == current_version (for first save of new state)
        if state.save_version < current_version {
            return Err(StateError::ConcurrentModification {
                expected: state.save_version,
                found: current_version,
            }
            .into());
        }

        // Validate state before saving
        if self.config.validate_on_load {
            state.validate()?;
        }

        // Create new state with incremented version
        let mut state_to_save = state.clone();
        state_to_save.save_version = current_version + 1;
        state_to_save.updated_at = chrono::Utc::now();

        // Serialize state with new version
        let serialized = serde_json::to_string_pretty(&state_to_save)
            .map_err(|e| StateError::SaveFailed {
                reason: format!("Failed to serialize state: {}", e),
            })?;

        // Write to temporary file first (atomic operation)
        let temp_file_path = self.state_file_path.with_extension("tmp");

        {
            let mut file = fs::File::create(&temp_file_path)
                .map_err(|e| StateError::SaveFailed {
                    reason: format!("Failed to create temp file: {}", e),
                })?;

            file.write_all(serialized.as_bytes())
                .map_err(|e| StateError::SaveFailed {
                    reason: format!("Failed to write state: {}", e),
                })?;

            file.sync_all()
                .map_err(|e| StateError::SaveFailed {
                    reason: format!("Failed to sync file: {}", e),
                })?;
        }

        // Atomic rename (third-party process could theoretically read old file here,
        // but version check on next save will catch any corruption)
        fs::rename(&temp_file_path, &self.state_file_path)
            .map_err(|e| StateError::SaveFailed {
                reason: format!("Failed to rename temp file: {}", e),
            })?;

        // Get file size
        let file_size_bytes = fs::metadata(&self.state_file_path)
            .map(|m| m.len())
            .unwrap_or(0);

        let save_duration = start_time.elapsed().unwrap_or_default();

        // Update the original state's save_version to keep it synchronized with disk
        // This prevents false concurrent modification errors on subsequent saves
        state.save_version = state_to_save.save_version;
        state.updated_at = state_to_save.updated_at;

        Ok(SaveStateResult {
            success: true,
            file_size_bytes,
            save_duration,
        })
    }

    /// Load release state from file
    pub async fn load_state(&mut self) -> Result<LoadStateResult> {
        // Acquire lock
        self.acquire_lock().await?;

        let warnings = Vec::new();
        let recovered_from_backup = false;

        // Load from main state file
        let state =
            self.load_from_file(&self.state_file_path)
                .map_err(|e| StateError::LoadFailed {
                    reason: format!("Failed to load state file: {}", e),
                })?;

        // Validate loaded state
        if self.config.validate_on_load {
            state.validate()?;
        }

        Ok(LoadStateResult {
            state,
            recovered_from_backup,
            warnings,
        })
    }

    /// Check if state file exists
    pub fn state_exists(&self) -> bool {
        self.state_file_path.exists()
    }

    /// Delete state files
    pub fn cleanup_state(&self) -> Result<()> {
        let mut errors = Vec::new();

        // Remove main state file
        if self.state_file_path.exists()
            && let Err(e) = fs::remove_file(&self.state_file_path)
        {
            errors.push(format!("Failed to remove state file: {}", e));
        }

        // Remove lock file
        if self.lock_file_path.exists()
            && let Err(e) = fs::remove_file(&self.lock_file_path)
        {
            errors.push(format!("Failed to remove lock file: {}", e));
        }

        if !errors.is_empty() {
            return Err(StateError::SaveFailed {
                reason: format!("Cleanup errors: {}", errors.join("; ")),
            }
            .into());
        }

        Ok(())
    }

    /// Get state file information
    pub fn get_state_info(&self) -> Result<StateFileInfo> {
        let main_info = if self.state_file_path.exists() {
            let metadata =
                fs::metadata(&self.state_file_path).map_err(|e| StateError::LoadFailed {
                    reason: format!("Failed to get state file metadata: {}", e),
                })?;

            Some(FileInfo {
                size_bytes: metadata.len(),
                modified_at: metadata.modified().ok(),
                created_at: metadata.created().ok(),
            })
        } else {
            None
        };

        let is_locked = self.lock_file_path.exists();

        Ok(StateFileInfo {
            state_file_path: self.state_file_path.clone(),
            main_file_info: main_info,
            is_locked,
        })
    }

    /// Check if another process has locked the state
    pub fn is_locked_by_other_process(&self) -> bool {
        if !self.lock_file_path.exists() {
            return false;
        }

        // Try to read lock file
        match fs::read_to_string(&self.lock_file_path) {
            Ok(content) => {
                // Try parsing as JSON first (new format)
                if let Ok(lock_info) = serde_json::from_str::<serde_json::Value>(&content)
                    && let Some(pid) = lock_info["pid"].as_u64()
                {
                    return pid as u32 != std::process::id();
                }
                // Fall back to plain PID format (backward compatibility)
                if let Ok(pid) = content.trim().parse::<u32>() {
                    return pid != std::process::id();
                }
                false
            }
            Err(_) => false,
        }
    }

    /// Check if existing lock file is stale (process no longer exists)
    ///
    /// Returns:
    /// - `Ok(true)` if lock file exists but process is dead (safe to remove)
    /// - `Ok(false)` if no lock file, or lock is held by living process
    /// - Conservative: returns `Ok(false)` on any read/parse errors
    fn is_lock_stale(&self) -> Result<bool> {
        // No lock file = not stale
        if !self.lock_file_path.exists() {
            return Ok(false);
        }
        
        // Try to read lock file
        let content = match fs::read_to_string(&self.lock_file_path) {
            Ok(c) => c,
            Err(_) => return Ok(false),  // Can't read = assume valid (conservative)
        };
        
        // Parse JSON to extract PID
        let lock_data: serde_json::Value = match serde_json::from_str(&content) {
            Ok(d) => d,
            Err(_) => return Ok(false),  // Can't parse = assume valid
        };
        
        let pid = match lock_data["pid"].as_u64() {
            Some(p) => p as u32,
            None => return Ok(false),  // No PID field = assume valid
        };
        
        // Check if process exists using sysinfo (cross-platform)
        // Pattern from temp_clone.rs:256-286
        let mut sys = System::new_all();
        sys.refresh_all();
        
        let process_exists = sys.process(Pid::from_u32(pid)).is_some();
        
        // Stale if process doesn't exist
        Ok(!process_exists)
    }

    /// Force remove lock (use with caution)
    pub fn force_unlock(&mut self) -> Result<()> {
        // Drop existing lock (triggers FileLock::drop which calls UnlockFileEx on Windows)
        self.lock_handle = None;

        // Remove lock metadata file
        if self.lock_file_path.exists() {
            fs::remove_file(&self.lock_file_path).map_err(|e| StateError::SaveFailed {
                reason: format!("Failed to remove lock file: {}", e),
            })?;
        }

        Ok(())
    }

    /// Load state from specific file
    fn load_from_file(&self, file_path: &Path) -> Result<ReleaseState> {
        let mut file = fs::File::open(file_path).map_err(|e| StateError::LoadFailed {
            reason: format!("Failed to open file {}: {}", file_path.display(), e),
        })?;

        let mut contents = String::new();
        file.read_to_string(&mut contents)
            .map_err(|e| StateError::LoadFailed {
                reason: format!("Failed to read file {}: {}", file_path.display(), e),
            })?;

        let state: ReleaseState =
            serde_json::from_str(&contents).map_err(|e| StateError::Corrupted {
                reason: format!("Failed to deserialize state: {}", e),
            })?;

        Ok(state)
    }

    /// Acquire file lock using platform-specific advisory locking
    async fn acquire_lock(&mut self) -> Result<()> {
        if self.lock_handle.is_some() {
            return Ok(()); // Already locked
        }

        let start_time = SystemTime::now();
        let timeout = Duration::from_millis(self.config.lock_timeout_ms);

        loop {
            // Check timeout
            if start_time.elapsed().unwrap_or_default() >= timeout {
                return Err(StateError::SaveFailed {
                    reason: "Timeout waiting for file lock".to_string(),
                }
                .into());
            }

            // Check for stale locks before attempting acquisition
            if self.lock_file_path.exists() && self.is_lock_stale()? {
                // Lock is stale - safe to remove
                let _ = fs::remove_file(&self.lock_file_path);
            }

            // Step 1: Open or create lock file
            #[cfg(unix)]
            let file = match fs::OpenOptions::new()
                .read(true)
                .write(true)
                .create(true)
                .truncate(true)
                .open(&self.lock_file_path)
            {
                Ok(f) => f,
                Err(e) => {
                    return Err(StateError::SaveFailed {
                        reason: format!("Failed to open lock file: {}", e),
                    }
                    .into());
                }
            };
            
            #[cfg(not(unix))]
            let mut file = match fs::OpenOptions::new()
                .read(true)
                .write(true)
                .create(true)
                .truncate(true)
                .open(&self.lock_file_path)
            {
                Ok(f) => f,
                Err(e) => {
                    return Err(StateError::SaveFailed {
                        reason: format!("Failed to open lock file: {}", e),
                    }
                    .into());
                }
            };

            // Step 2: Try to acquire platform-specific lock
            #[cfg(unix)]
            {
                // Unix: Use RAII Flock wrapper (takes ownership)
                match try_platform_lock(file) {
                    Ok(mut flock_guard) => {
                        // Step 3: Write lock metadata using auto-deref to underlying File
                        write_lock_metadata(&mut flock_guard)?;

                        // Step 4: Store lock guard (keeps lock alive, auto-unlocks on drop)
                        self.lock_handle = Some(FileLock {
                            _lock_file: self.lock_file_path.clone(),
                            _pid: std::process::id(),
                            _acquired_at: SystemTime::now(),
                            _lock_guard: flock_guard,
                        });

                        return Ok(());
                    }
                    Err(LockError::Busy(returned_file)) => {
                        // Lock held by another process - wait and retry
                        drop(returned_file);
                        tokio::time::sleep(Duration::from_millis(100)).await;
                        continue;
                    }
                    Err(LockError::Error(msg)) => {
                        return Err(StateError::SaveFailed {
                            reason: format!("Lock acquisition failed: {}", msg),
                        }
                        .into());
                    }
                }
            }

            #[cfg(windows)]
            {
                // Windows: Use LockFileEx with borrowed file
                match try_platform_lock(&file) {
                    LockResult::Acquired => {
                        // Step 3: Write lock metadata
                        write_lock_metadata(&mut file)?;

                        // Step 4: Store lock handle (keeps lock alive)
                        self.lock_handle = Some(FileLock {
                            _lock_file: self.lock_file_path.clone(),
                            _pid: std::process::id(),
                            _acquired_at: SystemTime::now(),
                            _lock_handle: file,
                            _is_locked: true,
                        });

                        return Ok(());
                    }
                    LockResult::Busy => {
                        // Lock held by another process - wait and retry
                        drop(file);
                        tokio::time::sleep(Duration::from_millis(100)).await;
                        continue;
                    }
                    LockResult::Error(msg) => {
                        return Err(StateError::SaveFailed {
                            reason: format!("Lock acquisition failed: {}", msg),
                        }
                        .into());
                    }
                }
            }

            #[cfg(not(any(unix, windows)))]
            {
                // Fallback: Use age-based stale detection
                match try_platform_lock_with_metadata(&file, self.config.stale_lock_timeout_seconds) {
                    LockResult::Acquired => {
                        // Step 3: Write lock metadata
                        write_lock_metadata(&mut file)?;

                        // Step 4: Store lock handle (keeps lock alive)
                        self.lock_handle = Some(FileLock {
                            _lock_file: self.lock_file_path.clone(),
                            _pid: std::process::id(),
                            _acquired_at: SystemTime::now(),
                            _lock_handle: file,
                        });

                        return Ok(());
                    }
                    LockResult::Busy => {
                        // Lock held by another process - wait and retry
                        drop(file);
                        tokio::time::sleep(Duration::from_millis(100)).await;
                        continue;
                    }
                    LockResult::Error(msg) => {
                        return Err(StateError::SaveFailed {
                            reason: format!("Lock acquisition failed: {}", msg),
                        }
                        .into());
                    }
                }
            }
        }
    }
}

/// Write lock metadata to file (called after successful lock acquisition)
fn write_lock_metadata(file: &mut std::fs::File) -> Result<()> {
    let pid = std::process::id();
    let acquired_at = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|e| StateError::SaveFailed {
            reason: format!("System time error: {}", e),
        })?
        .as_secs();

    let lock_data = serde_json::json!({
        "pid": pid,
        "acquired_at": acquired_at,
    });

    let lock_data_str = serde_json::to_string(&lock_data)
        .map_err(|e| StateError::SaveFailed {
            reason: format!("Failed to serialize lock data: {}", e),
        })?;

    // Truncate and write atomically
    file.set_len(0)
        .map_err(|e| StateError::SaveFailed {
            reason: format!("Failed to truncate lock file: {}", e),
        })?;
    
    file.write_all(lock_data_str.as_bytes())
        .map_err(|e| StateError::SaveFailed {
            reason: format!("Failed to write lock file: {}", e),
        })?;
    
    file.sync_all()
        .map_err(|e| StateError::SaveFailed {
            reason: format!("Failed to sync lock file: {}", e),
        })?;

    Ok(())
}

/// Try to acquire exclusive lock using Flock RAII wrapper (Unix)
#[cfg(unix)]
fn try_platform_lock(file: std::fs::File) -> std::result::Result<nix::fcntl::Flock<std::fs::File>, LockError> {
    use nix::fcntl::{Flock, FlockArg};

    match Flock::lock(file, FlockArg::LockExclusiveNonblock) {
        Ok(guard) => Ok(guard),
        Err((file, e)) if e == nix::errno::Errno::EWOULDBLOCK => {
            Err(LockError::Busy(file))
        }
        Err((_file, e)) => {
            Err(LockError::Error(format!("flock error: {}", e)))
        }
    }
}

// Windows error code from winerror.h
// Returned when file is already locked by another process
#[cfg(windows)]
const ERROR_LOCK_VIOLATION: u32 = 33;

/// Try to acquire exclusive lock using LockFileEx (Windows)
#[cfg(windows)]
fn try_platform_lock(file: &std::fs::File) -> LockResult {
    use std::os::windows::io::AsRawHandle;
    use windows::Win32::Foundation::HANDLE;
    use windows::Win32::Storage::FileSystem::{
        LockFileEx, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY,
    };
    use windows::Win32::System::IO::OVERLAPPED;

    let raw_handle = file.as_raw_handle();
    let handle = HANDLE(raw_handle as isize);
    
    // Initialize OVERLAPPED for synchronous file locking.
    // Per Microsoft documentation: For synchronous operations (file not opened 
    // with FILE_FLAG_OVERLAPPED), OVERLAPPED structure should have:
    // - Offset/OffsetHigh: Byte offset to start locking (0 = start of file)
    // - hEvent: NULL for synchronous operations
    // Default::default() provides correct zero-initialization.
    // Reference: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex
    let mut overlapped = OVERLAPPED::default();

    // SAFETY: This unsafe block calls LockFileEx with the following guarantees:
    // 
    // 1. Handle validity:
    //    - `file` is a valid reference to an open File with exclusive ownership
    //    - The File owns the underlying OS handle returned by as_raw_handle()
    //    - The borrow ensures no other code can close the file during this call
    //    - The handle remains valid for the duration of the unsafe block
    //
    // 2. OVERLAPPED initialization:
    //    - Zero-initialized via Default::default()
    //    - Offset = 0, OffsetHigh = 0: Lock from start of file
    //    - hEvent = NULL: Required for synchronous locking
    //    - Internal/InternalHigh: Unused for synchronous file locking
    //    - Per MSDN: This initialization is correct for synchronous LockFileEx
    //
    // 3. Lock parameters:
    //    - LOCKFILE_EXCLUSIVE_LOCK: Request exclusive (write) lock
    //    - LOCKFILE_FAIL_IMMEDIATELY: Non-blocking operation (returns immediately)
    //    - dwReserved: Must be 0 per MSDN specification
    //    - nNumberOfBytesToLockLow/High: Both u32::MAX
    //      Combined with Offset=0, this locks entire file from byte 0 to 2^64-1
    //      (standard practice for advisory whole-file locks)
    //
    // 4. OVERLAPPED reference validity:
    //    - `overlapped` is stack-allocated and lives for entire unsafe block
    //    - Mutable reference is valid and exclusive (no aliasing)
    //    - LockFileEx does not retain the pointer beyond the call
    //
    // Reference: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex
    unsafe {
        match LockFileEx(
            handle,
            LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
            0,        // Reserved (must be 0 per MSDN)
            u32::MAX, // nNumberOfBytesToLockLow (lock entire file from offset 0)
            u32::MAX, // nNumberOfBytesToLockHigh (lock to end: 2^64-1 bytes)
            &mut overlapped,
        ) {
            Ok(()) => LockResult::Acquired,
            Err(e) => {
                let code = e.code().0 as u32;
                if code == ERROR_LOCK_VIOLATION {
                    LockResult::Busy
                } else {
                    LockResult::Error(format!("LockFileEx error {}: {:?}", code, e))
                }
            }
        }
    }
}

/// Try to acquire lock using age-based stale detection (fallback)
#[cfg(not(any(unix, windows)))]
fn try_platform_lock_with_metadata(
    file: &std::fs::File,
    stale_timeout_secs: u64,
) -> LockResult {
    // Check if existing lock file is stale based on modification time
    let is_stale = file
        .metadata()
        .ok()
        .and_then(|m| m.modified().ok())
        .and_then(|t| t.elapsed().ok())
        .map(|d| d.as_secs() > stale_timeout_secs)
        .unwrap_or(false);

    if is_stale {
        LockResult::Acquired
    } else {
        LockResult::Busy
    }
}

impl StateManager {
    /// Update configuration
    pub fn set_config(&mut self, config: StateConfig) {
        self.config = config;
    }

    /// Get configuration
    pub fn config(&self) -> &StateConfig {
        &self.config
    }
}

impl Drop for StateManager {
    fn drop(&mut self) {
        // Release lock when manager is dropped
        if self.lock_handle.is_some() {
            let _ = fs::remove_file(&self.lock_file_path);
        }
    }
}

/// Information about state files
#[derive(Debug, Clone)]
pub struct StateFileInfo {
    /// Path to main state file
    pub state_file_path: PathBuf,
    /// Information about main state file
    pub main_file_info: Option<FileInfo>,
    /// Whether state is currently locked
    pub is_locked: bool,
}

/// Information about a single file
#[derive(Debug, Clone)]
pub struct FileInfo {
    /// File size in bytes
    pub size_bytes: u64,
    /// Last modified time
    pub modified_at: Option<SystemTime>,
    /// Created time
    pub created_at: Option<SystemTime>,
}

impl StateFileInfo {
    /// Check if state files exist
    pub fn has_state(&self) -> bool {
        self.main_file_info.is_some()
    }

    /// Get total size of all state files
    pub fn total_size_bytes(&self) -> u64 {
        self.main_file_info
            .as_ref()
            .map(|f| f.size_bytes)
            .unwrap_or(0)
    }

    /// Format state info for display
    pub fn format_info(&self) -> String {
        let mut info = String::new();

        if let Some(main_info) = &self.main_file_info {
            info.push_str(&format!("State: {} bytes", main_info.size_bytes));
            if let Some(modified) = main_info.modified_at
                && let Ok(elapsed) = modified.elapsed()
            {
                info.push_str(&format!(" (modified {}s ago)", elapsed.as_secs()));
            }
        } else {
            info.push_str("No state file");
        }

        if self.is_locked {
            info.push_str(" [LOCKED]");
        }

        info
    }
}

impl SaveStateResult {
    /// Format save result for display
    pub fn format_result(&self) -> String {
        if self.success {
            format!(
                "✅ State saved: {} bytes in {:.2}s",
                self.file_size_bytes,
                self.save_duration.as_secs_f64()
            )
        } else {
            "❌ Failed to save state".to_string()
        }
    }
}

impl LoadStateResult {
    /// Format load result for display
    pub fn format_result(&self) -> String {
        let mut result = if self.recovered_from_backup {
            "⚠️ State loaded from backup".to_string()
        } else {
            "✅ State loaded successfully".to_string()
        };

        if !self.warnings.is_empty() {
            result.push_str(&format!(" ({} warnings)", self.warnings.len()));
        }

        result
    }
}
