use clap::{Parser, Subcommand};
use clap_verbosity_flag::Verbosity;
use log::{debug, error, info, warn};
use std::path::PathBuf;

mod bootloader;
mod slip;

#[derive(Parser)]
#[command(name = "espload")]
#[command(about = "A lightweight ESP8266 flasher written in Rust", long_about = None)]
struct Cli {
    /// Serial port (e.g., /dev/ttyUSB0 or COM3)
    #[arg(short, long)]
    port: String,

    /// Baud rate for the bootloader connection (default: 115200)
    #[arg(short, long, default_value_t = 115200)]
    baud: u32,

    #[command(flatten)]
    verbose: Verbosity,

    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Test the connection to the ESP8266 (Handshake)
    Ping,

    /// Flash a binary/hex/srec file to the ESP8266
    Flash {
        /// Path to the firmware file (.hex, .srec, .bin)
        #[arg(short, long)]
        file: PathBuf,

        /// Optional flash start address (e.g., 0x00000). Autodetected if omitted.
        #[arg(short, long, default_value = None)]
        address: Option<String>,
    },
}

fn main() {
    let cli = Cli::parse();

    // Initialize SimpleLogger and let the clap verbosity flag control the log level dynamically
    simple_logger::SimpleLogger::new()
        .with_level(cli.verbose.log_level_filter())
        .init()
        .unwrap();

    // Evaluate the CLI subcommand
    match &cli.command {
        Commands::Ping => match bootloader::EspBootloader::connect(&cli.port, cli.baud) {
            Ok(_) => info!("Status: READY"),
            Err(e) => {
                error!("Connection failed: {}", e);
                std::process::exit(1);
            }
        },
        Commands::Flash { file, address } => {
            debug!("Starting flash command execution");
            info!("Reading and analyzing file {:?}...", file);

            let bin_file_instance = match bin_file::BinFile::from_file(file) {
                Ok(bf) => bf,
                Err(e) => {
                    error!("Failed to load file with bin_file: {:?}", e);
                    std::process::exit(1);
                }
            };

            let start_addr = if let Some(address) = address {
                // Parse hex address string (e.g., "0x0" or "0x1000") into u32
                let clean_addr = address.trim_start_matches("0x").trim_start_matches("0X");
                u32::from_str_radix(clean_addr, 16).unwrap_or(0)
            } else {
                let addr = bin_file_instance.minimum_address().map(|i| i as u32);
                if let Some(addr) = addr {
                    info!(
                        "No address specified. Using autodetected start address: 0x{:05X}",
                        addr
                    );
                    addr
                } else {
                    warn!("No address found in file, defaulting to 0x00000.");
                    0
                }
            };

            // Export all data within the full range (..) using the bin_file library
            let firmware_data = match bin_file_instance.to_bytes(.., None) {
                Ok(data) => data,
                Err(e) => {
                    error!("Failed to extract bytes from file: {:?}", e);
                    std::process::exit(1);
                }
            };

            let total_size = firmware_data.len() as u32;
            let block_size = 1024;

            if total_size == 0 {
                error!("The parsed file contains no data.");
                std::process::exit(1);
            }

            // Establish connection to the ESP8266 bootloader
            match bootloader::EspBootloader::connect(&cli.port, cli.baud) {
                Ok(mut bootloader) => {
                    // Initialize the flash process (erase targeted sectors)
                    if let Err(e) = bootloader.prepare_flash(total_size, block_size, start_addr) {
                        error!("Flash initialization failed: {}", e);
                        std::process::exit(1);
                    }

                    // Transmit firmware chunks block by block
                    info!("Starting transmission of data blocks...");

                    let total_blocks = total_size.div_ceil(block_size);

                    for (seq_number, chunk) in firmware_data.chunks(block_size as usize).enumerate()
                    {
                        debug!("Sending block {}/{}...", seq_number + 1, total_blocks);

                        if let Err(e) = bootloader.write_flash_block(seq_number as u32, chunk) {
                            error!("Error writing block {}: {}", seq_number, e);
                            std::process::exit(1);
                        }
                        info!(
                            "Block {}/{} successfully transmitted.",
                            seq_number + 1,
                            total_blocks
                        );
                    }

                    // Finalize the flash sequence and request a device reboot
                    if let Err(e) = bootloader.finish_flash() {
                        warn!("Notice during wrap-up: {}", e);
                    }
                    info!("🎉 Firmware successfully transferred and started!");
                }
                Err(e) => {
                    error!("Connection failed: {}", e);
                    std::process::exit(1);
                }
            }
        }
    }
}
