use tokio::net::UdpSocket;
use tokio::sync::mpsc;
use std::time::{Duration, Instant};

use crate::protocol::{self, EisMessage};

const DEFAULT_ADDR: &str = "192.168.4.1:5941";
const SETTINGS_FILE: &str = ".eis4_udp_addr";

pub fn load_addr() -> String {
    let path = dirs_next::home_dir()
        .map(|h| h.join(SETTINGS_FILE))
        .unwrap_or_default();
    std::fs::read_to_string(path)
        .ok()
        .map(|s| s.trim().to_string())
        .filter(|s| !s.is_empty())
        .unwrap_or_else(|| DEFAULT_ADDR.to_string())
}

pub fn save_addr(addr: &str) {
    if let Some(path) = dirs_next::home_dir().map(|h| h.join(SETTINGS_FILE)) {
        let _ = std::fs::write(path, addr);
    }
}
const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(5);
const TIMEOUT: Duration = Duration::from_secs(10);

#[derive(Debug, Clone)]
pub enum UdpEvent {
    Status(String),
    Data(EisMessage),
}

fn extract_sysex_frames(buf: &[u8]) -> Vec<Vec<u8>> {
    let mut frames = Vec::new();
    let mut i = 0;
    while i < buf.len() {
        if buf[i] == 0xF0 {
            if let Some(end) = buf[i..].iter().position(|&b| b == 0xF7) {
                frames.push(buf[i + 1..i + end].to_vec());
                i += end + 1;
                continue;
            }
        }
        i += 1;
    }
    frames
}

pub async fn connect_and_run(
    tx: mpsc::UnboundedSender<UdpEvent>,
    mut cmd_rx: mpsc::UnboundedReceiver<Vec<u8>>,
    addr: String,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let esp_addr = if addr.contains(':') { addr.clone() } else { format!("{addr}:5941") };
    save_addr(&esp_addr);

    loop {
        let _ = tx.send(UdpEvent::Status(format!("Connecting UDP to {esp_addr}...")));

        let sock = match UdpSocket::bind("0.0.0.0:0").await {
            Ok(s) => s,
            Err(e) => {
                let _ = tx.send(UdpEvent::Status(format!("Bind failed: {e}")));
                tokio::time::sleep(Duration::from_secs(2)).await;
                continue;
            }
        };

        if let Err(e) = sock.connect(&esp_addr).await {
            let _ = tx.send(UdpEvent::Status(format!("Connect failed: {e}")));
            tokio::time::sleep(Duration::from_secs(2)).await;
            continue;
        }

        /* Initial keepalive to register with ESP */
        let keepalive = protocol::build_sysex_get_temp();
        let _ = sock.send(&keepalive).await;

        let mut last_rx = Instant::now();
        let mut last_keepalive = Instant::now();
        let mut connected = false;
        let mut buf = [0u8; 4096];

        loop {
            let deadline = tokio::time::sleep(Duration::from_millis(50));
            tokio::pin!(deadline);

            tokio::select! {
                result = sock.recv(&mut buf) => {
                    match result {
                        Ok(n) if n > 0 => {
                            last_rx = Instant::now();
                            if !connected {
                                connected = true;
                                let _ = tx.send(UdpEvent::Status("Connected".into()));
                            }
                            for frame in extract_sysex_frames(&buf[..n]) {
                                if let Some(msg) = protocol::parse_sysex(&frame) {
                                    let _ = tx.send(UdpEvent::Data(msg));
                                }
                            }
                        }
                        Ok(_) => {}
                        Err(e) => {
                            let _ = tx.send(UdpEvent::Status(format!("Recv error: {e}")));
                            break;
                        }
                    }
                }
                cmd = cmd_rx.recv() => {
                    match cmd {
                        Some(pkt) => {
                            if let Err(e) = sock.send(&pkt).await {
                                eprintln!("UDP send: {e}");
                            }
                        }
                        None => return Ok(()),
                    }
                }
                _ = &mut deadline => {}
            }

            if last_keepalive.elapsed() >= KEEPALIVE_INTERVAL {
                let _ = sock.send(&keepalive).await;
                last_keepalive = Instant::now();
            }

            if connected && last_rx.elapsed() >= TIMEOUT {
                let _ = tx.send(UdpEvent::Status("Timeout — reconnecting...".into()));
                break;
            }
        }

        tokio::time::sleep(Duration::from_millis(500)).await;
    }
}
