//! Timing metrics for performance analysis.
//!
//! This module provides timing instrumentation to identify bottlenecks in URL processing.

use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;

/// Timing metrics for a single URL processing operation.
///
/// All timing fields are in microseconds (μs). Values are converted to milliseconds
/// only when displaying to users or in API responses.
#[derive(Debug, Clone, Default)]
pub struct UrlTimingMetrics {
    /// HTTP request time (including redirects) in microseconds
    pub http_request_us: u64,
    /// DNS forward lookup time (hostname to IP) in microseconds
    pub dns_forward_us: u64,
    /// DNS reverse lookup time (IP to hostname) in microseconds
    pub dns_reverse_us: u64,
    /// DNS additional records time (NS, TXT, MX lookups) in microseconds
    pub dns_additional_us: u64,
    /// TLS handshake time in microseconds
    pub tls_handshake_us: u64,
    /// HTML parsing time in microseconds
    pub html_parsing_us: u64,
    /// Technology detection time in microseconds
    pub tech_detection_us: u64,
    /// `GeoIP` lookup time in microseconds
    pub geoip_lookup_us: u64,
    /// WHOIS lookup time in microseconds
    pub whois_lookup_us: u64,
    /// Security analysis time in microseconds
    pub security_analysis_us: u64,
    /// Total processing time (from start to finish) in microseconds
    pub total_us: u64,
}

/// Aggregated timing statistics across all processed URLs.
///
/// All times are stored in microseconds for precision, then converted to milliseconds
/// only when displaying to users.
/// `AtomicU64` can be mutated through &self (interior mutability), so individual
/// Arc wrappers are unnecessary when the whole struct is already in Arc<TimingStats>.
#[derive(Debug, Default)]
pub struct TimingStats {
    /// Total number of URLs processed
    pub count: AtomicU64,
    /// Sum of HTTP request times in microseconds (for average calculation)
    pub http_request_sum_us: AtomicU64,
    /// Sum of DNS forward lookup times in microseconds
    pub dns_forward_sum_us: AtomicU64,
    /// Sum of DNS reverse lookup times in microseconds
    pub dns_reverse_sum_us: AtomicU64,
    /// Sum of DNS additional records times in microseconds
    pub dns_additional_sum_us: AtomicU64,
    /// Sum of TLS handshake times in microseconds
    pub tls_handshake_sum_us: AtomicU64,
    /// Sum of HTML parsing times in microseconds
    pub html_parsing_sum_us: AtomicU64,
    /// Sum of technology detection times in microseconds
    pub tech_detection_sum_us: AtomicU64,
    /// Sum of `GeoIP` lookup times in microseconds
    pub geoip_lookup_sum_us: AtomicU64,
    /// Sum of WHOIS lookup times in microseconds
    pub whois_lookup_sum_us: AtomicU64,
    /// Sum of security analysis times in microseconds
    pub security_analysis_sum_us: AtomicU64,
    /// Sum of total processing times in microseconds
    pub total_sum_us: AtomicU64,
}

impl TimingStats {
    pub fn new() -> Self {
        Self::default()
    }

    /// Records timing metrics for a single URL.
    pub fn record(&self, metrics: &UrlTimingMetrics) {
        self.count.fetch_add(1, Ordering::Relaxed);
        self.http_request_sum_us
            .fetch_add(metrics.http_request_us, Ordering::Relaxed);
        self.dns_forward_sum_us
            .fetch_add(metrics.dns_forward_us, Ordering::Relaxed);
        self.dns_reverse_sum_us
            .fetch_add(metrics.dns_reverse_us, Ordering::Relaxed);
        self.dns_additional_sum_us
            .fetch_add(metrics.dns_additional_us, Ordering::Relaxed);
        self.tls_handshake_sum_us
            .fetch_add(metrics.tls_handshake_us, Ordering::Relaxed);
        self.html_parsing_sum_us
            .fetch_add(metrics.html_parsing_us, Ordering::Relaxed);
        self.tech_detection_sum_us
            .fetch_add(metrics.tech_detection_us, Ordering::Relaxed);
        self.geoip_lookup_sum_us
            .fetch_add(metrics.geoip_lookup_us, Ordering::Relaxed);
        self.whois_lookup_sum_us
            .fetch_add(metrics.whois_lookup_us, Ordering::Relaxed);
        self.security_analysis_sum_us
            .fetch_add(metrics.security_analysis_us, Ordering::Relaxed);
        self.total_sum_us
            .fetch_add(metrics.total_us, Ordering::Relaxed);
    }

