//! Output formatting.

use console::style;
use serde::Serialize;
use tabled::{builder::Builder, settings::Style as TableStyle};

/// Output format.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OutputFormat {
    Text,
    Json,
}

impl OutputFormat {
    pub fn from_str(s: &str) -> Self {
        match s.to_lowercase().as_str() {
            "json" => OutputFormat::Json,
            _ => OutputFormat::Text,
        }
    }
}

/// Prints a value in the specified format.
pub fn print_value<T: Serialize + std::fmt::Debug>(value: &T, format: OutputFormat) {
    match format {
        OutputFormat::Json => {
            println!("{}", serde_json::to_string_pretty(value).unwrap());
        }
        OutputFormat::Text => {
            println!("{:#?}", value);
        }
    }
}

/// Prints a success message.
pub fn print_success(message: &str) {
    println!("{} {}", style("✓").green().bold(), message);
}

/// Prints an error message.
pub fn print_error(message: &str) {
    eprintln!("{} {}", style("✗").red().bold(), message);
}

/// Prints a warning message.
pub fn print_warning(message: &str) {
    println!("{} {}", style("⚠").yellow().bold(), message);
}

/// Prints info message.
pub fn print_info(message: &str) {
    println!("{} {}", style("ℹ").blue().bold(), message);
}

/// Prints a key-value pair.
pub fn print_kv(key: &str, value: &str) {
    println!("{}: {}", style(key).bold(), value);
}

/// Prints a header.
pub fn print_header(title: &str) {
    println!();
    println!("{}", style(title).bold().underlined());
    println!();
}

/// Prints a table.
pub fn print_table(headers: Vec<&str>, rows: Vec<Vec<String>>) {
    let mut builder = Builder::default();
    builder.push_record(headers);

    for row in rows {
        builder.push_record(row);
    }

    let mut table = builder.build();
    let styled = table.with(TableStyle::rounded());
    println!("{}", styled);
}

/// Formats SYNOR amount.
pub fn format_synor(sompi: u64) -> String {
    let synor = sompi as f64 / 100_000_000.0;
    format!("{:.8} SYNOR", synor)
}

/// Formats a hash for display.
pub fn format_hash(hash: &str) -> String {
    if hash.len() > 16 {
        format!("{}...{}", &hash[..8], &hash[hash.len() - 8..])
    } else {
        hash.to_string()
    }
}

/// Formats a timestamp.
pub fn format_timestamp(ts: u64) -> String {
    use chrono::{DateTime, Utc};

    let dt = DateTime::<Utc>::from_timestamp_millis(ts as i64)
        .unwrap_or_else(|| DateTime::<Utc>::from_timestamp(0, 0).unwrap());
    dt.format("%Y-%m-%d %H:%M:%S UTC").to_string()
}

/// Formats duration.
pub fn format_duration(seconds: u64) -> String {
    if seconds < 60 {
        format!("{}s", seconds)
    } else if seconds < 3600 {
        format!("{}m {}s", seconds / 60, seconds % 60)
    } else if seconds < 86400 {
        format!("{}h {}m", seconds / 3600, (seconds % 3600) / 60)
    } else {
        format!("{}d {}h", seconds / 86400, (seconds % 86400) / 3600)
    }
}

/// Formats hashrate.
pub fn format_hashrate(hps: f64) -> String {
    const K: f64 = 1000.0;
    const M: f64 = K * 1000.0;
    const G: f64 = M * 1000.0;
    const T: f64 = G * 1000.0;

    if hps >= T {
        format!("{:.2} TH/s", hps / T)
    } else if hps >= G {
        format!("{:.2} GH/s", hps / G)
    } else if hps >= M {
        format!("{:.2} MH/s", hps / M)
    } else if hps >= K {
        format!("{:.2} KH/s", hps / K)
    } else {
        format!("{:.2} H/s", hps)
    }
}

/// Formats bytes size.
pub fn format_size(bytes: u64) -> String {
    const KB: u64 = 1024;
    const MB: u64 = KB * 1024;
    const GB: u64 = MB * 1024;

    if bytes >= GB {
        format!("{:.2} GB", bytes as f64 / GB as f64)
    } else if bytes >= MB {
        format!("{:.2} MB", bytes as f64 / MB as f64)
    } else if bytes >= KB {
        format!("{:.2} KB", bytes as f64 / KB as f64)
    } else {
        format!("{} B", bytes)
    }
}

/// Progress bar for long operations.
pub fn create_progress_bar(len: u64, message: &str) -> indicatif::ProgressBar {
    let pb = indicatif::ProgressBar::new(len);
    pb.set_style(
        indicatif::ProgressStyle::default_bar()
            .template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
            .unwrap()
            .progress_chars("=>-"),
    );
    pb.set_message(message.to_string());
    pb
}

/// Spinner for indeterminate operations.
pub fn create_spinner(message: &str) -> indicatif::ProgressBar {
    let sp = indicatif::ProgressBar::new_spinner();
    sp.set_style(
        indicatif::ProgressStyle::default_spinner()
            .template("{spinner:.green} {msg}")
            .unwrap(),
    );
    sp.set_message(message.to_string());
    sp
}
