use crate::types::UnitMode;

pub fn format_bytes(b: u64, mode: UnitMode) -> String {
    match mode {
        UnitMode::Bytes => format!("{b}B"),
        UnitMode::MiB => format!("{:.1}M", b as f64 / 1_048_576.0),
        UnitMode::GiB => format!("{:.1}G", b as f64 / 1_073_741_824.0),
        UnitMode::Human => {
            if b >= 1_099_511_627_776 {
                format!("{:.1}T", b as f64 / 1_099_511_627_776.0)
            } else if b >= 1_073_741_824 {
                format!("{:.1}G", b as f64 / 1_073_741_824.0)
            } else if b >= 1_048_576 {
                format!("{:.1}M", b as f64 / 1_048_576.0)
            } else if b >= 1024 {
                format!("{:.1}K", b as f64 / 1024.0)
            } else {
                format!("{b}B")
            }
        }
    }
}

pub fn format_uptime(secs: u64) -> String {
    let d = secs / 86400;
    let h = (secs % 86400) / 3600;
    let m = (secs % 3600) / 60;
    if d > 0 {
        format!("{d}d{h}h{m}m")
    } else if h > 0 {
        format!("{h}h{m}m")
    } else {
        format!("{m}m")
    }
}

pub fn format_latency(ms: f64) -> String {
    if ms < 1.0 {
        "<1ms".into()
    } else if ms < 1000.0 {
        format!("{:.0}ms", ms)
    } else {
        format!("{:.1}s", ms / 1000.0)
    }
}

pub fn format_rate(bytes_per_sec: f64) -> String {
    if bytes_per_sec < 1.0 {
        "0B/s".into()
    } else if bytes_per_sec < 1024.0 {
        format!("{:.0}B/s", bytes_per_sec)
    } else if bytes_per_sec < 1_048_576.0 {
        format!("{:.1}K/s", bytes_per_sec / 1024.0)
    } else if bytes_per_sec < 1_073_741_824.0 {
        format!("{:.1}M/s", bytes_per_sec / 1_048_576.0)
    } else {
        format!("{:.1}G/s", bytes_per_sec / 1_073_741_824.0)
    }
}

