use clap::{Arg, Command};
use color_eyre::Result;
use std::io::Write;

pub struct PrintfOptions {
    pub format: String,
    pub arguments: Vec<String>,
}

pub fn parse_options(matches: &clap::ArgMatches) -> Result<PrintfOptions> {
    let format = matches.get_one::<String>("format")
        .cloned()
        .unwrap_or_default();
    let arguments: Vec<String> = matches.get_many::<String>("arguments")
        .map(|vals| vals.cloned().collect())
        .unwrap_or_default();
    Ok(PrintfOptions { format, arguments })
}

pub fn command() -> Command {
    Command::new("printf")
        .about("Format and print data")
        .ignore_errors(true)
        .allow_hyphen_values(true)
        .arg(Arg::new("format")
            .required(true)
            .allow_hyphen_values(true)
            .help("Format string"))
        .arg(Arg::new("arguments")
            .num_args(0..)
            .allow_hyphen_values(true)
            .help("Arguments for format string"))
}

/// Parse escape sequences in a string (like \n, \t, \xHH, \0NNN)
/// Returns the processed string, stopping at \c if encountered
struct ParsedEscapes {
    result: String,
    stopped: bool,  // true if \c was encountered
}

fn parse_escapes_with_stop(s: &str) -> ParsedEscapes {
    let mut result = String::new();
    let mut chars = s.chars().peekable();

    while let Some(c) = chars.next() {
        if c != '\\' {
            result.push(c);
            continue;
        }

        match chars.next() {
            Some('\\') => result.push('\\'),
            Some('a') => result.push('\x07'),
            Some('b') => result.push('\x08'),
            Some('f') => result.push('\x0c'),
            Some('n') => result.push('\n'),
            Some('r') => result.push('\r'),
            Some('t') => result.push('\t'),
            Some('v') => result.push('\x0b'),
            Some('e') => result.push('\x1b'),
            Some('0') => {
                let mut octal = String::new();
                for _ in 0..3 {
                    match chars.peek() {
                        Some(&ch) if ('0'..='7').contains(&ch) => {
                            octal.push(chars.next().unwrap());
                        }
                        _ => break,
                    }
                }
                let val = u32::from_str_radix(&octal, 8).unwrap_or(0);
                if val <= 0xFF {
                    result.push(char::from(val as u8));
                }
            }
            Some('x') => {
                let mut hex = String::new();
                for _ in 0..2 {
                    match chars.peek() {
                        Some(&ch) if ch.is_ascii_hexdigit() => {
                            hex.push(chars.next().unwrap());
                        }
                        _ => break,
                    }
                }
                if !hex.is_empty() {
                    let val = u32::from_str_radix(&hex, 16).unwrap_or(0);
                    if val <= 0xFF {
                        result.push(char::from(val as u8));
                    }
                } else {
                    result.push_str("\\x");
                }
            }
            Some('c') => {
                // \c stops output
                return ParsedEscapes { result, stopped: true };
            }
            Some(other) => {
                result.push('\\');
                result.push(other);
            }
            None => {
                result.push('\\');
            }
        }
    }
    ParsedEscapes { result, stopped: false }
}

/// Check if format string has any format specifiers that consume arguments
/// %% is not a format specifier, it's just an escaped %
fn has_format_specifiers(format: &str) -> bool {
    let mut chars = format.chars().peekable();
    while let Some(c) = chars.next() {
        if c == '%' {
            // Check if this is %%
            if chars.peek() == Some(&'%') {
                chars.next();  // consume the second %
                continue;  // %% is not a format specifier
            }
            // This is a real format specifier
            return true;
        }
    }
    false
}


