//! Configuration persistence module
//!
//! Provides functionality to save and load port configurations to/from disk.
//!
//! **IMPORTANT**: This module is designed for frontend use (TUI/GUI/WebUI). CLI processes
//! should NOT use this persistence layer to avoid communication conflicts and race
//! conditions. The configuration file is stored in a platform-appropriate location.
//!
//! ## --no-config-cache flag
//!
//! When the application is started with `--no-config-cache`, all save/load operations are
//! skipped. This is useful for E2E tests to ensure clean state without cache
//! interference. Call `set_no_cache(true)` early in application startup to enable this.

use anyhow::{Context, Result};
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};
use std::{
    collections::HashMap,
    fs,
    path::PathBuf,
    sync::atomic::{AtomicBool, Ordering},
};

use crate::protocol::status::types::{
    modbus::{ModbusMasterDataSource, ModbusRegisterItem},
    port::PortConfig,
};

/// Global flag to disable config cache (set via --no-config-cache)
static NO_CONFIG_CACHE: AtomicBool = AtomicBool::new(false);

/// Global config file path (set via --config-file)
static CONFIG_FILE_PATH: OnceCell<Option<PathBuf>> = OnceCell::new();

/// Set the no-cache flag (should be called early in TUI startup)
///
/// # Parameters
/// - `enabled`: true to disable cache, false to enable cache
///
/// # Example
/// ```rust,ignore
/// // In TUI startup (src/tui/mod.rs):
/// let no_cache = matches.get_flag("no-config-cache");
/// persistence::set_no_cache(no_cache);
/// ```
pub fn set_no_cache(enabled: bool) {
    NO_CONFIG_CACHE.store(enabled, Ordering::SeqCst);
    if enabled {
        log::info!("🚫 Config cache disabled (--no-config-cache)");
    }
}

/// Set the config file path (should be called early in TUI startup)
///
/// # Parameters
/// - `path`: Optional config file path. If None, TUI operates in temporary mode.
///
/// # Example
/// ```rust,ignore
/// // In TUI startup (src/tui/runtime.rs):
/// let config_path = matches.get_one::<String>("config-file").map(PathBuf::from);
/// persistence::set_config_path(config_path);
/// ```
pub fn set_config_path(path: Option<PathBuf>) {
    if CONFIG_FILE_PATH.set(path.clone()).is_err() {
        log::warn!("⚠️ Config path already set, ignoring new value");
        return;
    }
    if let Some(ref p) = path {
        log::info!("📂 Config file path set to: {}", p.display());
    } else {
        log::info!("⚡ TUI starting in temporary mode (no config file specified)");
    }
}

/// Get the configured config file path
pub fn get_config_path_setting() -> Option<PathBuf> {
    CONFIG_FILE_PATH.get().and_then(|p| p.clone())
}

/// Get the current no-cache flag value
fn is_no_cache() -> bool {
    NO_CONFIG_CACHE.load(Ordering::SeqCst)
}

/// Represents a persisted port configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistedPortConfig {
    pub name: String,
    pub config: SerializablePortConfig,
}

/// Serializable version of PortConfig
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SerializablePortConfig {
    Modbus {
        mode: String, // "Master" or "Slave"
        master_source: Option<SerializableMasterSource>,
        stations: Vec<SerializableStation>,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableMasterSource {
    pub kind: String,
    pub value: Option<String>,
}

/// Serializable version of ModbusRegisterItem
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableStation {
    pub station_id: u8,
    pub register_mode: String, // "Holding", "Input", "Coils", "Discrete"
    pub register_address: u16,
    pub register_length: u16,
    pub last_values: Vec<u16>,
}

/// Get the path to the configuration file
///
/// This configuration file is **TUI-only** and should NOT be used by CLI processes
/// to avoid communication conflicts.
///
/// Returns the explicitly configured path via --config-file, or None if no path was set.
fn get_config_path() -> Result<Option<PathBuf>> {
    Ok(get_config_path_setting())
}

/// Save port configurations to disk (TUI-only)
///
/// This function saves TUI port configurations to the specified config file.
/// CLI processes should NOT call this function.
///
/// Save is automatically skipped if `--no-config-cache` flag is set or no config path is specified.
///
/// # Returns
/// - `Ok(())` if save succeeded or was skipped
/// - `Err` if save failed
pub fn save_port_configs(configs: &HashMap<String, PortConfig>) -> Result<()> {
    if is_no_cache() {
        return Ok(());
    }

    let path = match get_config_path()? {
        Some(p) => p,
        None => {
            return Ok(());
        }
    };

    let persisted: Vec<PersistedPortConfig> = configs
        .iter()
        .map(|(name, config)| {
            let serializable_config = match config {
                PortConfig::Modbus {
                    mode,
                    master_source,
                    stations,
                } => {
                    let mode_str = if mode.is_master() { "Master" } else { "Slave" };
                    let persist_values = mode.is_master();
                    let master_source_serialized = if mode.is_master() {
                        match master_source {
                            ModbusMasterDataSource::Manual => None,
                            ModbusMasterDataSource::MqttServer { url } => {
                                Some(SerializableMasterSource {
                                    kind: "mqtt".to_string(),
                                    value: Some(url.clone()),
                                })
                            }
                            ModbusMasterDataSource::HttpServer { port } => {
                                Some(SerializableMasterSource {
                                    kind: "http".to_string(),
                                    value: Some(port.to_string()),
                                })
                            }
                            ModbusMasterDataSource::IpcPipe { path } => {
                                Some(SerializableMasterSource {
                                    kind: "ipc".to_string(),
                                    value: Some(path.clone()),
                                })
                            }
                            ModbusMasterDataSource::PortForwarding { source_port } => {
                                Some(SerializableMasterSource {
                                    kind: "port_forwarding".to_string(),
                                    value: Some(source_port.clone()),
                                })
                            }
                        }
                    } else {
                        None
                    };

                    let serializable_stations = stations
                        .iter()
                        .map(|station| SerializableStation {
                            station_id: station.station_id,
                            register_mode: format!("{:?}", station.register_mode),
                            register_address: station.register_address,
                            register_length: station.register_length,
                            last_values: if persist_values {
                                station.last_values.clone()
                            } else {
                                Vec::new()
                            },
                        })
                        .collect();

                    SerializablePortConfig::Modbus {
                        mode: mode_str.to_string(),
                        master_source: master_source_serialized,
                        stations: serializable_stations,
                    }
                }
            };

            PersistedPortConfig {
                name: name.clone(),
                config: serializable_config,
            }
        })
        .collect();

    let json =
        serde_json::to_string_pretty(&persisted).context("Failed to serialize port configs")?;

    // Create parent directory if it doesn't exist
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)
            .with_context(|| format!("Failed to create directory {parent:?}"))?;
    }

    fs::write(&path, json).with_context(|| format!("Failed to write config to {path:?}"))?;

    log::info!(
        "💾 Saved {} port configurations to {:?}",
        configs.len(),
        path
    );
    Ok(())
}

