pub mod master;
pub mod slave;

use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use std::{io::Write, time::Duration};

use crate::protocol::status::types::modbus::{RegisterMode, StationConfig};

/// Convert a byte slice into an uppercase hexadecimal string separated by spaces.
pub(crate) fn format_hex_bytes(bytes: &[u8]) -> String {
    if bytes.is_empty() {
        return String::new();
    }
    bytes
        .iter()
        .map(|byte| format!("{byte:02X}"))
        .collect::<Vec<_>>()
        .join(" ")
}

/// Emit a Modbus IPC log message to the TUI if IPC connections are active.
#[derive(Clone, Debug)]
pub(crate) struct ModbusIpcLogPayload<'a> {
    pub port: &'a str,
    pub direction: &'a str,
    pub frame: &'a [u8],
    pub station_id: Option<u8>,
    pub register_mode: Option<crate::protocol::status::types::modbus::RegisterMode>,
    pub start_address: Option<u16>,
    pub quantity: Option<u16>,
    pub success: Option<bool>,
    pub error: Option<String>,
    pub config_index: Option<u16>,
}

/// Emit a Modbus IPC log message to the TUI if IPC connections are active.
pub(crate) fn emit_modbus_ipc_log(
    ipc_connections: &mut Option<crate::cli::actions::IpcConnections>,
    payload: ModbusIpcLogPayload<'_>,
) {
    if let Some(ref mut ipc) = ipc_connections {
        let _ = ipc
            .status
            .send(&crate::protocol::ipc::IpcMessage::ModbusData {
                port_name: payload.port.to_string(),
                direction: payload.direction.to_string(),
                data: format_hex_bytes(payload.frame),
                timestamp: None,
                station_id: payload.station_id,
                register_mode: payload.register_mode.map(|mode| format!("{mode:?}")),
                start_address: payload.start_address,
                quantity: payload.quantity,
                success: payload.success,
                error: payload.error,
                config_index: payload.config_index,
            });
    }
}

/// Open a serial port with the requested timeout, enabling exclusive access on Unix systems.
pub(crate) fn open_serial_port(
    port: &str,
    baud_rate: u32,
    timeout: Duration,
) -> Result<Box<dyn serialport::SerialPort>> {
    let builder = serialport::new(port, baud_rate).timeout(timeout);

    #[cfg(unix)]
    {
        let mut handle = builder
            .open_native()
            .map_err(|err| anyhow!("Failed to open port {port}: {err}"))?;
        handle
            .set_exclusive(true)
            .map_err(|err| anyhow!("Failed to acquire exclusive access to {port}: {err}"))?;
        Ok(Box::new(handle))
    }

    #[cfg(not(unix))]
    {
        builder
            .open()
            .map_err(|err| anyhow!("Failed to open port {port}: {err}"))
    }
}

/// Response structure for modbus operations
#[derive(Serialize, Deserialize, Clone)]
pub struct ModbusResponse {
    pub station_id: u8,
    pub register_address: u16,
    pub register_mode: String,
    pub values: Vec<u16>,
    pub timestamp: String,
}

/// Data source for master mode
#[derive(Clone)]
pub enum DataSource {
    Manual,
    File(String),
    Pipe(String),
    MqttServer(String), // URL
    HttpServer(u16),    // Port
    IpcPipe(String),    // pipe path
}

impl std::str::FromStr for DataSource {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s == "manual" {
            Ok(DataSource::Manual)
        } else if let Some(path) = s.strip_prefix("file:") {
            Ok(DataSource::File(path.to_string()))
        } else if let Some(name) = s.strip_prefix("pipe:") {
            Ok(DataSource::Pipe(name.to_string()))
        } else if let Some(url) = s.strip_prefix("mqtt://") {
            Ok(DataSource::MqttServer(format!("mqtt://{}", url)))
        } else if let Some(url) = s.strip_prefix("mqtts://") {
            Ok(DataSource::MqttServer(format!("mqtts://{}", url)))
        } else if let Some(port_str) = s.strip_prefix("http://") {
            // Parse port number from http://PORT format
            let port: u16 = port_str.parse().map_err(|_| {
                anyhow!("Invalid HTTP port number. Use: http://<port> (e.g., http://8080)")
            })?;
            Ok(DataSource::HttpServer(port))
        } else if let Some(path) = s.strip_prefix("ipc:") {
            Ok(DataSource::IpcPipe(path.to_string()))
        } else {
            Err(anyhow!(
                "Invalid data source format. Use: manual, mqtt://<url>, http://<port>, ipc:<path>, or file:<path>"
            ))
        }
    }
}

