//
// Copyright (C) 2024 - 2025 Automated Design Corp. All Rights Reserved.
//

//! CommandMessage - the core message type for IPC communication.
//!
//! This is the unified message format used for all communication between:
//! - autocore-server and external modules (over TCP)
//! - Internal servelets within autocore-server
//! - WebSocket clients and the backend
//!
//! ## Topic Format (FQDN)
//!
//! Topics use a dot-separated fully-qualified naming convention:
//! - `ads.plc1.GM.stDataResults` - ADS symbol access
//! - `modbus.server1.holding_0` - Modbus register
//! - `python.load_project` - Python command
//! - `gnv.app.version` - Config value
//!
//! The first segment typically identifies the module/domain.

use std::sync::atomic::{AtomicU32, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};

use crc32fast::Hasher;
use num_derive::{FromPrimitive, ToPrimitive};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use serde_json::Value;

/// Global counter for generating unique transaction IDs
static TRANSACTION_ID_COUNTER: AtomicU32 = AtomicU32::new(1);

/// Generate a unique transaction ID
pub fn next_transaction_id() -> u32 {
    TRANSACTION_ID_COUNTER.fetch_add(1, Ordering::SeqCst)
}

/// Get the current timestamp in milliseconds since UNIX epoch
pub fn current_timecode() -> i64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_millis() as i64)
        .unwrap_or(0)
}

/// Message types for IPC communication.
///
/// These define the semantic meaning of each message in the protocol.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr, FromPrimitive, ToPrimitive)]
#[repr(u8)]
pub enum MessageType {
    /// Invalid or no operation. Used for keepalive/ping.
    NoOp = 0,

    /// Response to a previous request.
    Response = 1,

    /// Read request - module should return data for the specified topic.
    Read = 2,

    /// Write request - module should store the provided data.
    Write = 3,

    /// Subscribe to updates on a topic.
    Subscribe = 4,

    /// Unsubscribe from a topic.
    Unsubscribe = 5,

    /// Broadcast message (unsolicited push from module to server or vice versa).
    Broadcast = 6,

    /// Heartbeat/keepalive message.
    Heartbeat = 7,

    /// Control message (initialize, finalize, configuration).
    Control = 8,

    /// Generic request - module parses topic to determine action.
    Request = 10,
}

impl Default for MessageType {
    fn default() -> Self {
        MessageType::NoOp
    }
}

impl From<u8> for MessageType {
    fn from(value: u8) -> Self {
        match value {
            0 => MessageType::NoOp,
            1 => MessageType::Response,
            2 => MessageType::Read,
            3 => MessageType::Write,
            4 => MessageType::Subscribe,
            5 => MessageType::Unsubscribe,
            6 => MessageType::Broadcast,
            7 => MessageType::Heartbeat,
            8 => MessageType::Control,
            10 => MessageType::Request,
            _ => MessageType::NoOp,
        }
    }
}

impl From<MessageType> for u8 {
    fn from(value: MessageType) -> Self {
        value as u8
    }
}

/// CommandMessage is the unified message format for all IPC communication.
///
/// This struct serves as both request and response, with `message_type` indicating
/// the role and `success`/`error_message` fields populated for responses.
///
/// ## Example: Creating a read request
/// ```
/// use mechutil::ipc::{CommandMessage, MessageType};
///
/// let msg = CommandMessage::read("modbus.server1.holding_0");
/// assert_eq!(msg.message_type, MessageType::Read);
/// ```
///
/// ## Example: Creating a response
/// ```
/// use mechutil::ipc::CommandMessage;
/// use serde_json::json;
///
/// let mut response = CommandMessage::response(42, json!({"value": 123}));
/// assert!(response.success);
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandMessage {
    /// Unique identifier to match responses to requests.
    /// Generated automatically if not specified.
    pub transaction_id: u32,

    /// Timestamp in milliseconds since UNIX epoch.
    /// Set automatically on message creation.
    pub timecode: i64,

    /// Fully-qualified topic name (FQDN).
    /// Format: "domain.subtopic.path" e.g., "ads.plc1.GM.stData"
    pub topic: String,

    /// The type/purpose of this message.
    pub message_type: MessageType,

    /// The payload data.
    /// - For requests: arguments/parameters
    /// - For responses: result data
    pub data: Value,

    /// CRC32 checksum of the message (optional, for integrity verification).
    #[serde(default)]
    pub crc: u32,

    /// Whether the operation was successful (for responses).
    #[serde(default)]
    pub success: bool,

    /// Error message if the operation failed (for responses).
    #[serde(default)]
    pub error_message: String,
}

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

