// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! I/O scheduler metrics recording functions.
//!
//! This module provides metrics recording for I/O scheduler operations.

/// Record I/O scheduler decision.
///
/// # Arguments
///
/// * `buffer_size` - Buffer size in bytes
/// * `load_level` - Load level string
/// * `strategy` - Strategy type string
#[inline(always)]
pub fn record_io_scheduler_decision(buffer_size: usize, load_level: &str, strategy: &str) {
    use metrics::{counter, gauge, histogram};

    counter!("rustfs_io_scheduler_decisions").increment(1);
    gauge!("rustfs_io_scheduler_buffer_size").set(buffer_size as f64);
    counter!("rustfs_io_scheduler_load", "level" => load_level.to_string()).increment(1);
    counter!("rustfs_io_scheduler_strategy", "type" => strategy.to_string()).increment(1);
    histogram!("rustfs_io_scheduler_buffer_size_histogram").record(buffer_size as f64);
}

/// Record I/O priority decision.
///
/// # Arguments
///
/// * `priority` - Priority level string
/// * `size` - Request size in bytes
#[inline(always)]
pub fn record_io_priority_decision(priority: &str, size: usize) {
    use metrics::{counter, histogram};

    counter!("rustfs_io_priority_decisions").increment(1);
    counter!("rustfs_io_priority_by_level", "priority" => priority.to_string()).increment(1);
    histogram!("rustfs_io_priority_request_size").record(size as f64);
}

/// Record load level change.
///
/// # Arguments
///
/// * `from` - Previous load level
/// * `to` - New load level
#[inline(always)]
pub fn record_load_level_change(from: &str, to: &str) {
    use metrics::counter;
    counter!("rustfs_io_load_changes", "from" => from.to_string(), "to" => to.to_string()).increment(1);
}

/// Record bandwidth observation.
///
/// # Arguments
///
/// * `bps` - Bytes per second
#[inline(always)]
pub fn record_bandwidth_observation(bps: u64) {
    use metrics::{gauge, histogram};
    gauge!("rustfs_io_bandwidth_bps").set(bps as f64);
    histogram!("rustfs_io_bandwidth_histogram").record(bps as f64);
}

/// Record buffer size adjustment.
///
/// # Arguments
///
/// * `original` - Original buffer size
/// * `adjusted` - Adjusted buffer size
/// * `reason` - Reason for adjustment
#[inline(always)]
pub fn record_buffer_size_adjustment(original: usize, adjusted: usize, reason: &str) {
    use metrics::{counter, gauge};
    counter!("rustfs_io_buffer_adjustments", "reason" => reason.to_string()).increment(1);
    gauge!("rustfs_io_buffer_original").set(original as f64);
    gauge!("rustfs_io_buffer_adjusted").set(adjusted as f64);
}

/// Record queue operation.
///
/// # Arguments
///
/// * `operation` - Operation type ("enqueue" or "dequeue")
/// * `priority` - Priority level
/// * `queue_size` - Current queue size
#[inline(always)]
pub fn record_queue_operation(operation: &str, priority: &str, queue_size: usize) {
    use metrics::{counter, gauge};
    counter!("rustfs_io_queue_operations", "operation" => operation.to_string(), "priority" => priority.to_string()).increment(1);
    gauge!("rustfs_io_queue_size", "priority" => priority.to_string()).set(queue_size as f64);
}

/// Record starvation event.
///
/// # Arguments
///
/// * `priority` - Starved priority level
#[inline(always)]
pub fn record_starvation_event(priority: &str) {
    use metrics::counter;
    counter!("rustfs_io_starvation_events", "priority" => priority.to_string()).increment(1);
}

/// I/O scheduler statistics.
#[derive(Debug, Clone, Default)]
pub struct IoSchedulerStats {
    /// Number of scheduler decisions.
    pub decisions: u64,
    /// Number of priority decisions.
    pub priority_decisions: u64,
    /// Number of load level changes.
    pub load_changes: u64,
    /// Number of buffer adjustments.
    pub buffer_adjustments: u64,
    /// Number of starvation events.
    pub starvation_events: u64,
}