pub fn truncate_mount(mount: &str, width: usize) -> String {
    if mount.chars().count() <= width {
        format!("{:<width$}", mount, width = width)
    } else {
        let s: String = mount.chars().take(width.saturating_sub(1)).collect();
        format!("{}\u{2026}", s)
    }
}

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

    // ── format_bytes ───────────────────────────────────────

    #[test]
    fn format_bytes_zero() {
        assert_eq!(format_bytes(0, UnitMode::Human), "0B");
        assert_eq!(format_bytes(0, UnitMode::Bytes), "0B");
        assert_eq!(format_bytes(0, UnitMode::GiB), "0.0G");
        assert_eq!(format_bytes(0, UnitMode::MiB), "0.0M");
    }

    #[test]
    fn format_bytes_human_small() {
        assert_eq!(format_bytes(512, UnitMode::Human), "512B");
        assert_eq!(format_bytes(1023, UnitMode::Human), "1023B");
    }

    #[test]
    fn format_bytes_human_kilo() {
        assert_eq!(format_bytes(1024, UnitMode::Human), "1.0K");
        assert_eq!(format_bytes(1536, UnitMode::Human), "1.5K");
    }

    #[test]
    fn format_bytes_human_mega() {
        assert_eq!(format_bytes(1_048_576, UnitMode::Human), "1.0M");
        assert_eq!(format_bytes(10 * 1_048_576, UnitMode::Human), "10.0M");
    }

    #[test]
    fn format_bytes_human_giga() {
        assert_eq!(format_bytes(1_073_741_824, UnitMode::Human), "1.0G");
        assert_eq!(format_bytes(5 * 1_073_741_824, UnitMode::Human), "5.0G");
    }

    #[test]
    fn format_bytes_human_tera() {
        assert_eq!(format_bytes(1_099_511_627_776, UnitMode::Human), "1.0T");
        assert_eq!(format_bytes(2 * 1_099_511_627_776, UnitMode::Human), "2.0T");
    }

    #[test]
    fn format_bytes_explicit_modes() {
        let gb = 1_073_741_824u64;
        assert_eq!(format_bytes(gb, UnitMode::Bytes), "1073741824B");
        assert_eq!(format_bytes(gb, UnitMode::MiB), "1024.0M");
        assert_eq!(format_bytes(gb, UnitMode::GiB), "1.0G");
    }

    // ── format_uptime ──────────────────────────────────────

    #[test]
    fn format_uptime_minutes_only() {
        assert_eq!(format_uptime(0), "0m");
        assert_eq!(format_uptime(59), "0m");
        assert_eq!(format_uptime(60), "1m");
        assert_eq!(format_uptime(300), "5m");
    }

    #[test]
    fn format_uptime_hours() {
        assert_eq!(format_uptime(3600), "1h0m");
        assert_eq!(format_uptime(3660), "1h1m");
        assert_eq!(format_uptime(7200), "2h0m");
    }

    #[test]
    fn format_uptime_days() {
        assert_eq!(format_uptime(86400), "1d0h0m");
        assert_eq!(format_uptime(90000), "1d1h0m");
        assert_eq!(format_uptime(2 * 86400 + 3 * 3600 + 15 * 60), "2d3h15m");
    }

    // ── truncate_mount ─────────────────────────────────────

    #[test]
    fn truncate_mount_fits() {
        let r = truncate_mount("/mnt", 10);
        assert_eq!(r.len(), 10);
        assert!(r.starts_with("/mnt"));
    }

    #[test]
    fn truncate_mount_exact() {
        let r = truncate_mount("/mnt", 4);
        assert_eq!(r, "/mnt");
    }

    #[test]
    fn truncate_mount_too_long() {
        let r = truncate_mount("/very/long/mount/path", 10);
        assert_eq!(r.chars().count(), 10);
        assert!(r.ends_with('\u{2026}')); // ends with …
    }

    #[test]
    fn truncate_mount_empty() {
        let r = truncate_mount("", 5);
        assert_eq!(r.len(), 5);
    }

    // ── format_bytes edge cases ─────────────────────────────

    #[test]
    fn format_bytes_zero_all_modes() {
        assert_eq!(format_bytes(0, UnitMode::Human), "0B");
        assert_eq!(format_bytes(0, UnitMode::Bytes), "0B");
        assert_eq!(format_bytes(0, UnitMode::GiB), "0.0G");
        assert_eq!(format_bytes(0, UnitMode::MiB), "0.0M");
    }

    #[test]
    fn format_bytes_one() {
        assert_eq!(format_bytes(1, UnitMode::Human), "1B");
        assert_eq!(format_bytes(1, UnitMode::Bytes), "1B");
    }

    #[test]
    fn format_bytes_boundary_kilo_minus_one() {
        assert_eq!(format_bytes(1023, UnitMode::Human), "1023B");
    }

    #[test]
    fn format_bytes_boundary_kilo() {
        assert_eq!(format_bytes(1024, UnitMode::Human), "1.0K");
    }

    #[test]
    fn format_bytes_boundary_mega_minus_one() {
        assert_eq!(format_bytes(1_048_575, UnitMode::Human), "1024.0K");
    }

    #[test]
    fn format_bytes_boundary_mega() {
        assert_eq!(format_bytes(1_048_576, UnitMode::Human), "1.0M");
    }

    #[test]
    fn format_bytes_boundary_giga_minus_one() {
        assert_eq!(format_bytes(1_073_741_823, UnitMode::Human), "1024.0M");
    }

    #[test]
    fn format_bytes_boundary_giga() {
        assert_eq!(format_bytes(1_073_741_824, UnitMode::Human), "1.0G");
    }

    #[test]
    fn format_bytes_boundary_tera_minus_one() {
        assert_eq!(format_bytes(1_099_511_627_775, UnitMode::Human), "1024.0G");
    }

    #[test]
    fn format_bytes_boundary_tera() {
        assert_eq!(format_bytes(1_099_511_627_776, UnitMode::Human), "1.0T");
    }

    #[test]
    fn format_bytes_max_u64() {
        // Should not panic
        let _ = format_bytes(u64::MAX, UnitMode::Human);
        let _ = format_bytes(u64::MAX, UnitMode::Bytes);
        let _ = format_bytes(u64::MAX, UnitMode::GiB);
        let _ = format_bytes(u64::MAX, UnitMode::MiB);
    }

    // ── format_uptime edge cases ────────────────────────────

    #[test]
    fn format_uptime_large_value() {
        // 10 years
        let secs = 10 * 365 * 86400;
        let r = format_uptime(secs);
        assert!(r.starts_with("3650d"));
    }

    #[test]
    fn format_uptime_59_seconds() {
        assert_eq!(format_uptime(59), "0m");
    }

    #[test]
    fn format_uptime_60_seconds() {
        assert_eq!(format_uptime(60), "1m");
    }

    #[test]
    fn format_uptime_23h59m() {
        assert_eq!(format_uptime(86399), "23h59m");
    }

    // ── truncate_mount edge cases ───────────────────────────

    #[test]
    fn truncate_mount_unicode() {
        let r = truncate_mount("/日本語/パス", 8);
        assert!(r.chars().count() <= 8);
    }

    #[test]
    fn truncate_mount_width_equals_string() {
        let s = "/mnt/data";
        let r = truncate_mount(s, s.len());
        assert_eq!(r, s);
    }

    #[test]
    fn truncate_mount_width_one_more_than_string() {
        let s = "/mnt";
        let r = truncate_mount(s, s.len() + 1);
        assert!(r.starts_with(s));
        assert_eq!(r.chars().count(), s.len() + 1);
    }

    // ── format_latency ──────────────────────────────────────

    #[test]
    fn format_latency_sub_millisecond() {
        assert_eq!(format_latency(0.3), "<1ms");
        assert_eq!(format_latency(0.0), "<1ms");
    }

    #[test]
    fn format_latency_milliseconds() {
        assert_eq!(format_latency(1.0), "1ms");
        assert_eq!(format_latency(12.4), "12ms");
        assert_eq!(format_latency(150.0), "150ms");
        assert_eq!(format_latency(999.9), "1000ms");
    }

    #[test]
    fn format_latency_seconds() {
        assert_eq!(format_latency(1000.0), "1.0s");
        assert_eq!(format_latency(2500.0), "2.5s");
    }

    // ── format_rate ─────────────────────────────────────

    #[test]
    fn format_rate_zero() {
        assert_eq!(format_rate(0.0), "0B/s");
    }

    #[test]
    fn format_rate_bytes() {
        assert_eq!(format_rate(512.0), "512B/s");
    }

    #[test]
    fn format_rate_kilobytes() {
        assert_eq!(format_rate(1024.0), "1.0K/s");
        assert_eq!(format_rate(10240.0), "10.0K/s");
    }

    #[test]
    fn format_rate_megabytes() {
        assert_eq!(format_rate(1_048_576.0), "1.0M/s");
        assert_eq!(format_rate(52_428_800.0), "50.0M/s");
    }

    #[test]
    fn format_rate_gigabytes() {
        assert_eq!(format_rate(1_073_741_824.0), "1.0G/s");
    }

    #[test]
    fn format_rate_sub_one_is_zero_band() {
        assert_eq!(format_rate(0.5), "0B/s");
        assert_eq!(format_rate(0.99), "0B/s");
    }

    #[test]
    fn format_rate_just_below_kilo_boundary() {
        assert_eq!(format_rate(1023.9), "1024B/s");
    }

    #[test]
    fn format_rate_just_below_mebi_boundary() {
        let x = 1_048_576.0 - 1.0;
        let s = format_rate(x);
        assert!(s.ends_with("K/s"), "got {s}");
    }

    #[test]
    fn format_rate_just_below_gibi_boundary() {
        let x = 1_073_741_824.0 - 1.0;
        let s = format_rate(x);
        assert!(s.ends_with("M/s"), "got {s}");
    }

    #[test]
    fn format_rate_very_large() {
        let s = format_rate(50.0 * 1_073_741_824.0);
        assert_eq!(s, "50.0G/s");
    }

    #[test]
    fn format_latency_boundary_ms_vs_s() {
        assert_eq!(format_latency(999.0), "999ms");
        assert_eq!(format_latency(1000.0), "1.0s");
    }

    #[test]
    fn format_latency_fractional_ms_rounds() {
        assert_eq!(format_latency(42.7), "43ms");
    }

    #[test]
    fn truncate_mount_width_zero_nonempty() {
        let r = truncate_mount("/ab", 0);
        assert_eq!(r, "\u{2026}");
    }

    #[test]
    fn truncate_mount_width_one() {
        let r = truncate_mount("/ab", 1);
        assert_eq!(r.chars().count(), 1);
    }

    #[test]
    fn format_bytes_human_one_below_tera() {
        assert_eq!(format_bytes(1_099_511_627_775, UnitMode::Human), "1024.0G");
    }

    #[test]
    fn format_uptime_day_with_nonzero_hours_minutes() {
        assert_eq!(format_uptime(86400 + 3661), "1d1h1m");
    }

    #[test]
    fn format_uptime_hour_only_no_days() {
        assert_eq!(format_uptime(7200), "2h0m");
    }

    #[test]
    fn format_rate_negative_falls_through_zero_band() {
        assert_eq!(format_rate(-50.0), "0B/s");
    }

    #[test]
    fn format_bytes_human_exactly_one_byte_below_kilo() {
        assert_eq!(format_bytes(1023, UnitMode::Human), "1023B");
    }

    #[test]
    fn format_bytes_gib_mode_fraction() {
        assert_eq!(format_bytes(536_870_912, UnitMode::GiB), "0.5G");
    }

    #[test]
    fn format_bytes_mib_exact_one() {
        assert_eq!(format_bytes(1_048_576, UnitMode::MiB), "1.0M");
    }

    #[test]
    fn format_latency_exactly_one_ms() {
        assert_eq!(format_latency(1.0), "1ms");
    }

    #[test]
    fn format_bytes_bytes_mode_three_digits() {
        assert_eq!(format_bytes(999, UnitMode::Bytes), "999B");
    }

    #[test]
    fn format_uptime_only_minutes_no_hours() {
        assert_eq!(format_uptime(3599), "59m");
    }

    #[test]
    fn format_bytes_bytes_mode_one_mebibyte() {
        assert_eq!(format_bytes(1_048_576, UnitMode::Bytes), "1048576B");
    }

    #[test]
    fn format_latency_ten_seconds() {
        assert_eq!(format_latency(10_000.0), "10.0s");
    }

    #[test]
    fn format_rate_exactly_one_kilo_per_sec() {
        assert_eq!(format_rate(1024.0), "1.0K/s");
    }

    #[test]
    fn format_rate_just_under_one_kilo() {
        assert_eq!(format_rate(1023.0), "1023B/s");
    }

    #[test]
    fn format_bytes_mib_half() {
        assert_eq!(format_bytes(524_288, UnitMode::MiB), "0.5M");
    }

    #[test]
    fn format_uptime_exactly_one_day() {
        assert_eq!(format_uptime(86400), "1d0h0m");
    }

    #[test]
    fn format_uptime_one_day_one_minute() {
        assert_eq!(format_uptime(86400 + 60), "1d0h1m");
    }

    #[test]
    fn format_bytes_gib_mode_zero() {
        assert_eq!(format_bytes(0, UnitMode::GiB), "0.0G");
    }

    #[test]
    fn format_rate_exactly_one_gib_per_sec() {
        assert_eq!(format_rate(1_073_741_824.0), "1.0G/s");
    }

    #[test]
    fn format_rate_just_under_one_gib_per_sec() {
        let s = format_rate(1_073_741_823.0);
        assert!(s.ends_with("M/s"), "got {s}");
    }

    #[test]
    fn format_uptime_two_full_days() {
        assert_eq!(format_uptime(86400 * 2), "2d0h0m");
    }

    #[test]
    fn format_uptime_two_days_one_hour() {
        assert_eq!(format_uptime(86400 * 2 + 3600), "2d1h0m");
    }

    #[test]
    fn format_uptime_only_hours_no_days_branch() {
        assert_eq!(format_uptime(3 * 3600 + 2 * 60), "3h2m");
    }

    #[test]
    fn format_bytes_human_exactly_one_kib() {
        assert_eq!(format_bytes(1024, UnitMode::Human), "1.0K");
    }

    #[test]
    fn truncate_mount_preserves_leading_slash_when_wide() {
        let r = truncate_mount("/very/long/mount/point", 40);
        assert!(r.starts_with('/'));
    }

    /// Whole-KiB human labels stay on the `N.0K` form (guards float formatting).
    #[test]
    fn format_bytes_human_whole_kib_through_twenty() {
        for k in 1u64..=20 {
            assert_eq!(
                format_bytes(k * 1024, UnitMode::Human),
                format!("{k}.0K"),
                "k={k}"
            );
        }
    }

    #[test]
    fn format_bytes_human_whole_mib_through_twelve() {
        let mib = 1_048_576u64;
        for n in 1u64..=12 {
            assert_eq!(
                format_bytes(n * mib, UnitMode::Human),
                format!("{n}.0M"),
                "n={n}"
            );
        }
    }

    #[test]
    fn format_bytes_human_fractional_kib_one_decimal() {
        assert_eq!(format_bytes(1024 + 512, UnitMode::Human), "1.5K");
        assert_eq!(format_bytes(10 * 1024 + 512, UnitMode::Human), "10.5K");
    }

    #[test]
    fn format_latency_exactly_below_one_ms_boundary() {
        assert_eq!(format_latency(0.999), "<1ms");
    }

    // ─── format_bytes boundary pins ──────────────────────────────────
    //
    // The human-mode unit thresholds are exact IEC binary powers; an
    // off-by-one drift here re-classifies every line in the storage
    // dashboard. Pin both sides of each boundary.

    #[test]
    fn format_bytes_human_exactly_1024_promotes_to_kib() {
        assert_eq!(format_bytes(1024, UnitMode::Human), "1.0K");
    }

    #[test]
    fn format_bytes_human_1023_stays_in_bytes() {
        assert_eq!(format_bytes(1023, UnitMode::Human), "1023B");
    }

    #[test]
    fn format_bytes_human_exactly_1mib_promotes_to_m() {
        assert_eq!(format_bytes(1_048_576, UnitMode::Human), "1.0M");
    }

    #[test]
    fn format_bytes_human_exactly_1gib_promotes_to_g() {
        assert_eq!(format_bytes(1_073_741_824, UnitMode::Human), "1.0G");
    }

    #[test]
    fn format_bytes_human_exactly_1tib_promotes_to_t() {
        assert_eq!(format_bytes(1_099_511_627_776, UnitMode::Human), "1.0T");
    }

    #[test]
    fn format_bytes_zero_renders_zero_in_every_mode() {
        // Empty filesystems / unused slots show 0 — must stringify
        // identically across modes so column widths stay aligned.
        assert_eq!(format_bytes(0, UnitMode::Human), "0B");
        assert_eq!(format_bytes(0, UnitMode::Bytes), "0B");
        assert_eq!(format_bytes(0, UnitMode::MiB), "0.0M");
        assert_eq!(format_bytes(0, UnitMode::GiB), "0.0G");
    }

    // ─── format_uptime branch pins ───────────────────────────────────

    #[test]
    fn format_uptime_under_one_minute_renders_as_zero_minutes() {
        assert_eq!(format_uptime(30), "0m");
    }

    #[test]
    fn format_uptime_exactly_one_hour_drops_days_keeps_hour_minute() {
        assert_eq!(format_uptime(3600), "1h0m");
    }

    #[test]
    fn format_uptime_exactly_one_day_renders_days_hours_minutes() {
        assert_eq!(format_uptime(86_400), "1d0h0m");
    }

    // ─── format_rate boundary pins ───────────────────────────────────
    //
    // The bytes/sec → human formatter uses 1024-based thresholds for
    // K/M/G, so the unit-switch boundaries are exact powers of 1024.
    // A drift here misclassifies every line in the throughput column.

    #[test]
    fn format_rate_under_one_byte_renders_as_zero() {
        assert_eq!(format_rate(0.0), "0B/s");
        assert_eq!(format_rate(0.5), "0B/s");
    }

    #[test]
    fn format_rate_one_kib_promotes_to_k_per_sec() {
        // 1024 B/s is the exact threshold.
        assert_eq!(format_rate(1024.0), "1.0K/s");
    }

    #[test]
    fn format_rate_1023_bytes_stays_in_b_per_sec() {
        assert_eq!(format_rate(1023.0), "1023B/s");
    }

    #[test]
    fn format_rate_one_mib_promotes_to_m() {
        assert_eq!(format_rate(1_048_576.0), "1.0M/s");
    }

    #[test]
    fn format_rate_one_gib_promotes_to_g() {
        assert_eq!(format_rate(1_073_741_824.0), "1.0G/s");
    }

    // ─── format_latency boundary pins ────────────────────────────────

    #[test]
    fn format_latency_zero_is_sub_millisecond_marker() {
        assert_eq!(format_latency(0.0), "<1ms");
    }

    #[test]
    fn format_latency_one_ms_promotes_to_ms_units() {
        assert_eq!(format_latency(1.0), "1ms");
    }

    #[test]
    fn format_latency_999ms_stays_in_ms() {
        assert_eq!(format_latency(999.0), "999ms");
    }

    #[test]
    fn format_latency_1000ms_promotes_to_seconds_format() {
        assert_eq!(format_latency(1000.0), "1.0s");
    }

    // ─── truncate_mount width-bound pins ─────────────────────────────

    #[test]
    fn truncate_mount_exact_width_pads_with_spaces() {
        // String exactly equals width → padded out (left-aligned)
        // to the column.
        let out = truncate_mount("abc", 5);
        assert_eq!(out.chars().count(), 5);
        assert!(out.starts_with("abc"));
    }

    #[test]
    fn truncate_mount_short_string_pads_to_width() {
        let out = truncate_mount("a", 4);
        assert_eq!(out.chars().count(), 4);
    }
}