impl CommandMessage {
    // =========================================================================
    // Constructors
    // =========================================================================

    /// Create an empty CommandMessage with auto-generated transaction_id and timecode.
    pub fn new() -> Self {
        Self {
            transaction_id: next_transaction_id(),
            timecode: current_timecode(),
            topic: String::new(),
            message_type: MessageType::NoOp,
            data: Value::Null,
            crc: 0,
            success: false,
            error_message: String::new(),
        }
    }

    /// Create a request message with the specified topic and data.
    pub fn request(topic: &str, data: Value) -> Self {
        Self {
            transaction_id: next_transaction_id(),
            timecode: current_timecode(),
            topic: topic.to_string(),
            message_type: MessageType::Request,
            data,
            crc: 0,
            success: false,
            error_message: String::new(),
        }
    }

    /// Create a read request for the specified topic.
    pub fn read(topic: &str) -> Self {
        Self {
            transaction_id: next_transaction_id(),
            timecode: current_timecode(),
            topic: topic.to_string(),
            message_type: MessageType::Read,
            data: Value::Null,
            crc: 0,
            success: false,
            error_message: String::new(),
        }
    }

    /// Create a write request for the specified topic with data.
    pub fn write(topic: &str, data: Value) -> Self {
        Self {
            transaction_id: next_transaction_id(),
            timecode: current_timecode(),
            topic: topic.to_string(),
            message_type: MessageType::Write,
            data,
            crc: 0,
            success: false,
            error_message: String::new(),
        }
    }

    /// Create a subscribe request for the specified topic.
    pub fn subscribe(topic: &str) -> Self {
        Self {
            transaction_id: next_transaction_id(),
            timecode: current_timecode(),
            topic: topic.to_string(),
            message_type: MessageType::Subscribe,
            data: Value::Null,
            crc: 0,
            success: false,
            error_message: String::new(),
        }
    }

    /// Create an unsubscribe request for the specified topic.
    pub fn unsubscribe(topic: &str) -> Self {
        Self {
            transaction_id: next_transaction_id(),
            timecode: current_timecode(),
            topic: topic.to_string(),
            message_type: MessageType::Unsubscribe,
            data: Value::Null,
            crc: 0,
            success: false,
            error_message: String::new(),
        }
    }

    /// Create a successful response to a request.
    pub fn response(transaction_id: u32, data: Value) -> Self {
        Self {
            transaction_id,
            timecode: current_timecode(),
            topic: String::new(),
            message_type: MessageType::Response,
            data,
            crc: 0,
            success: true,
            error_message: String::new(),
        }
    }

    /// Create an error response to a request.
    pub fn error_response(transaction_id: u32, error: &str) -> Self {
        Self {
            transaction_id,
            timecode: current_timecode(),
            topic: String::new(),
            message_type: MessageType::Response,
            data: Value::Null,
            crc: 0,
            success: false,
            error_message: error.to_string(),
        }
    }

    /// Create a broadcast message with the specified topic and data.
    pub fn broadcast(topic: &str, data: Value) -> Self {
        Self {
            transaction_id: next_transaction_id(),
            timecode: current_timecode(),
            topic: topic.to_string(),
            message_type: MessageType::Broadcast,
            data,
            crc: 0,
            success: true,
            error_message: String::new(),
        }
    }

    /// Create a heartbeat message.
    pub fn heartbeat() -> Self {
        Self {
            transaction_id: next_transaction_id(),
            timecode: current_timecode(),
            topic: String::new(),
            message_type: MessageType::Heartbeat,
            data: Value::Null,
            crc: 0,
            success: true,
            error_message: String::new(),
        }
    }

    /// Create a control message.
    pub fn control(topic: &str, data: Value) -> Self {
        Self {
            transaction_id: next_transaction_id(),
            timecode: current_timecode(),
            topic: topic.to_string(),
            message_type: MessageType::Control,
            data,
            crc: 0,
            success: false,
            error_message: String::new(),
        }
    }

    // =========================================================================
    // Builder methods
    // =========================================================================

    /// Set the transaction ID.
    pub fn with_transaction_id(mut self, id: u32) -> Self {
        self.transaction_id = id;
        self
    }

    /// Set the topic.
    pub fn with_topic(mut self, topic: &str) -> Self {
        self.topic = topic.to_string();
        self
    }

    /// Set the data payload.
    pub fn with_data(mut self, data: Value) -> Self {
        self.data = data;
        self
    }