/// Process escape sequences in format string when there are no format specifiers
fn process_escapes_in_format(format: &str) -> String {
    let mut result = String::new();
    let mut chars = format.chars().peekable();
    while let Some(c) = chars.next() {
        if c == '\\' {
            match chars.next() {
                Some('\\') => result.push('\\'),
                Some('a') => result.push('\x07'),
                Some('b') => result.push('\x08'),
                Some('f') => result.push('\x0c'),
                Some('n') => result.push('\n'),
                Some('r') => result.push('\r'),
                Some('t') => result.push('\t'),
                Some('v') => result.push('\x0b'),
                Some('e') => result.push('\x1b'),
                Some('0') => {
                    let mut octal = String::new();
                    for _ in 0..3 {
                        match chars.peek() {
                            Some(&ch) if ('0'..='7').contains(&ch) => {
                                octal.push(chars.next().unwrap());
                            }
                            _ => break,
                        }
                    }
                    let val = u32::from_str_radix(&octal, 8).unwrap_or(0);
                    if val <= 0xFF {
                        result.push(char::from(val as u8));
                    }
                }
                Some('x') => {
                    let mut hex = String::new();
                    for _ in 0..2 {
                        match chars.peek() {
                            Some(&ch) if ch.is_ascii_hexdigit() => {
                                hex.push(chars.next().unwrap());
                            }
                            _ => break,
                        }
                    }
                    if !hex.is_empty() {
                        let val = u32::from_str_radix(&hex, 16).unwrap_or(0);
                        if val <= 0xFF {
                            result.push(char::from(val as u8));
                        }
                    } else {
                        result.push_str("\\x");
                    }
                }
                Some('c') => {
                    // \c stops output
                    return result;
                }
                Some(other) => {
                    result.push('\\');
                    result.push(other);
                }
                None => {
                    result.push('\\');
                }
            }
        } else if c == '%' && chars.peek() == Some(&'%') {
            chars.next();  // consume second %
            result.push('%');
        } else {
            result.push(c);
        }
    }
    result
}

/// Get argument as string, or empty string if no more arguments
fn get_arg(arguments: &[String], arg_index: &mut usize) -> String {
    if *arg_index < arguments.len() {
        let arg = arguments[*arg_index].clone();
        *arg_index += 1;
        arg
    } else {
        String::new()
    }
}

/// Get argument as i64 (for width/precision), returns 0 if no more arguments
fn get_arg_i64(arguments: &[String], arg_index: &mut usize) -> i64 {
    if *arg_index < arguments.len() {
        let arg = arguments[*arg_index].clone();
        *arg_index += 1;
        arg.parse::<i64>().unwrap_or(0)
    } else {
        0
    }
}


/// Printf output result
pub fn run(options: PrintfOptions) -> Result<()> {
    // Process format specifiers directly - don't pre-process escapes in format string
    // because \c needs to be handled AFTER %s arguments are processed
    run_printf(&options.format, &options.arguments)
}

