//! WebSocket configuration for real-time status updates.
//!
//! This module provides configuration options for WebSocket-based
//! credential verification status subscriptions.

#[cfg(feature = "json-schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

/// WebSocket configuration for real-time status updates.
///
/// # Example
///
/// ```toml
/// [websocket]
/// enabled = true
/// connect_timeout_seconds = 10
/// receive_timeout_seconds = 300
/// max_reconnect_attempts = 3
/// initial_reconnect_delay_ms = 1000
/// max_reconnect_delay_ms = 30000
/// reconnect_backoff = 2.0
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
#[serde(default)]
pub struct WebSocketConfig {
    /// Whether WebSocket transport is enabled for status subscriptions.
    /// When disabled, SDK falls back to HTTP polling.
    /// Default: `true`
    pub enabled: bool,

    /// Timeout for initial WebSocket connection in seconds.
    /// Default: `10`
    pub connect_timeout_seconds: u64,

    /// Timeout for receiving messages in seconds.
    /// Should match or exceed the request expiration time.
    /// Default: `300` (5 minutes, matches request expiry)
    pub receive_timeout_seconds: u64,

    /// Maximum number of reconnection attempts before falling back to polling.
    /// Default: `3`
    pub max_reconnect_attempts: u32,

    /// Initial delay before reconnection in milliseconds.
    /// Default: `1000`
    pub initial_reconnect_delay_ms: u64,

    /// Maximum delay between reconnection attempts in milliseconds.
    /// Default: `30000`
    pub max_reconnect_delay_ms: u64,

    /// Backoff multiplier for exponential reconnection delays.
    /// Default: `2.0`
    pub reconnect_backoff: f64,
}

impl Default for WebSocketConfig {
    fn default() -> Self {
        Self {
            enabled: true,
            connect_timeout_seconds: 10,
            receive_timeout_seconds: 300,
            max_reconnect_attempts: 3,
            initial_reconnect_delay_ms: 1000,
            max_reconnect_delay_ms: 30000,
            reconnect_backoff: 2.0,
        }
    }
}

impl WebSocketConfig {
    /// Create a new WebSocket configuration builder.
    #[must_use]
    pub fn builder() -> WebSocketConfigBuilder {
        WebSocketConfigBuilder::new()
    }

    /// Validate the configuration.
    ///
    /// # Errors
    ///
    /// Returns an error if validation fails.
    pub fn validate(&self) -> Result<(), crate::error::LastIDError> {
        if self.connect_timeout_seconds == 0 {
            return Err(crate::error::LastIDError::config(
                "websocket.connect_timeout_seconds must be > 0",
            ));
        }

        if self.receive_timeout_seconds == 0 {
            return Err(crate::error::LastIDError::config(
                "websocket.receive_timeout_seconds must be > 0",
            ));
        }

        if self.reconnect_backoff <= 0.0 {
            return Err(crate::error::LastIDError::config(
                "websocket.reconnect_backoff must be > 0",
            ));
        }

        if self.initial_reconnect_delay_ms == 0 {
            return Err(crate::error::LastIDError::config(
                "websocket.initial_reconnect_delay_ms must be > 0",
            ));
        }

        Ok(())
    }

    /// Merge another configuration into this one.
    ///
    /// Non-default values from `other` override values in `self`.
    pub fn merge(&mut self, other: &Self) {
        let default = Self::default();

        if other.enabled != default.enabled {
            self.enabled = other.enabled;
        }
        if other.connect_timeout_seconds != default.connect_timeout_seconds {
            self.connect_timeout_seconds = other.connect_timeout_seconds;
        }
        if other.receive_timeout_seconds != default.receive_timeout_seconds {
            self.receive_timeout_seconds = other.receive_timeout_seconds;
        }
        if other.max_reconnect_attempts != default.max_reconnect_attempts {
            self.max_reconnect_attempts = other.max_reconnect_attempts;
        }
        if other.initial_reconnect_delay_ms != default.initial_reconnect_delay_ms {
            self.initial_reconnect_delay_ms = other.initial_reconnect_delay_ms;
        }
        if other.max_reconnect_delay_ms != default.max_reconnect_delay_ms {
            self.max_reconnect_delay_ms = other.max_reconnect_delay_ms;
        }
        if (other.reconnect_backoff - default.reconnect_backoff).abs() > f64::EPSILON {
            self.reconnect_backoff = other.reconnect_backoff;
        }
    }