    /// Set as successful with data.
    pub fn with_success(mut self, data: Value) -> Self {
        self.success = true;
        self.data = data;
        self.error_message.clear();
        self
    }

    /// Set as error with message.
    pub fn with_error(mut self, error: &str) -> Self {
        self.success = false;
        self.error_message = error.to_string();
        self
    }

    // =========================================================================
    // Mutation methods
    // =========================================================================

    /// Set successful result on this message.
    pub fn set_success(&mut self, data: Value) {
        self.message_type = MessageType::Response;
        self.success = true;
        self.data = data;
        self.error_message.clear();
    }

    /// Set error result on this message.
    pub fn set_error(&mut self, error: &str) {
        self.message_type = MessageType::Response;
        self.success = false;
        self.error_message = error.to_string();
    }

    /// Convert this request into a successful response.
    pub fn into_response(mut self, data: Value) -> Self {
        self.message_type = MessageType::Response;
        self.timecode = current_timecode();
        self.success = true;
        self.data = data;
        self.error_message.clear();
        self
    }

    /// Convert this request into an error response.
    pub fn into_error_response(mut self, error: &str) -> Self {
        self.message_type = MessageType::Response;
        self.timecode = current_timecode();
        self.success = false;
        self.error_message = error.to_string();
        self
    }

    // =========================================================================
    // Query methods
    // =========================================================================

    /// Check if this is a response message.
    pub fn is_response(&self) -> bool {
        self.message_type == MessageType::Response
    }

    /// Check if this is a broadcast message.
    pub fn is_broadcast(&self) -> bool {
        self.message_type == MessageType::Broadcast
    }

    /// Check if this is a heartbeat message.
    pub fn is_heartbeat(&self) -> bool {
        self.message_type == MessageType::Heartbeat
    }

    /// Check if this is a request (Read, Write, Subscribe, Unsubscribe, or Request).
    pub fn is_request(&self) -> bool {
        matches!(
            self.message_type,
            MessageType::Read
                | MessageType::Write
                | MessageType::Subscribe
                | MessageType::Unsubscribe
                | MessageType::Request
        )
    }

    /// Get the domain (first segment of the topic).
    pub fn domain(&self) -> &str {
        self.topic.split('.').next().unwrap_or("")
    }

    /// Get the subtopic (everything after the first dot), normalized to lowercase.
    pub fn subtopic(&self) -> String {
        self.topic.split_once('.')
            .map(|(_, rest)| rest.to_ascii_lowercase())
            .unwrap_or_default()
    }

    // =========================================================================
    // CRC methods
    // =========================================================================

    /// Compute CRC32 of the message content.
    pub fn compute_crc(&self) -> u32 {
        let mut hasher = Hasher::new();
        hasher.update(&self.transaction_id.to_le_bytes());
        hasher.update(&self.timecode.to_le_bytes());
        hasher.update(self.topic.as_bytes());
        hasher.update(&[self.message_type as u8]);
        if let Ok(data_bytes) = serde_json::to_vec(&self.data) {
            hasher.update(&data_bytes);
        }
        hasher.update(&[self.success as u8]);
        hasher.update(self.error_message.as_bytes());
        hasher.finalize()
    }

    /// Update the CRC field with the computed value.
    pub fn update_crc(&mut self) {
        self.crc = self.compute_crc();
    }

    /// Verify that the CRC matches the message content.
    pub fn verify_crc(&self) -> bool {
        self.crc == 0 || self.crc == self.compute_crc()
    }
}

// =============================================================================
// Legacy compatibility types (deprecated, will be removed)
// =============================================================================

/// Defines an action that can be executed on a servelet.
///
/// **Deprecated**: Use CommandMessage directly with topic field.
#[deprecated(since = "0.6.0", note = "Use CommandMessage with topic field instead")]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Action {
    /// The name of the target servelet instance of this command.
    pub domain: String,
    /// The name of the function in the servelet to execute.
    pub fname: String,
    /// The arguments to pass with this command.
    pub args: serde_json::Value,
}

#[allow(deprecated)]
impl Action {
    pub fn new() -> Self {
        Self {
            domain: String::new(),
            fname: String::new(),
            args: serde_json::Value::Null,
        }
    }

    /// Convert to a topic string.
    pub fn to_topic(&self) -> String {
        if self.fname.is_empty() {
            self.domain.to_lowercase()
        } else {
            format!("{}.{}", self.domain.to_lowercase(), self.fname.to_lowercase())
        }
    }
}