    /// Calculates and returns average times for each operation (in microseconds).
    ///
    /// # Safety
    /// This function is safe from division by zero. If `count == 0`, it returns
    /// a default `UrlTimingMetrics` with all zeros instead of dividing.
    pub fn averages(&self) -> UrlTimingMetrics {
        let count = self.count.load(Ordering::Relaxed);
        if count == 0 {
            return UrlTimingMetrics::default();
        }

        // Safe to divide by count here because we verified count > 0 above
        UrlTimingMetrics {
            http_request_us: self.http_request_sum_us.load(Ordering::Relaxed) / count,
            dns_forward_us: self.dns_forward_sum_us.load(Ordering::Relaxed) / count,
            dns_reverse_us: self.dns_reverse_sum_us.load(Ordering::Relaxed) / count,
            dns_additional_us: self.dns_additional_sum_us.load(Ordering::Relaxed) / count,
            tls_handshake_us: self.tls_handshake_sum_us.load(Ordering::Relaxed) / count,
            html_parsing_us: self.html_parsing_sum_us.load(Ordering::Relaxed) / count,
            tech_detection_us: self.tech_detection_sum_us.load(Ordering::Relaxed) / count,
            geoip_lookup_us: self.geoip_lookup_sum_us.load(Ordering::Relaxed) / count,
            whois_lookup_us: self.whois_lookup_sum_us.load(Ordering::Relaxed) / count,
            security_analysis_us: self.security_analysis_sum_us.load(Ordering::Relaxed) / count,
            total_us: self.total_sum_us.load(Ordering::Relaxed) / count,
        }
    }

    /// Converts microseconds to milliseconds for display (rounds to nearest).
    fn micros_to_ms(micros: u64) -> u64 {
        (micros + 500) / 1000 // Round to nearest millisecond
    }

    /// Formats a timing value, showing microseconds if the average rounds to 0ms but total is non-zero.
    fn format_timing_with_micros(
        sum_micros: u64,
        avg_ms: u64,
        name: &str,
        percentage: f64,
    ) -> String {
        if avg_ms == 0 && sum_micros > 0 {
            // Average rounds to 0ms but total is non-zero - show in microseconds
            format!(
                "  {:20} {:>6} ms ({:.1}%) (< 1ms avg, {}μs total)",
                name, avg_ms, percentage, sum_micros
            )
        } else {
            // Normal display in milliseconds
            format!("  {:20} {:>6} ms ({:.1}%)", name, avg_ms, percentage)
        }
    }