impl IoSchedulerStats {
    /// Create new statistics.
    pub fn new() -> Self {
        Self::default()
    }

    /// Record a scheduler decision.
    pub fn record_decision(&mut self) {
        self.decisions += 1;
    }

    /// Record a priority decision.
    pub fn record_priority_decision(&mut self) {
        self.priority_decisions += 1;
    }

    /// Record a load change.
    pub fn record_load_change(&mut self) {
        self.load_changes += 1;
    }

    /// Record a buffer adjustment.
    pub fn record_buffer_adjustment(&mut self) {
        self.buffer_adjustments += 1;
    }

    /// Record a starvation event.
    pub fn record_starvation(&mut self) {
        self.starvation_events += 1;
    }

    /// Reset statistics.
    pub fn reset(&mut self) {
        *self = Self::default();
    }
}

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

    /// Replaces the per-helper smoke tests that called the record_* helpers
    /// and asserted nothing: the calls (same literals) now run against a local
    /// DebuggingRecorder and every metric name the helpers own must actually
    /// be emitted (rustfs/backlog#1836 PR3).
    #[test]
    fn record_helpers_emit_their_metrics() {
        let recorder = metrics_util::debugging::DebuggingRecorder::new();
        let snapshotter = recorder.snapshotter();
        metrics::with_local_recorder(&recorder, || {
            record_io_scheduler_decision(128 * 1024, "low", "sequential");
            record_io_scheduler_decision(64 * 1024, "high", "random");
            record_io_priority_decision("high", 1024);
            record_io_priority_decision("normal", 1024 * 1024);
            record_io_priority_decision("low", 10 * 1024 * 1024);
            record_load_level_change("low", "medium");
            record_load_level_change("medium", "high");
            record_bandwidth_observation(100 * 1024 * 1024);
            record_bandwidth_observation(500 * 1024 * 1024);
            record_buffer_size_adjustment(128 * 1024, 64 * 1024, "concurrency");
            record_buffer_size_adjustment(128 * 1024, 256 * 1024, "sequential");
            record_queue_operation("enqueue", "high", 10);
            record_queue_operation("dequeue", "high", 9);
            record_starvation_event("low");
        });

        let emitted: std::collections::HashSet<String> = snapshotter
            .snapshot()
            .into_vec()
            .into_iter()
            .map(|(composite, _, _, _)| composite.key().name().to_string())
            .collect();
        for expected in [
            "rustfs_io_scheduler_decisions",
            "rustfs_io_scheduler_buffer_size",
            "rustfs_io_scheduler_load",
            "rustfs_io_scheduler_strategy",
            "rustfs_io_scheduler_buffer_size_histogram",
            "rustfs_io_priority_decisions",
            "rustfs_io_priority_by_level",
            "rustfs_io_priority_request_size",
            "rustfs_io_load_changes",
            "rustfs_io_bandwidth_bps",
            "rustfs_io_bandwidth_histogram",
            "rustfs_io_buffer_adjustments",
            "rustfs_io_buffer_original",
            "rustfs_io_buffer_adjusted",
            "rustfs_io_queue_operations",
            "rustfs_io_queue_size",
            "rustfs_io_starvation_events",
        ] {
            assert!(emitted.contains(expected), "{expected} must be emitted by its record helper");
        }
    }

    #[test]
    fn test_io_scheduler_stats() {
        let mut stats = IoSchedulerStats::new();

        stats.record_decision();
        stats.record_priority_decision();
        stats.record_load_change();
        stats.record_buffer_adjustment();
        stats.record_starvation();

        assert_eq!(stats.decisions, 1);
        assert_eq!(stats.priority_decisions, 1);
        assert_eq!(stats.load_changes, 1);
        assert_eq!(stats.buffer_adjustments, 1);
        assert_eq!(stats.starvation_events, 1);
    }
}