/// Load port configurations from disk (TUI-only)
///
/// This function loads TUI port configurations from the specified config file.
/// CLI processes should NOT call this function.
///
/// Load is automatically skipped if `--no-config-cache` flag is set or no config path is specified.
///
/// # Returns
/// - `Ok(HashMap)` with loaded configs, or empty HashMap if skipped/not found
/// - `Err` if load failed
pub fn load_port_configs() -> Result<HashMap<String, PortConfig>> {
    if is_no_cache() {
        return Ok(HashMap::new());
    }

    let path = match get_config_path()? {
        Some(p) => p,
        None => {
            return Ok(HashMap::new());
        }
    };

    if !path.exists() {
        log::info!("📂 Config file does not exist yet: {path:?} (will be created on save)");
        return Ok(HashMap::new());
    }

    let json = fs::read_to_string(&path)
        .with_context(|| format!("Failed to read config from {path:?}"))?;

    let persisted: Vec<PersistedPortConfig> =
        serde_json::from_str(&json).context("Failed to deserialize port configs")?;

    let mut configs = HashMap::new();

    for p in persisted {
        let config = match p.config {
            SerializablePortConfig::Modbus {
                mode,
                master_source,
                stations,
            } => {
                use crate::protocol::status::types::modbus::{
                    ModbusConnectionMode, ModbusMasterDataSource, RegisterMode,
                };

                let mode_enum = if mode == "Master" {
                    ModbusConnectionMode::default_master()
                } else {
                    ModbusConnectionMode::default_slave()
                };

                let master_source_enum = if mode_enum.is_master() {
                    master_source
                        .and_then(|src| {
                            let SerializableMasterSource { kind, value } = src;
                            match kind.as_str() {
                                "mqtt" => Some(ModbusMasterDataSource::MqttServer {
                                    url: value.unwrap_or_default(),
                                }),
                                "http" => Some(ModbusMasterDataSource::HttpServer {
                                    port: value.and_then(|v| v.parse().ok()).unwrap_or(8080),
                                }),
                                "ipc" => Some(ModbusMasterDataSource::IpcPipe {
                                    path: value.unwrap_or_default(),
                                }),
                                "port_forwarding" => Some(ModbusMasterDataSource::PortForwarding {
                                    source_port: value.unwrap_or_default(),
                                }),
                                _ => None,
                            }
                        })
                        .unwrap_or_default()
                } else {
                    ModbusMasterDataSource::default()
                };

                let register_items: Vec<ModbusRegisterItem> = stations
                    .iter()
                    .map(|s| {
                        let register_mode = match s.register_mode.as_str() {
                            "Holding" => RegisterMode::Holding,
                            "Input" => RegisterMode::Input,
                            "Coils" => RegisterMode::Coils,
                            "DiscreteInputs" => RegisterMode::DiscreteInputs,
                            _ => RegisterMode::Holding, // Default fallback
                        };

                        ModbusRegisterItem {
                            station_id: s.station_id,
                            register_mode,
                            register_address: s.register_address,
                            register_length: s.register_length,
                            last_values: s.last_values.clone(),
                            req_success: 0,
                            req_total: 0,
                            next_poll_at: std::time::Instant::now(),
                            last_request_time: None,
                            last_response_time: None,
                            pending_requests: Vec::new(),
                            pending_writes: std::collections::HashMap::new(),
                        }
                    })
                    .collect();

                PortConfig::Modbus {
                    mode: mode_enum,
                    master_source: master_source_enum,
                    stations: register_items,
                }
            }
        };

        configs.insert(p.name, config);
    }

    log::info!(
        "📂 Loaded {} port configurations from {:?}",
        configs.len(),
        path
    );
    Ok(configs)
}
