use serde::{Deserialize, Serialize};

use super::command_id::CommandId;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PingCommand {
    pub timestamp: u64,
    pub nonce: Option<Vec<u8>>,
}

impl PingCommand {
    pub fn new() -> Self {
        Self {
            timestamp: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs(),
            nonce: None,
        }
    }

    pub fn with_nonce(nonce: Vec<u8>) -> Self {
        Self {
            timestamp: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs(),
            nonce: Some(nonce),
        }
    }

    pub fn id() -> CommandId {
        CommandId::Ping
    }

    pub fn encode(&self) -> Vec<u8> {
        let mut bytes = vec![];
        bytes.extend_from_slice(&(CommandId::Ping.as_u32()).to_le_bytes());
        if let Ok(v) = serde_json::to_vec(self) {
            bytes.extend_from_slice(&v);
        }
        bytes
    }

    pub fn decode(data: &[u8]) -> Result<Self, String> {
        if data.len() < 4 {
            return Err("data too short".to_string());
        }
        let id = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
        if id != CommandId::Ping.as_u32() {
            return Err("invalid command id".to_string());
        }
        serde_json::from_slice(&data[4..]).map_err(|e| e.to_string())
    }
}

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

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PongCommand {
    pub timestamp: u64,
    pub nonce: Option<Vec<u8>>,
    pub local_time: u64,
}

impl PongCommand {
    pub fn new(timestamp: u64, nonce: Option<Vec<u8>>) -> Self {
        Self {
            timestamp,
            nonce,
            local_time: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs(),
        }
    }

    pub fn id() -> CommandId {
        CommandId::Pong
    }

    pub fn encode(&self) -> Vec<u8> {
        let mut bytes = vec![];
        bytes.extend_from_slice(&(CommandId::Pong.as_u32()).to_le_bytes());
        if let Ok(v) = serde_json::to_vec(self) {
            bytes.extend_from_slice(&v);
        }
        bytes
    }

    pub fn decode(data: &[u8]) -> Result<Self, String> {
        if data.len() < 4 {
            return Err("data too short".to_string());
        }
        let id = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
        if id != CommandId::Pong.as_u32() {
            return Err("invalid command id".to_string());
        }
        serde_json::from_slice(&data[4..]).map_err(|e| e.to_string())
    }

    pub fn latency(&self) -> u64 {
        self.local_time.saturating_sub(self.timestamp)
    }
}

pub fn detect_command_type(data: &[u8]) -> Option<CommandId> {
    if data.len() < 4 {
        return None;
    }
    let id = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
    CommandId::from_u32(id)
}