/// Output sink for slave mode
pub enum OutputSink {
    Stdout,
    File(String),
    Pipe(String),
}

impl std::str::FromStr for OutputSink {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Some(path) = s.strip_prefix("file:") {
            Ok(OutputSink::File(path.to_string()))
        } else if let Some(name) = s.strip_prefix("pipe:") {
            Ok(OutputSink::Pipe(name.to_string()))
        } else {
            Err(anyhow!(
                "Invalid output format. Use file:<path> or pipe:<name>"
            ))
        }
    }
}

impl OutputSink {
    /// Write output to the sink
    pub fn write(&self, data: &str) -> Result<()> {
        match self {
            OutputSink::Stdout => {
                println!("{data}");
                Ok(())
            }
            OutputSink::File(path) => {
                let mut file = std::fs::OpenOptions::new()
                    .create(true)
                    .append(true)
                    .open(path)?;
                writeln!(file, "{data}")?;
                Ok(())
            }
            OutputSink::Pipe(path) => {
                let mut file = std::fs::OpenOptions::new().write(true).open(path)?;
                writeln!(file, "{data}")?;
                Ok(())
            }
        }
    }
}

/// Parse register mode from string
pub fn parse_register_mode(
    mode: &str,
) -> Result<crate::protocol::status::types::modbus::RegisterMode> {
    use crate::protocol::status::types::modbus::RegisterMode;
    match mode.to_lowercase().as_str() {
        "holding" => Ok(RegisterMode::Holding),
        "input" => Ok(RegisterMode::Input),
        "coils" => Ok(RegisterMode::Coils),
        "discrete" => Ok(RegisterMode::DiscreteInputs),
        _ => Err(anyhow!("Invalid register mode: {mode}")),
    }
}

/// Parse a data line in JSON format
pub fn parse_data_line(
    line: &str,
    station_id: u8,
    register_mode: RegisterMode,
    start_address: u16,
    register_length: u16,
) -> Result<Vec<u16>> {
    let line = line.trim();
    if line.is_empty() {
        return Err(anyhow!("Empty line"));
    }

    match serde_json::from_str::<Vec<StationConfig>>(line) {
        Ok(stations) => {
            return extract_values_from_station_configs(
                &stations,
                station_id,
                register_mode,
                start_address,
                register_length,
            );
        }
        Err(e) => {
            log::debug!(
                "Failed to parse as Vec<StationConfig>: {}. Trying legacy format...",
                e
            );
        }
    }

    // Fallback: legacy format {"values": [...]}
    if let Ok(json) = serde_json::from_str::<serde_json::Value>(line) {
        if let Some(values) = json.get("values") {
            if let Some(arr) = values.as_array() {
                let mut result = Vec::new();
                for val in arr {
                    if let Some(num) = val.as_u64() {
                        result.push(num as u16);
                    }
                }
                return Ok(result);
            }
        }
    }

    Err(anyhow!("Invalid data format: could not parse as Vec<StationConfig> or legacy {{\"values\": [...]}} format"))
}

