use anyhow::{
    Context,
    Result,
};
use clap::{
    Parser,
    Subcommand,
};
use env_logger;
use improv_setup::improv::{
    RawPacket,
    ImprovPacket,
    SendWiFiSettingsPacket,
    RequestCurrentStatePacket,
    RequestDeviceInformationPacket,
    RequestScannedWiFiNetworksPacket,
};
use improv_setup::serial;
use tokio_serial;

#[derive(Subcommand, Clone)]
#[command(arg_required_else_help = true)]
enum DeviceCommands {
    /// Request current state
    State,
    /// Connect to wifi network
    Connect {
        /// SSID of the network
        ssid: String,
        /// Password for the SSID
        password: String,
    },
    /// Request device info
    Info,
    /// WiFi networks visible to device
    Scan,
}

impl Default for DeviceCommands {
    fn default() -> Self {
        return Self::State;
    }
}

#[derive(Subcommand, Clone)]
#[command(arg_required_else_help = true)]
enum Commands {
    /// List available serial devices
    ListDevices,
    /// Device
    Device {
        /// Path to the serial device
        path: String,
        /// Baud rate used for connecting to the serial device
        #[arg(long, default_value_t = 115200)]
        baud_rate: u32,
        #[command(subcommand)]
        device_command: Option<DeviceCommands>,
    },
}

#[derive(Parser)]
#[command(version, about, long_about = None, arg_required_else_help = true)]
struct Cli {
    #[command(subcommand)]
    command: Option<Commands>,
}


#[tokio::main]
async fn main() -> Result<()>{
    env_logger::init();

    let cli = Cli::parse();

    let command = &cli.command.context("No command provided")?.clone();

    match &command {
        Commands::ListDevices => {
            println!("{}", tokio_serial::available_ports()
                .context("Failed to list available ports")?
                .iter()
                .map(|serialport| serialport.port_name.clone())
                .fold(String::new(), |a, b| a + &b + &String::from("\n")));
        },
        Commands::Device {path, baud_rate, device_command} => {
            match &device_command.clone().unwrap_or_default() {
                DeviceCommands::State => {
                    let request_current_state_packet = RequestCurrentStatePacket {};

                    let mut serial_interface = serial::SerialInterface::new(path, *baud_rate).context("Couldn't init serial interface")?;

                    serial_interface.send(&request_current_state_packet).context("Failed to send improv packet")?;

                    let result_bytes = serial_interface.recv_bytes().context("Couldn't receive any improv packet")?;
                    let raw_packet = RawPacket::try_from_bytes(&result_bytes).context("Failed to deserialize packet")?;

                    if let ImprovPacket::CurrentState(current_state_response) = ImprovPacket::try_from_raw_packet(&raw_packet).context("Failed to read packet")? {
                        println!("Current state: {}", &current_state_response.current_state);
                    }

                    if let ImprovPacket::ErrorState(error_state) = ImprovPacket::try_from_raw_packet(&raw_packet).context("Failed to read packet")? {
                        println!("Error state: {}", &error_state.error_state);
                    }
                },
                DeviceCommands::Connect{ssid, password} => {
                    let send_wifi_settings_packet = SendWiFiSettingsPacket {
                        ssid: ssid.clone(),
                        password: password.clone(),
                    };

                    let mut serial_interface = serial::SerialInterface::new(path, *baud_rate).context("Couldn't init serial interface")?;

                    serial_interface.send(&send_wifi_settings_packet).context("Failed to send improv packet")?;

                    let result_bytes = serial_interface.recv_bytes().context("Couldn't receive any improv packet")?;
                    let raw_packet = RawPacket::try_from_bytes(&result_bytes).context("Failed to deserialize packet")?;

                    if let ImprovPacket::RPCResult(rpc_result) = ImprovPacket::try_from_raw_packet(&raw_packet).context("Failed to read packet")? {
                        for r in rpc_result.results {
                            println!("{}", &r);
                        }
                    }
                },
                DeviceCommands::Info => {
                    let request_device_information_packet = RequestDeviceInformationPacket {};

                    let mut serial_interface = serial::SerialInterface::new(path, *baud_rate).context("Couldn't init serial interface")?;

                    serial_interface.send(&request_device_information_packet).context("Failed to send improv packet")?;

                    let result_bytes = serial_interface.recv_bytes().context("Couldn't receive any improv packet")?;
                    let raw_packet = RawPacket::try_from_bytes(&result_bytes).context("Failed to deserialize packet")?;

                    if let ImprovPacket::RPCResult(rpc_result) = ImprovPacket::try_from_raw_packet(&raw_packet).context("Failed to read packet")? {
                        for r in rpc_result.results {
                            println!("{}", &r);
                        }
                    }
                },
                DeviceCommands::Scan => {
                    let request_scanned_wifi_networks_packet = RequestScannedWiFiNetworksPacket {};

                    let mut serial_interface = serial::SerialInterface::new(path, *baud_rate).context("Couldn't init serial interface")?;

                    serial_interface.send(&request_scanned_wifi_networks_packet).context("Failed to send improv packet")?;

                    loop {
                        let result_bytes = serial_interface.recv_bytes().context("Couldn't receive any improv packet")?;
                        let raw_packet = RawPacket::try_from_bytes(&result_bytes).context("Failed to deserialize packet")?;

                        if let ImprovPacket::RPCResult(rpc_result) = ImprovPacket::try_from_raw_packet(&raw_packet).context("Failed to read packet")? {
                            if rpc_result.results.len() <= 0 {
                                break;
                            }
                            println!("{:<25}   {:>3}   {}", &rpc_result.results[0], &rpc_result.results[1], match rpc_result.results[2].as_str() {
                                "YES" => "secure",
                                "NO" => "open",
                                _ => "",
                            });
                        }
                    }
                },
            };

        },
    };

    Ok(())
}