use std::fs::File;
use std::io::{Cursor, Seek, SeekFrom};
use std::path::PathBuf;
use std::{fs, io, process};

use clap::{arg, value_parser, ArgAction, ArgMatches, Command};
use yara_x_fmt::Formatter;

use crate::config::FormatConfig;
use crate::help::FMT_CHECK_MODE;

pub fn fmt() -> Command {
    super::command("fmt")
        .about("Format YARA source files")
        .arg(
            arg!(<FILE>)
                .help("Path to YARA source file")
                .required(true)
                .value_parser(value_parser!(PathBuf))
                .action(ArgAction::Append),
        )
        .arg(arg!(-c --check  "Run in 'check' mode").long_help(FMT_CHECK_MODE))
}

pub fn exec_fmt(
    args: &ArgMatches,
    config: FormatConfig,
) -> anyhow::Result<()> {
    let files = args.get_many::<PathBuf>("FILE").unwrap();
    let check = args.get_flag("check");

    let formatter = Formatter::new()
        .align_metadata(config.meta.align_values)
        .align_patterns(config.patterns.align_values)
        .indent_section_headers(config.rule.indent_section_headers)
        .indent_section_contents(config.rule.indent_section_contents)
        .indent_spaces(config.rule.indent_spaces)
        .newline_before_curly_brace(config.rule.newline_before_curly_brace)
        .empty_line_before_section_header(
            config.rule.empty_line_before_section_header,
        )
        .empty_line_after_section_header(
            config.rule.empty_line_after_section_header,
        );

    let mut modified = false;

    for file in files {
        let input = fs::read(file.as_path())?;
        modified = if check {
            formatter.format(input.as_slice(), io::sink())?
        } else {
            let mut formatted = Cursor::new(Vec::with_capacity(input.len()));
            if formatter.format(input.as_slice(), &mut formatted)? {
                formatted.seek(SeekFrom::Start(0))?;
                let mut output_file = File::create(file.as_path())?;
                io::copy(&mut formatted, &mut output_file)?;
                true
            } else {
                false
            }
        } || modified;
    }

    if modified {
        process::exit(1)
    }

    Ok(())
}