    /// Logs a summary of timing statistics.
    ///
    /// Optionally accepts flags to indicate whether `GeoIP` and WHOIS are enabled,
    /// which will be displayed in the output when these features are disabled.
    #[allow(clippy::too_many_lines)]
    pub fn log_summary(&self, geoip_enabled: Option<bool>, whois_enabled: Option<bool>) {
        let count = self.count.load(Ordering::Relaxed);
        if count == 0 {
            log::info!("No timing data collected");
            return;
        }

        let avg = self.averages(); // Returns values in microseconds
        let total_sum_micros = self.total_sum_us.load(Ordering::Relaxed);

        // Convert to milliseconds for display (struct for log output only)
        let avg_ms = DisplayMs {
            http_request_ms: Self::micros_to_ms(avg.http_request_us),
            dns_forward_ms: Self::micros_to_ms(avg.dns_forward_us),
            dns_reverse_ms: Self::micros_to_ms(avg.dns_reverse_us),
            dns_additional_ms: Self::micros_to_ms(avg.dns_additional_us),
            tls_handshake_ms: Self::micros_to_ms(avg.tls_handshake_us),
            html_parsing_ms: Self::micros_to_ms(avg.html_parsing_us),
            tech_detection_ms: Self::micros_to_ms(avg.tech_detection_us),
            geoip_lookup_ms: Self::micros_to_ms(avg.geoip_lookup_us),
            whois_lookup_ms: Self::micros_to_ms(avg.whois_lookup_us),
            security_analysis_ms: Self::micros_to_ms(avg.security_analysis_us),
            total_ms: Self::micros_to_ms(avg.total_us),
        };
        let total_sum_ms = Self::micros_to_ms(total_sum_micros);

        log::info!("=== Timing Metrics Summary ({} URLs) ===", count);
        log::info!("Average times per URL:");
        // Safe percentage calculation: explicitly handles division by zero
        // Returns 0.0 when total is 0 to prevent panic
        let percentage = |part: u64, total: u64| -> f64 {
            if total == 0 {
                0.0
            } else {
                // SAFETY: Cast u64 to f64 for percentage calculation
                // - Timing values are in microseconds (10^-6 seconds)
                // - Practical max: processing 1M URLs at 10s each = 10^13 microseconds
                // - f64 has 53 bits of precision, can exactly represent integers up to 2^53 (9 x 10^15)
                // - Timing sums in typical usage (< 10^13 μs) are well within f64 range
                // - If sum exceeds 2^53, precision loss is acceptable for display purposes
                // - This is display-only code; precision loss of a few microseconds in aggregate stats is negligible
                #[allow(clippy::cast_precision_loss)]
                {
                    part as f64 / total as f64 * 100.0
                }
            }
        };

        // Helper to get sum in microseconds for each metric
        let http_sum_micros = self.http_request_sum_us.load(Ordering::Relaxed);
        log::info!(
            "{}",
            Self::format_timing_with_micros(
                http_sum_micros,
                avg_ms.http_request_ms,
                "HTTP Request:",
                percentage(avg_ms.http_request_ms, avg_ms.total_ms),
            )
        );

        let dns_forward_sum_micros = self.dns_forward_sum_us.load(Ordering::Relaxed);
        log::info!(
            "{}",
            Self::format_timing_with_micros(
                dns_forward_sum_micros,
                avg_ms.dns_forward_ms,
                "DNS Forward:",
                percentage(avg_ms.dns_forward_ms, avg_ms.total_ms),
            )
        );

        let dns_reverse_sum_micros = self.dns_reverse_sum_us.load(Ordering::Relaxed);
        log::info!(
            "{}",
            Self::format_timing_with_micros(
                dns_reverse_sum_micros,
                avg_ms.dns_reverse_ms,
                "DNS Reverse:",
                percentage(avg_ms.dns_reverse_ms, avg_ms.total_ms),
            )
        );

        let dns_additional_sum_micros = self.dns_additional_sum_us.load(Ordering::Relaxed);
        log::info!(
            "{}",
            Self::format_timing_with_micros(
                dns_additional_sum_micros,
                avg_ms.dns_additional_ms,
                "DNS Additional:",
                percentage(avg_ms.dns_additional_ms, avg_ms.total_ms),
            )
        );

        let tls_sum_micros = self.tls_handshake_sum_us.load(Ordering::Relaxed);
        log::info!(
            "{}",
            Self::format_timing_with_micros(
                tls_sum_micros,
                avg_ms.tls_handshake_ms,
                "TLS Handshake:",
                percentage(avg_ms.tls_handshake_ms, avg_ms.total_ms),
            )
        );

        let html_sum_micros = self.html_parsing_sum_us.load(Ordering::Relaxed);
        log::info!(
            "{}",
            Self::format_timing_with_micros(
                html_sum_micros,
                avg_ms.html_parsing_ms,
                "HTML Parsing:",
                percentage(avg_ms.html_parsing_ms, avg_ms.total_ms),
            )
        );

        let tech_sum_micros = self.tech_detection_sum_us.load(Ordering::Relaxed);
        log::info!(
            "{}",
            Self::format_timing_with_micros(
                tech_sum_micros,
                avg_ms.tech_detection_ms,
                "Tech Detection:",
                percentage(avg_ms.tech_detection_ms, avg_ms.total_ms),
            )
        );
        // GeoIP Lookup - show "(disabled)" if GeoIP is not enabled, or show total in microseconds if very fast
        let geoip_sum_micros = self.geoip_lookup_sum_us.load(Ordering::Relaxed);
        if let Some(false) = geoip_enabled {
            log::info!(
                "  GeoIP Lookup:        {:>6} ms ({:.1}%) (disabled)",
                avg_ms.geoip_lookup_ms,
                percentage(avg_ms.geoip_lookup_ms, avg_ms.total_ms)
            );
        } else {
            log::info!(
                "{}",
                Self::format_timing_with_micros(
                    geoip_sum_micros,
                    avg_ms.geoip_lookup_ms,
                    "GeoIP Lookup:",
                    percentage(avg_ms.geoip_lookup_ms, avg_ms.total_ms),
                )
            );
        }

        // WHOIS Lookup - show "(disabled)" if WHOIS is not enabled
        let whois_sum_micros = self.whois_lookup_sum_us.load(Ordering::Relaxed);
        if let Some(false) = whois_enabled {
            log::info!(
                "  WHOIS Lookup:        {:>6} ms ({:.1}%) (disabled)",
                avg_ms.whois_lookup_ms,
                percentage(avg_ms.whois_lookup_ms, avg_ms.total_ms)
            );
        } else {
            log::info!(
                "{}",
                Self::format_timing_with_micros(
                    whois_sum_micros,
                    avg_ms.whois_lookup_ms,
                    "WHOIS Lookup:",
                    percentage(avg_ms.whois_lookup_ms, avg_ms.total_ms),
                )
            );
        }

        // Security Analysis - always enabled, but very fast (just checking conditions)
        let security_sum_micros = self.security_analysis_sum_us.load(Ordering::Relaxed);
        log::info!(
            "{}",
            Self::format_timing_with_micros(
                security_sum_micros,
                avg_ms.security_analysis_ms,
                "Security Analysis:",
                percentage(avg_ms.security_analysis_ms, avg_ms.total_ms),
            )
        );
        let other_ms = avg_ms.total_ms.saturating_sub(
            avg_ms.http_request_ms
                + avg_ms.dns_forward_ms
                + avg_ms.dns_reverse_ms
                + avg_ms.dns_additional_ms
                + avg_ms.tls_handshake_ms
                + avg_ms.html_parsing_ms
                + avg_ms.tech_detection_ms
                + avg_ms.geoip_lookup_ms
                + avg_ms.whois_lookup_ms
                + avg_ms.security_analysis_ms,
        );
        log::info!(
            "  Other/Overhead:      {:>6} ms ({:.1}%)",
            other_ms,
            percentage(other_ms, avg_ms.total_ms)
        );
        log::info!("  Total:               {:>6} ms", avg_ms.total_ms);
        // SAFETY: Cast u64 to f64 for display formatting
        // - Converting microseconds to seconds for human-readable output
        // - See justification for cast_precision_loss above in percentage closure
        // - This is display-only code; precision loss is acceptable
        #[allow(clippy::cast_precision_loss)]
        {
            log::info!(
                "Total time across all URLs: {} ms ({:.2} seconds)",
                total_sum_ms,
                total_sum_micros as f64 / 1_000_000.0
            );
        }
    }
}