/// Run printf with immediate error output to stderr
fn run_printf(format: &str, arguments: &[String]) -> Result<()> {
    // Check if format has any format specifiers that consume arguments
    if !has_format_specifiers(format) {
        print!("{}", process_escapes_in_format(format));
        std::io::stdout().flush()?;
        return Ok(());
    }

    let mut arg_index = 0;
    let mut had_error = false;

    // Process arguments: reuse format string for each set of arguments
    while arg_index < arguments.len() || (arg_index == 0 && arguments.is_empty()) {
        let round_start_arg = arg_index;
        let mut chars = format.chars().peekable();

        while let Some(c) = chars.next() {
            // Handle escape sequences in format string
            if c == '\\' {
                match chars.next() {
                    Some('\\') => print!("\\"),
                    Some('a') => print!("\x07"),
                    Some('b') => print!("\x08"),
                    Some('f') => print!("\x0c"),
                    Some('n') => print!("\n"),
                    Some('r') => print!("\r"),
                    Some('t') => print!("\t"),
                    Some('v') => print!("\x0b"),
                    Some('e') => print!("\x1b"),
                    Some('0') => {
                        let mut octal = String::new();
                        for _ in 0..3 {
                            match chars.peek() {
                                Some(&ch) if ('0'..='7').contains(&ch) => {
                                    octal.push(chars.next().unwrap());
                                }
                                _ => break,
                            }
                        }
                        let val = u32::from_str_radix(&octal, 8).unwrap_or(0);
                        if val <= 0xFF {
                            print!("{}", char::from(val as u8));
                        }
                    }
                    Some('x') => {
                        let mut hex = String::new();
                        for _ in 0..2 {
                            match chars.peek() {
                                Some(&ch) if ch.is_ascii_hexdigit() => {
                                    hex.push(chars.next().unwrap());
                                }
                                _ => break,
                            }
                        }
                        if !hex.is_empty() {
                            let val = u32::from_str_radix(&hex, 16).unwrap_or(0);
                            if val <= 0xFF {
                                print!("{}", char::from(val as u8));
                            }
                        } else {
                            print!("\\x");
                        }
                    }
                    Some('c') => {
                        // \c stops output immediately
                        std::io::stdout().flush()?;
                        return Ok(());
                    }
                    Some(other) => {
                        print!("\\{}", other);
                    }
                    None => {
                        print!("\\");
                    }
                }
                continue;
            }

            if c != '%' {
                print!("{}", c);
                continue;
            }

            // Check for %%
            if chars.peek() == Some(&'%') {
                chars.next();
                print!("%");
                continue;
            }

            // Parse format specifier: %[flags][width][.precision][length]type
            let mut flags = String::new();
            let mut width: Option<i64> = None;
            let mut precision: Option<i64> = None;
            let mut length = String::new();

            // Flags: -, +, space, #, 0
            while let Some(&f) = chars.peek() {
                if f == '-' || f == '+' || f == ' ' || f == '#' || f == '0' {
                    flags.push(chars.next().unwrap());
                } else {
                    break;
                }
            }

            // Width: number or *
            let mut w = 0i64;
            while let Some(&wc) = chars.peek() {
                if wc == '*' {
                    chars.next();
                    w = get_arg_i64(arguments, &mut arg_index);
                    width = Some(w);
                    break;
                } else if wc.is_ascii_digit() {
                    w = w * 10 + (chars.next().unwrap() as i64 - '0' as i64);
                    width = Some(w);
                } else {
                    break;
                }
            }

            // Precision: .number or .*
            if chars.peek() == Some(&'.') {
                chars.next();
                let mut p = 0i64;
                let mut has_digit = false;
                while let Some(&pc) = chars.peek() {
                    if pc == '*' {
                        chars.next();
                        p = get_arg_i64(arguments, &mut arg_index);
                        precision = Some(p);
                        has_digit = true;
                        break;
                    } else if pc.is_ascii_digit() {
                        p = p * 10 + (chars.next().unwrap() as i64 - '0' as i64);
                        precision = Some(p);
                        has_digit = true;
                    } else {
                        break;
                    }
                }
                if !has_digit {
                    precision = Some(0);
                }
            }

            // Length modifier: h, hh, l, ll, L, z, j, t
            while let Some(&l) = chars.peek() {
                if l == 'l' || l == 'h' || l == 'L' || l == 'z' || l == 'j' || l == 't' {
                    length.push(chars.next().unwrap());
                    if l == 'l' && chars.peek() == Some(&'l') {
                        length.push(chars.next().unwrap());
                    }
                    if l == 'h' && chars.peek() == Some(&'h') {
                        length.push(chars.next().unwrap());
                    }
                } else {
                    break;
                }
            }

            // Type specifier
            let type_char = chars.next();

            // Handle %b specially - it interprets escapes in the argument
            if type_char == Some('b') {
                let arg = get_arg(arguments, &mut arg_index);
                let parsed = parse_escapes_with_stop(&arg);
                if parsed.stopped {
                    print!("{}", parsed.result);
                    std::io::stdout().flush()?;
                    return Ok(());
                }
                print!("{}", parsed.result);
                continue;
            }

            // Handle invalid format specifiers - abort immediately
            if type_char.is_none() {
                eprintln!("printf: %: invalid format");
                std::process::exit(1);
            }

            let tc = type_char.unwrap();

            // Handle unsupported format specifiers - abort immediately
            if tc == 'r' {
                eprintln!("printf: %r: invalid format");
                std::process::exit(1);
            }

            // Get argument for other format specifiers
            let arg = get_arg(arguments, &mut arg_index);

            // Format the value - handle invalid numbers with immediate error output
            match tc {
                's' => {
                    let w = width.unwrap_or(0).max(0) as usize;
                    let left_align = flags.contains('-') || width.map(|w| w < 0).unwrap_or(false);
                    if w > 0 && arg.len() < w {
                        if left_align {
                            print!("{:<width$}", arg, width = w);
                        } else {
                            print!("{:>width$}", arg, width = w);
                        }
                    } else {
                        print!("{}", arg);
                    }
                }
                'd' | 'i' | 'u' => {
                    let (val, parse_err) = parse_numeric_arg_with_error(&arg);
                    if let Some(err) = parse_err {
                        eprintln!("{}", err);
                        had_error = true;
                    }
                    let output = format_integer_to_string(val, &flags, width, precision, tc == 'd' || tc == 'i');
                    print!("{}", output);
                }
                'x' | 'X' => {
                    let (val, parse_err) = parse_numeric_arg_with_error(&arg);
                    if let Some(err) = parse_err {
                        eprintln!("{}", err);
                        had_error = true;
                    }
                    let hex_str = if tc == 'X' {
                        format!("{:X}", val.abs() as u64)
                    } else {
                        format!("{:x}", val.abs() as u64)
                    };
                    let prefix = if flags.contains('#') {
                        if tc == 'X' { "0X" } else { "0x" }
                    } else {
                        ""
                    };
                    let num_str = format!("{}{}", prefix, hex_str);
                    let w = width.map(|w| w.abs() as usize).unwrap_or(0);
                    let left_align = flags.contains('-') || width.map(|w| w < 0).unwrap_or(false);
                    if w > 0 && num_str.len() < w {
                        if left_align {
                            print!("{:<width$}", num_str, width = w);
                        } else {
                            print!("{:>width$}", num_str, width = w);
                        }
                    } else {
                        print!("{}", num_str);
                    }
                }
                'o' => {
                    let (val, parse_err) = parse_numeric_arg_with_error(&arg);
                    if let Some(err) = parse_err {
                        eprintln!("{}", err);
                        had_error = true;
                    }
                    let oct_str = format!("{:o}", val.abs() as u64);
                    let prefix = if flags.contains('#') && !oct_str.starts_with('0') { "0" } else { "" };
                    let num_str = format!("{}{}", prefix, oct_str);
                    let w = width.map(|w| w.abs() as usize).unwrap_or(0);
                    let left_align = flags.contains('-') || width.map(|w| w < 0).unwrap_or(false);
                    if w > 0 && num_str.len() < w {
                        if left_align {
                            print!("{:<width$}", num_str, width = w);
                        } else {
                            print!("{:>width$}", num_str, width = w);
                        }
                    } else {
                        print!("{}", num_str);
                    }
                }
                'f' | 'F' | 'e' | 'E' | 'g' | 'G' => {
                    let (val, parse_err) = parse_float_arg_with_error(&arg);
                    if let Some(err) = parse_err {
                        eprintln!("{}", err);
                        had_error = true;
                    }
                    let prec = precision.map(|p| if p < 0 { 6 } else { p as usize }).unwrap_or(6);
                    let output = match tc {
                        'f' | 'F' => format!("{:.prec$}", val, prec = prec),
                        'e' => format!("{:.prec$e}", val, prec = prec),
                        'E' => format!("{:.prec$E}", val, prec = prec),
                        'g' | 'G' => {
                            if val.abs() < 1e-4 || val.abs() >= 10f64.powi(prec as i32) {
                                if tc == 'G' {
                                    format!("{:.prec$E}", val, prec = prec)
                                } else {
                                    format!("{:.prec$e}", val, prec = prec)
                                }
                            } else {
                                let fixed = format!("{:.prec$}", val, prec = prec);
                                fixed.trim_end_matches('0').trim_end_matches('.').to_string()
                            }
                        }
                        _ => unreachable!(),
                    };
                    let w = width.unwrap_or(0).abs() as usize;
                    let left_align = flags.contains('-') || width.map(|w| w < 0).unwrap_or(false);
                    if w > 0 && output.len() < w {
                        if left_align {
                            print!("{:<width$}", output, width = w);
                        } else {
                            print!("{:>width$}", output, width = w);
                        }
                    } else {
                        print!("{}", output);
                    }
                }
                'c' => {
                    let ch = arg.chars().next().unwrap_or('\0');
                    print!("{}", ch);
                }
                '%' => {
                    print!("%");
                }
                _ => {
                    print!("%{}{}{}{}{}",
                        flags,
                        width.map(|w| w.to_string()).unwrap_or_default(),
                        precision.map(|p| format!(".{}", p)).unwrap_or_default(),
                        length,
                        tc);
                }
            }
        }

        std::io::stdout().flush()?;

        // If we consumed no arguments in this round, or all arguments are consumed, break
        if arg_index == round_start_arg || arg_index >= arguments.len() {
            break;
        }
    }

    std::io::stdout().flush()?;

    if had_error {
        // Exit with code 1 without color_eyre formatting
        std::process::exit(1);
    }

    Ok(())
}

