//! Flash Revka Arduino firmware via arduino-cli.
//!
//! Ensures arduino-cli is available (installs via brew on macOS if missing),
//! installs the AVR core, compiles and uploads the base firmware.

use anyhow::{Context, Result};
use std::process::Command;

/// Revka Arduino Uno base firmware (capabilities, gpio_read, gpio_write).
const FIRMWARE_INO: &str = include_str!("../../firmware/arduino/arduino.ino");

const FQBN: &str = "arduino:avr:uno";
const SKETCH_NAME: &str = "arduino";

/// Check if arduino-cli is available.
pub fn arduino_cli_available() -> bool {
    Command::new("arduino-cli")
        .arg("version")
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false)
}

/// Try to install arduino-cli. Returns Ok(()) if installed or already present.
pub fn ensure_arduino_cli() -> Result<()> {
    if arduino_cli_available() {
        return Ok(());
    }

    #[cfg(target_os = "macos")]
    {
        println!("arduino-cli not found. Installing via Homebrew...");
        let status = Command::new("brew")
            .args(["install", "arduino-cli"])
            .status()
            .context("Failed to run brew install")?;
        if !status.success() {
            anyhow::bail!(
                "brew install arduino-cli failed. Install manually: https://arduino.github.io/arduino-cli/"
            );
        }
        println!("arduino-cli installed.");
        if !arduino_cli_available() {
            anyhow::bail!("arduino-cli still not found after install. Ensure it's in PATH.");
        }
    }

    #[cfg(target_os = "linux")]
    {
        println!("arduino-cli not found. Run the install script:");
        println!(
            "  curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh"
        );
        println!();
        println!("Or install via package manager (e.g. apt install arduino-cli on Debian/Ubuntu).");
        anyhow::bail!("arduino-cli not installed. Install it and try again.");
    }

    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
    {
        println!("arduino-cli not found. Install it: https://arduino.github.io/arduino-cli/");
        anyhow::bail!("arduino-cli not installed.");
    }

    #[allow(unreachable_code)]
    Ok(())
}

/// Ensure arduino:avr core is installed.
fn ensure_avr_core() -> Result<()> {
    let out = Command::new("arduino-cli")
        .args(["core", "list"])
        .output()
        .context("arduino-cli core list failed")?;
    let stdout = String::from_utf8_lossy(&out.stdout);
    if stdout.contains("arduino:avr") {
        return Ok(());
    }

    println!("Installing Arduino AVR core...");
    let status = Command::new("arduino-cli")
        .args(["core", "install", "arduino:avr"])
        .status()
        .context("arduino-cli core install failed")?;
    if !status.success() {
        anyhow::bail!("Failed to install arduino:avr core");
    }
    println!("AVR core installed.");
    Ok(())
}

/// Flash Revka firmware to Arduino at the given port.
pub fn flash_arduino_firmware(port: &str) -> Result<()> {
    ensure_arduino_cli()?;
    ensure_avr_core()?;

    let temp_dir = std::env::temp_dir().join(format!("revka_flash_{}", uuid::Uuid::new_v4()));
    let sketch_dir = temp_dir.join(SKETCH_NAME);
    let ino_path = sketch_dir.join(format!("{}.ino", SKETCH_NAME));

    std::fs::create_dir_all(&sketch_dir).context("Failed to create sketch dir")?;
    std::fs::write(&ino_path, FIRMWARE_INO).context("Failed to write firmware")?;

    let sketch_path = sketch_dir.to_string_lossy();

    // Compile
    println!("Compiling Revka Arduino firmware...");
    let compile = Command::new("arduino-cli")
        .args(["compile", "--fqbn", FQBN, &*sketch_path])
        .output()
        .context("arduino-cli compile failed")?;

    if !compile.status.success() {
        let stderr = String::from_utf8_lossy(&compile.stderr);
        let _ = std::fs::remove_dir_all(&temp_dir);
        anyhow::bail!("Compile failed:\n{}", stderr);
    }

    validate_serial_port_arg(port)?;

    // Upload
    println!("Uploading to {}...", port);
    let upload = Command::new("arduino-cli")
        .args(["upload", "-p", port, "--fqbn", FQBN, &*sketch_path])
        .output()
        .context("arduino-cli upload failed")?;

    let _ = std::fs::remove_dir_all(&temp_dir);

    if !upload.status.success() {
        let stderr = String::from_utf8_lossy(&upload.stderr);
        anyhow::bail!(
            "Upload failed:\n{}\n\nEnsure the board is connected and the port is correct (e.g. /dev/cu.usbmodem* on macOS).",
            stderr
        );
    }

    println!("Revka firmware flashed successfully.");
    println!("The Arduino now supports: capabilities, gpio_read, gpio_write.");
    Ok(())
}

/// Reject port values that begin with `-` (would be parsed by arduino-cli as
/// a misplaced option) or contain shell metacharacters. Expected shapes are
/// serial paths like `/dev/cu.usbmodem14301` or `COM3`.
fn validate_serial_port_arg(port: &str) -> Result<()> {
    if port.is_empty() {
        anyhow::bail!("serial port cannot be empty");
    }
    if port.starts_with('-') {
        anyhow::bail!("serial port '{port}' cannot start with '-' (would be parsed as an option)");
    }
    let allowed =
        |c: char| c.is_ascii_alphanumeric() || matches!(c, '/' | '.' | '-' | '_' | ':' | '\\');
    if !port.chars().all(allowed) {
        anyhow::bail!("serial port '{port}' contains disallowed characters");
    }
    Ok(())
}

/// Resolve port from config or path. Returns the path to use for flashing.
pub fn resolve_port(config: &crate::config::Config, path_override: Option<&str>) -> Option<String> {
    if let Some(p) = path_override {
        return Some(p.to_string());
    }
    config
        .peripherals
        .boards
        .iter()
        .find(|b| b.board == "arduino-uno" && b.transport == "serial")
        .and_then(|b| b.path.clone())
}
