// SPDX-License-Identifier: MIT
// Copyright (c) 2025-2026 SenWeaverCoding
// Licensed under the MIT License.

use super::super::protocol::{ZcCommand, ZcResponse};
use super::super::transport::{Transport, TransportError, TransportKind};
use aardvark_sys::AardvarkHandle;
use async_trait::async_trait;

pub struct AardvarkTransport {

    port: i32,

    bitrate_khz: u32,
}

impl AardvarkTransport {

    pub fn new(port: i32, bitrate_khz: u32) -> Self {
        Self { port, bitrate_khz }
    }

    pub fn probe_connected(&self) -> bool {
        AardvarkHandle::find_devices()
            .into_iter()
            .any(|p| i32::from(p) == self.port || self.port == 0)
    }

    fn open_handle(&self) -> Result<AardvarkHandle, TransportError> {
        AardvarkHandle::open_port(self.port)
            .map_err(|e| TransportError::Other(format!("aardvark open: {e}")))
    }
}

#[async_trait]
impl Transport for AardvarkTransport {
    fn kind(&self) -> TransportKind {
        TransportKind::Aardvark
    }

    fn is_connected(&self) -> bool {
        !AardvarkHandle::find_devices().is_empty()
    }

    async fn send(&self, cmd: &ZcCommand) -> Result<ZcResponse, TransportError> {

        let handle = self.open_handle()?;

        let result: serde_json::Value = match cmd.cmd.as_str() {

            "i2c_scan" => {
                handle
                    .i2c_enable(self.bitrate_khz)
                    .map_err(|e| TransportError::Other(e.to_string()))?;
                let devices: Vec<String> = handle
                    .i2c_scan()
                    .into_iter()
                    .map(|a| format!("{a:#04x}"))
                    .collect();
                serde_json::json!({ "ok": true, "data": { "devices": devices } })
            }

            "i2c_read" => {
                let addr = required_u8(&cmd.params, "addr")?;
                let reg = optional_u8(&cmd.params, "register");
                let len: usize = cmd
                    .params
                    .get("len")
                    .and_then(|v| v.as_u64())
                    .unwrap_or(1)
                    .try_into()
                    .unwrap_or(1);

                handle
                    .i2c_enable(self.bitrate_khz)
                    .map_err(|e| TransportError::Other(e.to_string()))?;

                let data = if let Some(r) = reg {
                    handle.i2c_write_read(addr, &[r], len)
                } else {
                    handle.i2c_read(addr, len)
                }
                .map_err(|e| TransportError::Other(e.to_string()))?;

                let hex: Vec<String> = data.iter().map(|b| format!("{b:#04x}")).collect();
                serde_json::json!({
                    "ok": true,
                    "data": { "bytes": data, "hex": hex }
                })
            }

            "i2c_write" => {
                let addr = required_u8(&cmd.params, "addr")?;
                let bytes = required_byte_array(&cmd.params, "bytes")?;

                handle
                    .i2c_enable(self.bitrate_khz)
                    .map_err(|e| TransportError::Other(e.to_string()))?;
                handle
                    .i2c_write(addr, &bytes)
                    .map_err(|e| TransportError::Other(e.to_string()))?;

                serde_json::json!({
                    "ok": true,
                    "data": { "bytes_written": bytes.len() }
                })
            }

            "spi_transfer" => {
                let bytes = required_byte_array(&cmd.params, "bytes")?;

                handle
                    .spi_enable(self.bitrate_khz)
                    .map_err(|e| TransportError::Other(e.to_string()))?;
                let recv = handle
                    .spi_transfer(&bytes)
                    .map_err(|e| TransportError::Other(e.to_string()))?;

                let hex: Vec<String> = recv.iter().map(|b| format!("{b:#04x}")).collect();
                serde_json::json!({
                    "ok": true,
                    "data": { "received": recv, "hex": hex }
                })
            }

            "gpio_set" => {
                let direction = required_u8(&cmd.params, "direction")?;
                let value = required_u8(&cmd.params, "value")?;

                handle
                    .gpio_set(direction, value)
                    .map_err(|e| TransportError::Other(e.to_string()))?;

                serde_json::json!({
                    "ok": true,
                    "data": { "direction": direction, "value": value }
                })
            }

            "gpio_get" => {
                let val = handle
                    .gpio_get()
                    .map_err(|e| TransportError::Other(e.to_string()))?;

                serde_json::json!({
                    "ok": true,
                    "data": { "value": val }
                })
            }

            unknown => serde_json::json!({
                "ok": false,
                "error": format!("unknown Aardvark command: {unknown}")
            }),
        };

        Ok(ZcResponse {
            ok: result["ok"].as_bool().unwrap_or(false),
            data: result["data"].clone(),
            error: result["error"].as_str().map(String::from),
        })
    }
}

fn required_u8(params: &serde_json::Value, key: &str) -> Result<u8, TransportError> {
    params
        .get(key)
        .and_then(|v| v.as_u64())
        .and_then(|n| u8::try_from(n).ok())
        .ok_or_else(|| {
            TransportError::Protocol(format!("missing or out-of-range u8 parameter: '{key}'"))
        })
}

fn optional_u8(params: &serde_json::Value, key: &str) -> Option<u8> {
    params
        .get(key)
        .and_then(|v| v.as_u64())
        .and_then(|n| u8::try_from(n).ok())
}

fn required_byte_array(params: &serde_json::Value, key: &str) -> Result<Vec<u8>, TransportError> {
    let arr = params
        .get(key)
        .and_then(|v| v.as_array())
        .ok_or_else(|| TransportError::Protocol(format!("missing array parameter: '{key}'")))?;

    arr.iter()
        .enumerate()
        .map(|(i, v)| {
            v.as_u64()
                .and_then(|n| u8::try_from(n).ok())
                .ok_or_else(|| {
                    TransportError::Protocol(format!(
                        "byte at index {i} in '{key}' is not a valid u8"
                    ))
                })
        })
        .collect()
}