pub(crate) fn extract_values_from_station_configs(
    stations: &[StationConfig],
    station_id: u8,
    register_mode: RegisterMode,
    start_address: u16,
    register_length: u16,
) -> Result<Vec<u16>> {
    if register_length == 0 {
        return Ok(Vec::new());
    }

    let station = stations
        .iter()
        .find(|station| station.station_id == station_id)
        .ok_or_else(|| anyhow!("Station {station_id} not found in data source payload"))?;

    let ranges = match register_mode {
        RegisterMode::Coils => &station.map.coils,
        RegisterMode::DiscreteInputs => &station.map.discrete_inputs,
        RegisterMode::Holding => &station.map.holding,
        RegisterMode::Input => &station.map.input,
    };

    let range = ranges
        .iter()
        .find(|range| {
            let end_address = range
                .address_start
                .saturating_add(range.length.saturating_sub(1));
            range.address_start <= start_address
                && end_address
                    >= start_address.saturating_add(register_length.saturating_sub(1))
        })
        .ok_or_else(|| {
            anyhow!(
                "Register range for station {station_id} does not cover address {start_address} (len {register_length})"
            )
        })?;

    if start_address < range.address_start {
        return Err(anyhow!(
            "Register range for station {station_id} starts at {} but requested address {start_address}",
            range.address_start
        ));
    }

    let offset = (start_address - range.address_start) as usize;
    let total_available = range.length.saturating_sub(offset as u16) as usize;

    if total_available < register_length as usize {
        return Err(anyhow!(
            "Register range length {} too small for requested length {} at offset {}",
            range.length,
            register_length,
            offset
        ));
    }

    let mut values = Vec::with_capacity(register_length as usize);
    let mut iter = range.initial_values.iter().skip(offset);
    for _ in 0..register_length {
        values.push(iter.next().copied().unwrap_or(0));
    }

    Ok(values)
}

/// Extract values from modbus storage
pub fn extract_values_from_storage(
    storage: &std::sync::Arc<std::sync::Mutex<rmodbus::server::storage::ModbusStorageSmall>>,
    start_addr: u16,
    length: u16,
    reg_mode: crate::protocol::status::types::modbus::RegisterMode,
) -> Result<Vec<u16>> {
    use rmodbus::server::context::ModbusContext;

    let storage = storage.lock().unwrap();
    let mut values = Vec::new();

    for i in 0..length {
        let addr = start_addr + i;
        let value = match reg_mode {
            crate::protocol::status::types::modbus::RegisterMode::Holding => {
                storage.get_holding(addr)?
            }
            crate::protocol::status::types::modbus::RegisterMode::Input => {
                storage.get_input(addr)?
            }
            crate::protocol::status::types::modbus::RegisterMode::Coils => {
                if storage.get_coil(addr)? {
                    1
                } else {
                    0
                }
            }
            crate::protocol::status::types::modbus::RegisterMode::DiscreteInputs => {
                if storage.get_discrete(addr)? {
                    1
                } else {
                    0
                }
            }
        };
        values.push(value);
    }

    Ok(values)
}

/// Build a StationConfig snapshot by reading current values from `storage`.
///
/// This clones the provided `station` and replaces each `RegisterRange`'s
/// `initial_values` with the values read from `storage` for that range.
pub fn build_station_snapshot_from_storage(
    storage: &std::sync::Arc<std::sync::Mutex<rmodbus::server::storage::ModbusStorageSmall>>,
    station: &crate::protocol::status::types::modbus::StationConfig,
) -> Result<crate::protocol::status::types::modbus::StationConfig> {
    use crate::protocol::status::types::modbus::RegisterMode;

    let mut sc = station.clone();

    for range in sc.map.holding.iter_mut() {
        let vals = extract_values_from_storage(
            storage,
            range.address_start,
            range.length,
            RegisterMode::Holding,
        )?;
        range.initial_values = vals;
    }

    for range in sc.map.coils.iter_mut() {
        let vals = extract_values_from_storage(
            storage,
            range.address_start,
            range.length,
            RegisterMode::Coils,
        )?;
        range.initial_values = vals;
    }

    for range in sc.map.discrete_inputs.iter_mut() {
        let vals = extract_values_from_storage(
            storage,
            range.address_start,
            range.length,
            RegisterMode::DiscreteInputs,
        )?;
        range.initial_values = vals;
    }

    for range in sc.map.input.iter_mut() {
        let vals = extract_values_from_storage(
            storage,
            range.address_start,
            range.length,
            RegisterMode::Input,
        )?;
        range.initial_values = vals;
    }

    Ok(sc)
}