/// Millisecond display values for log output (not stored).
struct DisplayMs {
    http_request_ms: u64,
    dns_forward_ms: u64,
    dns_reverse_ms: u64,
    dns_additional_ms: u64,
    tls_handshake_ms: u64,
    html_parsing_ms: u64,
    tech_detection_ms: u64,
    geoip_lookup_ms: u64,
    whois_lookup_ms: u64,
    security_analysis_ms: u64,
    total_ms: u64,
}

/// Helper function to convert a Duration to microseconds.
///
/// All timing measurements are stored in microseconds internally for precision,
/// then converted to milliseconds only when displaying to users.
pub fn duration_to_us(duration: Duration) -> u64 {
    // SAFETY: Cast u128 to u64 for Duration microseconds conversion
    // - Duration::as_micros() returns u128 to handle very large durations
    // - Practical max: URL processing timeout is ~60s = 60M microseconds
    // - u64 can hold up to 2^64-1 (~1.8 x 10^19), which is 584,942 years in microseconds
    // - If Duration exceeds u64::MAX microseconds (584K years), cast will truncate
    // - Acceptable: no URL processing operation will take centuries to complete
    // - Truncation to u64::MAX is acceptable failure mode (still represents "very long time")
    #[allow(clippy::cast_possible_truncation)]
    {
        duration.as_micros() as u64
    }
}

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

    #[test]
    fn test_duration_to_us_zero() {
        let duration = Duration::from_micros(0);
        assert_eq!(duration_to_us(duration), 0);
    }

    #[test]
    fn test_duration_to_us_microseconds() {
        let duration = Duration::from_micros(1234);
        assert_eq!(duration_to_us(duration), 1234);
    }

    #[test]
    fn test_duration_to_us_milliseconds() {
        let duration = Duration::from_millis(5);
        assert_eq!(duration_to_us(duration), 5000); // 5ms = 5000μs
    }

    #[test]
    fn test_duration_to_us_seconds() {
        let duration = Duration::from_secs(1);
        assert_eq!(duration_to_us(duration), 1_000_000); // 1s = 1,000,000μs
    }

    #[test]
    fn test_duration_to_us_nanoseconds() {
        let duration = Duration::from_nanos(500);
        assert_eq!(duration_to_us(duration), 0); // 500ns < 1μs, rounds to 0
    }

    #[test]
    fn test_timing_stats_new() {
        let stats = TimingStats::new();
        assert_eq!(stats.count.load(Ordering::Relaxed), 0);
        assert_eq!(stats.http_request_sum_us.load(Ordering::Relaxed), 0);
    }

    #[test]
    fn test_timing_stats_record_single() {
        let stats = TimingStats::new();
        let metrics = UrlTimingMetrics {
            http_request_us: 1000,
            dns_forward_us: 500,
            total_us: 2000,
            ..Default::default()
        };

        stats.record(&metrics);

        assert_eq!(stats.count.load(Ordering::Relaxed), 1);
        assert_eq!(stats.http_request_sum_us.load(Ordering::Relaxed), 1000);
        assert_eq!(stats.dns_forward_sum_us.load(Ordering::Relaxed), 500);
        assert_eq!(stats.total_sum_us.load(Ordering::Relaxed), 2000);
    }

    #[test]
    fn test_timing_stats_record_multiple() {
        let stats = TimingStats::new();
        let metrics1 = UrlTimingMetrics {
            http_request_us: 1000,
            total_us: 2000,
            ..Default::default()
        };
        let metrics2 = UrlTimingMetrics {
            http_request_us: 2000,
            total_us: 3000,
            ..Default::default()
        };

        stats.record(&metrics1);
        stats.record(&metrics2);

        assert_eq!(stats.count.load(Ordering::Relaxed), 2);
        assert_eq!(stats.http_request_sum_us.load(Ordering::Relaxed), 3000);
        assert_eq!(stats.total_sum_us.load(Ordering::Relaxed), 5000);
    }

    #[test]
    fn test_timing_stats_averages_zero_count() {
        let stats = TimingStats::new();
        let avg = stats.averages();
        assert_eq!(avg.http_request_us, 0);
        assert_eq!(avg.total_us, 0);
    }

    #[test]
    fn test_timing_stats_averages_single() {
        let stats = TimingStats::new();
        let metrics = UrlTimingMetrics {
            http_request_us: 1000,
            dns_forward_us: 500,
            total_us: 2000,
            ..Default::default()
        };

        stats.record(&metrics);
        let avg = stats.averages();

        assert_eq!(avg.http_request_us, 1000);
        assert_eq!(avg.dns_forward_us, 500);
        assert_eq!(avg.total_us, 2000);
    }

    #[test]
    fn test_timing_stats_averages_multiple() {
        let stats = TimingStats::new();
        let metrics1 = UrlTimingMetrics {
            http_request_us: 1000,
            total_us: 2000,
            ..Default::default()
        };
        let metrics2 = UrlTimingMetrics {
            http_request_us: 3000,
            total_us: 4000,
            ..Default::default()
        };

        stats.record(&metrics1);
        stats.record(&metrics2);
        let avg = stats.averages();

        assert_eq!(avg.http_request_us, 2000); // (1000 + 3000) / 2
        assert_eq!(avg.total_us, 3000); // (2000 + 4000) / 2
    }

    #[test]
    fn test_timing_stats_averages_rounding() {
        let stats = TimingStats::new();
        let metrics1 = UrlTimingMetrics {
            http_request_us: 1,
            total_us: 3,
            ..Default::default()
        };
        let metrics2 = UrlTimingMetrics {
            http_request_us: 2,
            total_us: 3,
            ..Default::default()
        };

        stats.record(&metrics1);
        stats.record(&metrics2);
        let avg = stats.averages();

        // (1 + 2) / 2 = 1.5, but integer division = 1
        assert_eq!(avg.http_request_us, 1);
        // (3 + 3) / 2 = 3
        assert_eq!(avg.total_us, 3);
    }

    #[test]
    fn test_timing_stats_percentage_division_by_zero() {
        // Test that percentage calculation handles division by zero correctly
        let stats = TimingStats::new();
        let avg = stats.averages();
        // When count is 0, averages should return all zeros
        assert_eq!(avg.total_us, 0);
        // Percentage calculation should handle total_us = 0 (returns 0.0)
        // This is tested implicitly - if it panicked, the test would fail
    }

    #[test]
    fn test_timing_stats_overflow_protection() {
        // Test that very large values don't cause overflow
        let stats = TimingStats::new();
        let metrics = UrlTimingMetrics {
            http_request_us: u64::MAX,
            total_us: u64::MAX,
            ..Default::default()
        };

        // Should not panic on overflow
        stats.record(&metrics);
        assert_eq!(stats.count.load(Ordering::Relaxed), 1);
        assert_eq!(stats.http_request_sum_us.load(Ordering::Relaxed), u64::MAX);
    }

    #[test]
    fn test_timing_stats_http_request_less_than_total() {
        // Test that http_request_us is always <= total_us (critical for percentage accuracy)
        let stats = TimingStats::new();
        let metrics = UrlTimingMetrics {
            http_request_us: 1000,
            total_us: 2000, // Total should be >= http_request
            ..Default::default()
        };

        stats.record(&metrics);
        let avg = stats.averages();
        assert!(avg.http_request_us <= avg.total_us);
    }

    #[test]
    fn test_timing_stats_all_components_sum_less_than_total() {
        // Test that sum of all components doesn't exceed total (with overhead)
        let stats = TimingStats::new();
        let metrics = UrlTimingMetrics {
            http_request_us: 1000,
            dns_forward_us: 500,
            dns_reverse_us: 300,
            dns_additional_us: 200,
            tls_handshake_us: 400,
            html_parsing_us: 100,
            tech_detection_us: 50,
            geoip_lookup_us: 10,
            whois_lookup_us: 0,
            security_analysis_us: 5,
            total_us: 3000, // Total includes overhead
        };

        stats.record(&metrics);
        let avg = stats.averages();
        let sum_components = avg.http_request_us
            + avg.dns_forward_us
            + avg.dns_reverse_us
            + avg.dns_additional_us
            + avg.tls_handshake_us
            + avg.html_parsing_us
            + avg.tech_detection_us
            + avg.geoip_lookup_us
            + avg.whois_lookup_us
            + avg.security_analysis_us;

        // Sum of components should be <= total (total includes overhead)
        assert!(sum_components <= avg.total_us);
    }

    #[test]
    fn test_duration_to_us_very_large_duration() {
        // Test that very large durations don't cause overflow
        let duration = Duration::from_secs(u64::MAX / 1_000_000);
        // Should not panic, but may lose precision
        let result = duration_to_us(duration);
        // Result should be reasonable (not cause overflow in downstream code)
        assert!(result > 0);
    }

    #[test]
    fn test_duration_to_us_overflow_protection() {
        // Test duration that would overflow u64::MAX microseconds
        // Duration::from_secs(u64::MAX) would be way too large
        // Test with a large but reasonable duration
        let duration = Duration::from_secs(18_446_744); // Close to u64::MAX / 1_000_000
        let result = duration_to_us(duration);
        // Should handle gracefully (may truncate but shouldn't panic)
        assert!(result > 0);
    }

    #[test]
    fn test_timing_stats_log_summary_zero_total() {
        // Test that log_summary handles zero total_us gracefully
        // This is critical - prevents division by zero in percentage calculations
        let stats = TimingStats::new();
        let metrics = UrlTimingMetrics {
            http_request_us: 0,
            total_us: 0,
            ..Default::default()
        };
        stats.record(&metrics);

        // Should not panic when logging with zero totals
        // The percentage function at line 181-187 handles total == 0
        stats.log_summary(None, None);
    }

    #[test]
    fn test_timing_stats_log_summary_component_sum_validation() {
        // Test that component sum doesn't exceed total (with overhead)
        // This is critical for percentage accuracy
        let stats = TimingStats::new();
        let metrics = UrlTimingMetrics {
            http_request_us: 1000,
            dns_forward_us: 500,
            dns_reverse_us: 300,
            dns_additional_us: 200,
            tls_handshake_us: 400,
            html_parsing_us: 100,
            tech_detection_us: 50,
            geoip_lookup_us: 10,
            whois_lookup_us: 5,
            security_analysis_us: 2,
            total_us: 3000, // Total includes overhead, so sum of components < total
        };

        stats.record(&metrics);
        let avg = stats.averages();

        // Verify that sum of components is less than total (overhead exists)
        let sum = avg.http_request_us
            + avg.dns_forward_us
            + avg.dns_reverse_us
            + avg.dns_additional_us
            + avg.tls_handshake_us
            + avg.html_parsing_us
            + avg.tech_detection_us
            + avg.geoip_lookup_us
            + avg.whois_lookup_us
            + avg.security_analysis_us;

        assert!(
            sum < avg.total_us,
            "Component sum should be less than total (overhead exists)"
        );
    }

    #[test]
    fn test_timing_stats_format_timing_with_micros_edge_cases() {
        // Test format_timing_with_micros with edge cases
        // This tests the logic at line 137-146
        let result1 = TimingStats::format_timing_with_micros(500, 0, "Test", 0.0);
        // When avg_ms is 0 but sum_micros > 0, should show microsecond format
        assert!(result1.contains("μs") || result1.contains("micros"));

        let result2 = TimingStats::format_timing_with_micros(0, 0, "Test", 0.0);
        // When both are 0, should show normal format
        assert!(!result2.contains("μs") || result2.contains("0 ms"));
    }

    #[test]
    fn test_timing_stats_micros_to_ms_rounding() {
        // Test that micros_to_ms rounds correctly (line 126-128)
        // Should round to nearest millisecond
        assert_eq!(TimingStats::micros_to_ms(0), 0);
        assert_eq!(TimingStats::micros_to_ms(499), 0); // Rounds down
        assert_eq!(TimingStats::micros_to_ms(500), 1); // Rounds up
        assert_eq!(TimingStats::micros_to_ms(1500), 2); // Rounds to nearest
        assert_eq!(TimingStats::micros_to_ms(1999), 2); // Rounds down
        assert_eq!(TimingStats::micros_to_ms(2000), 2); // Exact
    }

    #[test]
    fn test_timing_stats_other_overhead_calculation() {
        // Test that "Other/Overhead" calculation is correct (line 317-328)
        // This is critical - ensures timing breakdown is accurate
        let stats = TimingStats::new();
        let metrics = UrlTimingMetrics {
            http_request_us: 1000,
            dns_forward_us: 500,
            total_us: 2000, // 500μs overhead
            ..Default::default()
        };

        stats.record(&metrics);
        // The log_summary calculates other_ms = total - sum of components
        // This should be 2000 - 1500 = 500 (in display ms after conversion)
        // We test this implicitly by verifying the calculation doesn't panic
        stats.log_summary(None, None);
    }
}