/// Format integer to string
fn format_integer_to_string(val: i64, flags: &str, width: Option<i64>, precision: Option<i64>, signed: bool) -> String {
    let left_align = flags.contains('-') || width.map(|w| w < 0).unwrap_or(false);
    let show_sign = flags.contains('+');
    let space_sign = flags.contains(' ') && !show_sign;
    let zero_pad = flags.contains('0') && !left_align;

    // Handle precision for integers (minimum digits)
    let min_digits = precision.map(|p| if p < 0 { 1 } else { p as usize }).unwrap_or(1);
    let abs_val = val.abs();
    let digits = format!("{}", abs_val);

    // Pad digits to minimum precision
    let padded_digits = if digits.len() < min_digits {
        format!("{:0>width$}", digits, width = min_digits)
    } else {
        digits
    };

    // Build the number string with sign
    let num_str = if signed {
        if val < 0 {
            format!("-{}", padded_digits)
        } else if show_sign {
            format!("+{}", padded_digits)
        } else if space_sign {
            format!(" {}", padded_digits)
        } else {
            padded_digits
        }
    } else {
        padded_digits
    };

    // Apply width
    let w = width.map(|w| w.abs() as usize).unwrap_or(0);
    if w > 0 && num_str.len() < w {
        if left_align {
            format!("{:<width$}", num_str, width = w)
        } else if zero_pad {
            if num_str.starts_with('-') || num_str.starts_with('+') || num_str.starts_with(' ') {
                let sign = num_str.chars().next().unwrap();
                let rest = &num_str[1..];
                format!("{}{:0>width$}", sign, rest, width = w - 1)
            } else {
                format!("{:0>width$}", num_str, width = w)
            }
        } else {
            format!("{:>width$}", num_str, width = w)
        }
    } else {
        num_str
    }
}

