use crate::types::{DomainResult, DomainStatus, ErrorKind};
use colored::*;
use std::io::Write;
use std::path::PathBuf;

pub fn print_available_table(results: &[DomainResult], no_color: bool, no_unicode: bool) {
    let available: Vec<&DomainResult> = results.iter().filter(|r| r.is_available()).collect();

    if available.is_empty() {
        println!("No available domains.");
        return;
    }

    let max_len = available.iter().map(|r| r.full.len()).max().unwrap_or(20);
    let title = "Available Domains";
    let width = max_len.max(title.len()) + 4; // padding, ensure title fits
    let title_padded = format!("{:^width$}", title, width = width);

    if no_unicode {
        // ASCII box
        let border = format!("+{}+", "-".repeat(width));
        println!("{}", border);
        if no_color {
            println!("|{}|", title_padded);
        } else {
            println!("|{}|", title_padded.green());
        }
        println!("+{}+", "-".repeat(width));
        for r in &available {
            println!("| {:<pad$} |", r.full, pad = width - 2);
        }
        println!("{}", border);
    } else {
        // Unicode box
        let top = format!("┌{}┐", "─".repeat(width));
        let sep = format!("├{}┤", "─".repeat(width));
        let bot = format!("└{}┘", "─".repeat(width));
        println!("{}", top);
        if no_color {
            println!("│{}│", title_padded);
        } else {
            println!("│{}│", title_padded.green());
        }
        println!("{}", sep);
        for r in &available {
            println!("│ {:<pad$} │", r.full, pad = width - 2);
        }
        println!("{}", bot);
    }
}

pub fn print_full_table(results: &[DomainResult], no_color: bool, no_unicode: bool) {
    if results.is_empty() {
        println!("No results.");
        return;
    }

    // calc column widths
    let domain_w = results
        .iter()
        .map(|r| r.full.len())
        .max()
        .unwrap_or(10)
        .max(7);
    let status_w = 10; // "registered" is the longest
    let note_w = results
        .iter()
        .map(|r| r.note_str().len())
        .max()
        .unwrap_or(4)
        .max(4);

    let domain_col = domain_w + 2;
    let status_col = status_w + 2;
    let note_col = note_w + 2;

    if no_unicode {
        print_full_table_ascii(results, domain_col, status_col, note_col, no_color);
    } else {
        print_full_table_unicode(results, domain_col, status_col, note_col, no_color);
    }
}

fn print_full_table_unicode(
    results: &[DomainResult],
    dc: usize,
    sc: usize,
    nc: usize,
    no_color: bool,
) {
    let top = format!("┌{}┬{}┬{}┐", "─".repeat(dc), "─".repeat(sc), "─".repeat(nc));
    let sep = format!("├{}┼{}┼{}┤", "─".repeat(dc), "─".repeat(sc), "─".repeat(nc));
    let bot = format!("└{}┴{}┴{}┘", "─".repeat(dc), "─".repeat(sc), "─".repeat(nc));

    println!("{}", top);
    println!(
        "│{:^dc$}│{:^sc$}│{:^nc$}│",
        "Domains",
        "Status",
        "Note",
        dc = dc,
        sc = sc,
        nc = nc,
    );
    println!("{}", sep);

    for r in results {
        let domain_str = format!(" {:<width$} ", r.full, width = dc - 2);
        let status_str = format!(" {:<width$} ", r.status_str(), width = sc - 2);
        let note_str = format!(" {:<width$} ", r.note_str(), width = nc - 2);

        if no_color {
            println!("│{}│{}│{}│", domain_str, status_str, note_str);
        } else {
            let colored_domain = color_domain(&domain_str, &r.status);
            println!("│{}│{}│{}│", colored_domain, status_str, note_str);
        }
    }

    println!("{}", bot);
}

fn print_full_table_ascii(
    results: &[DomainResult],
    dc: usize,
    sc: usize,
    nc: usize,
    no_color: bool,
) {
    let border = format!("+{}+{}+{}+", "-".repeat(dc), "-".repeat(sc), "-".repeat(nc));

    println!("{}", border);
    println!(
        "|{:^dc$}|{:^sc$}|{:^nc$}|",
        "Domains",
        "Status",
        "Note",
        dc = dc,
        sc = sc,
        nc = nc,
    );
    println!("{}", border);

    for r in results {
        let domain_str = format!(" {:<width$} ", r.full, width = dc - 2);
        let status_str = format!(" {:<width$} ", r.status_str(), width = sc - 2);
        let note_str = format!(" {:<width$} ", r.note_str(), width = nc - 2);

        if no_color {
            println!("|{}|{}|{}|", domain_str, status_str, note_str);
        } else {
            let colored_domain = color_domain(&domain_str, &r.status);
            println!("|{}|{}|{}|", colored_domain, status_str, note_str);
        }
    }

    println!("{}", border);
}

fn color_domain(domain: &str, status: &DomainStatus) -> ColoredString {
    match status {
        DomainStatus::Available => domain.green(),
        DomainStatus::Registered { .. } => domain.red(),
        DomainStatus::Error { kind, .. } => match kind {
            ErrorKind::InvalidTld => domain.yellow(),
            _ => domain.blue(),
        },
    }
}

pub fn print_csv(results: &[DomainResult]) {
    println!("Domains, Status, Note");
    for r in results {
        println!("{}, {}, {}", r.full, r.status_str(), r.note_str());
    }
}

pub fn write_csv_file(results: &[DomainResult], path: &PathBuf) -> Result<(), String> {
    let mut file =
        std::fs::File::create(path).map_err(|e| format!("Could not create CSV file: {}", e))?;
    writeln!(file, "Domains, Status, Note").map_err(|e| format!("Write error: {}", e))?;
    for r in results {
        writeln!(file, "{}, {}, {}", r.full, r.status_str(), r.note_str())
            .map_err(|e| format!("Write error: {}", e))?;
    }
    Ok(())
}

pub fn print_errors(results: &[DomainResult], verbose: bool) {
    for r in results {
        if let DomainStatus::Error { kind, message } = &r.status {
            match kind {
                ErrorKind::InvalidTld => {
                    eprintln!("Error for {} : does not seem to exist", r.full);
                }
                _ => {
                    if verbose {
                        eprintln!("Error for {} : {} (raw: {})", r.full, message, message);
                    } else {
                        eprintln!(
                            "Error for {} : unknown error",
                            r.full
                        );
                    }
                }
            }
        }
    }
}

pub fn print_progress(current: usize, total: usize) {
    use std::sync::Mutex;
    use std::time::Instant;
    static START: Mutex<Option<Instant>> = Mutex::new(None);

    let mut lock = START.lock().unwrap();
    let start = *lock.get_or_insert_with(Instant::now);

    let percent = (current as f64 / total as f64 * 100.0) as u32;
    eprint!("\rParsing results : {}%", percent);
    if current == total {
        let secs = start.elapsed().as_secs_f64();
        eprintln!("\rParsing results : Done (Took {:.1}s)   ", secs);
        *lock = None; // reset for next search
    }
}