/// The result portion of a CommandMessage.
///
/// **Deprecated**: Success and error fields are now directly on CommandMessage.
#[deprecated(since = "0.6.0", note = "Use CommandMessage.success and CommandMessage.error_message directly")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandMessageResult {
    /// If true, the message was actually processed and the result updated.
    pub valid: bool,
    /// JSON object containing the result of the command.
    pub data: Value,
    /// If true, the command was processed successfully.
    pub success: bool,
    /// Error message if failed.
    pub error_message: String,
}

#[allow(deprecated)]
impl Default for CommandMessageResult {
    fn default() -> Self {
        CommandMessageResult {
            valid: false,
            data: Value::Null,
            success: false,
            error_message: String::new(),
        }
    }
}

#[allow(deprecated)]
impl CommandMessageResult {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn success(data: Value) -> Self {
        Self {
            valid: true,
            data,
            success: true,
            error_message: String::new(),
        }
    }

    pub fn error(message: &str) -> Self {
        Self {
            valid: true,
            data: Value::Null,
            success: false,
            error_message: message.to_string(),
        }
    }
}

// =============================================================================
// Tests
// =============================================================================

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

    #[test]
    fn test_read_request() {
        let msg = CommandMessage::read("modbus.server1.holding_0");
        assert_eq!(msg.message_type, MessageType::Read);
        assert_eq!(msg.topic, "modbus.server1.holding_0");
        assert_eq!(msg.domain(), "modbus");
        assert_eq!(msg.subtopic(), "server1.holding_0");
        assert!(msg.is_request());
        assert!(!msg.is_response());
    }

    #[test]
    fn test_write_request() {
        let msg = CommandMessage::write("gnv.app.theme", serde_json::json!("dark"));
        assert_eq!(msg.message_type, MessageType::Write);
        assert_eq!(msg.data, serde_json::json!("dark"));
        assert!(msg.is_request());
    }

    #[test]
    fn test_response() {
        let msg = CommandMessage::response(42, serde_json::json!({"value": 123}));
        assert_eq!(msg.transaction_id, 42);
        assert!(msg.is_response());
        assert!(msg.success);
        assert_eq!(msg.data, serde_json::json!({"value": 123}));
    }

    #[test]
    fn test_error_response() {
        let msg = CommandMessage::error_response(42, "Something went wrong");
        assert_eq!(msg.transaction_id, 42);
        assert!(msg.is_response());
        assert!(!msg.success);
        assert_eq!(msg.error_message, "Something went wrong");
    }

    #[test]
    fn test_broadcast() {
        let msg = CommandMessage::broadcast("ads.plc1.notifications", serde_json::json!({"symbol": "test"}));
        assert!(msg.is_broadcast());
        assert!(msg.success);
    }

    #[test]
    fn test_into_response() {
        let request = CommandMessage::read("test.value");
        let tid = request.transaction_id;

        let response = request.into_response(serde_json::json!(42));
        assert_eq!(response.transaction_id, tid);
        assert!(response.is_response());
        assert!(response.success);
        assert_eq!(response.data, serde_json::json!(42));
    }

    #[test]
    fn test_into_error_response() {
        let request = CommandMessage::read("test.value");
        let tid = request.transaction_id;

        let response = request.into_error_response("Not found");
        assert_eq!(response.transaction_id, tid);
        assert!(response.is_response());
        assert!(!response.success);
        assert_eq!(response.error_message, "Not found");
    }

    #[test]
    fn test_crc() {
        let mut msg = CommandMessage::read("test.topic");
        msg.update_crc();
        assert!(msg.verify_crc());
        assert_ne!(msg.crc, 0);

        // Modify and verify CRC fails
        let original_crc = msg.crc;
        msg.topic = "modified.topic".to_string();
        assert_ne!(msg.compute_crc(), original_crc);
    }

    #[test]
    fn test_serialization() {
        let msg = CommandMessage::write("test.value", serde_json::json!({"key": "value"}));
        let json = serde_json::to_string(&msg).unwrap();
        let decoded: CommandMessage = serde_json::from_str(&json).unwrap();

        assert_eq!(decoded.topic, msg.topic);
        assert_eq!(decoded.message_type, msg.message_type);
        assert_eq!(decoded.data, msg.data);
    }

    #[test]
    fn test_domain_extraction() {
        let msg = CommandMessage::read("ads.plc1.GM.stDataResults");
        assert_eq!(msg.domain(), "ads");
        assert_eq!(msg.subtopic(), "plc1.gm.stdataresults");

        let msg2 = CommandMessage::read("simple");
        assert_eq!(msg2.domain(), "simple");
        assert_eq!(msg2.subtopic(), "");
    }
}