/// Parse numeric argument with error tracking
/// Returns (value, optional error message)
/// For arguments like "123bad", returns 0 and error (test expects this, not partial parse)
fn parse_numeric_arg_with_error(arg: &str) -> (i64, Option<String>) {
    // Handle single-quote character notation: '"x' or "'x" -> ASCII value
    if arg.starts_with("'") || arg.starts_with("\"") {
        if arg.len() >= 2 {
            let ch = arg.chars().nth(1).unwrap();
            return (ch as i64, None);
        }
    }
    // Check for invalid input
    let trimmed = arg.trim().trim_start_matches('+');

    // Empty string or just "-" is invalid
    if trimmed.is_empty() {
        if arg.trim() == "-" || arg.trim().is_empty() {
            return (0, Some(format!("printf: invalid number '{}'", arg.trim())));
        }
        return (0, None);
    }

    // Try to parse as valid integer
    if let Ok(val) = trimmed.parse::<i64>() {
        return (val, None);
    }

    // Check if it has trailing garbage like "123bad"
    // Per test expectations, this should return 0 and error, not partial parse
    // Check if entire trimmed string is digits (possibly with leading -)
    let check_str = if trimmed.starts_with('-') {
        &trimmed[1..]
    } else {
        trimmed
    };

    if !check_str.chars().all(|c| c.is_ascii_digit()) {
        // Has non-digit characters, return 0 and error
        return (0, Some(format!("printf: invalid number '{}'", arg.trim())));
    }

    // Should have been caught by parse above, but just in case
    (0, Some(format!("printf: invalid number '{}'", arg.trim())))
}

/// Parse float argument with error tracking
fn parse_float_arg_with_error(arg: &str) -> (f64, Option<String>) {
    let trimmed = arg.trim().trim_start_matches('+');
    if trimmed.is_empty() {
        if arg.trim() == "-" {
            return (0.0, Some(format!("printf: invalid number '{}'", arg)));
        }
        return (0.0, None);
    }
    if let Ok(val) = trimmed.parse::<f64>() {
        return (val, None);
    }
    // Try to parse partial number
    let mut digits = String::new();
    let mut has_digit = false;
    let mut has_dot = false;
    for ch in trimmed.chars() {
        if ch.is_ascii_digit() {
            digits.push(ch);
            has_digit = true;
        } else if ch == '.' && !has_dot {
            digits.push(ch);
            has_dot = true;
        } else {
            break;
        }
    }
    if has_digit {
        let val: f64 = digits.parse().unwrap_or(0.0);
        return (val, Some(format!("printf: invalid number '{}'", arg)));
    }
    (0.0, Some(format!("printf: invalid number '{}'", arg)))
}