    /// Calculate reconnection delay for a given attempt number.
    ///
    /// Uses exponential backoff with jitter.
    #[must_use]
    #[allow(
        clippy::cast_precision_loss,
        clippy::cast_possible_truncation,
        clippy::cast_sign_loss,
        clippy::cast_possible_wrap
    )]
    pub fn reconnect_delay_ms(&self, attempt: u32) -> u64 {
        // Cast safety: values are small (< 2^32) so f64 precision is adequate
        let base_delay = self.initial_reconnect_delay_ms as f64
            * self
                .reconnect_backoff
                .powi(attempt.saturating_sub(1) as i32);
        // Return directly instead of binding to intermediate variable
        base_delay.min(self.max_reconnect_delay_ms as f64) as u64
    }
}

/// Builder for `WebSocketConfig`.
#[derive(Debug, Clone, Default)]
pub struct WebSocketConfigBuilder {
    config: WebSocketConfig,
}

impl WebSocketConfigBuilder {
    /// Create a new configuration builder.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Enable or disable WebSocket transport.
    #[must_use]
    pub const fn enabled(mut self, enabled: bool) -> Self {
        self.config.enabled = enabled;
        self
    }

    /// Set the connection timeout in seconds.
    #[must_use]
    pub const fn connect_timeout_seconds(mut self, seconds: u64) -> Self {
        self.config.connect_timeout_seconds = seconds;
        self
    }

    /// Set the receive timeout in seconds.
    #[must_use]
    pub const fn receive_timeout_seconds(mut self, seconds: u64) -> Self {
        self.config.receive_timeout_seconds = seconds;
        self
    }

    /// Set the maximum reconnection attempts.
    #[must_use]
    pub const fn max_reconnect_attempts(mut self, attempts: u32) -> Self {
        self.config.max_reconnect_attempts = attempts;
        self
    }

    /// Set the initial reconnection delay in milliseconds.
    #[must_use]
    pub const fn initial_reconnect_delay_ms(mut self, ms: u64) -> Self {
        self.config.initial_reconnect_delay_ms = ms;
        self
    }

    /// Set the maximum reconnection delay in milliseconds.
    #[must_use]
    pub const fn max_reconnect_delay_ms(mut self, ms: u64) -> Self {
        self.config.max_reconnect_delay_ms = ms;
        self
    }

    /// Set the reconnection backoff multiplier.
    #[must_use]
    pub const fn reconnect_backoff(mut self, backoff: f64) -> Self {
        self.config.reconnect_backoff = backoff;
        self
    }

    /// Build the configuration.
    #[must_use]
    pub const fn build(self) -> WebSocketConfig {
        self.config
    }
}

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

    #[test]
    fn test_default_config() {
        let config = WebSocketConfig::default();
        assert!(config.enabled);
        assert_eq!(config.connect_timeout_seconds, 10);
        assert_eq!(config.receive_timeout_seconds, 300);
        assert_eq!(config.max_reconnect_attempts, 3);
        assert_eq!(config.initial_reconnect_delay_ms, 1000);
        assert_eq!(config.max_reconnect_delay_ms, 30000);
        assert!((config.reconnect_backoff - 2.0).abs() < f64::EPSILON);
    }

    #[test]
    fn test_reconnect_delay_calculation() {
        let config = WebSocketConfig::default();

        // First attempt: 1000ms
        assert_eq!(config.reconnect_delay_ms(1), 1000);

        // Second attempt: 1000 * 2 = 2000ms
        assert_eq!(config.reconnect_delay_ms(2), 2000);

        // Third attempt: 1000 * 4 = 4000ms
        assert_eq!(config.reconnect_delay_ms(3), 4000);

        // Cap at max delay
        let capped_config = WebSocketConfig {
            max_reconnect_delay_ms: 5000,
            ..Default::default()
        };
        assert_eq!(capped_config.reconnect_delay_ms(10), 5000);
    }

    #[test]
    fn test_builder() {
        let config = WebSocketConfig::builder()
            .enabled(false)
            .connect_timeout_seconds(5)
            .max_reconnect_attempts(5)
            .build();

        assert!(!config.enabled);
        assert_eq!(config.connect_timeout_seconds, 5);
        assert_eq!(config.max_reconnect_attempts, 5);
    }

    #[test]
    fn test_validate() {
        let valid = WebSocketConfig::default();
        assert!(valid.validate().is_ok());

        let invalid_timeout = WebSocketConfig {
            connect_timeout_seconds: 0,
            ..Default::default()
        };
        assert!(invalid_timeout.validate().is_err());

        let invalid_backoff = WebSocketConfig {
            reconnect_backoff: 0.0,
            ..Default::default()
        };
        assert!(invalid_backoff.validate().is_err());
    }
}
