use futures::SinkExt;
use midir::{MidiInput, MidiOutput, MidiInputConnection, MidiOutputConnection};
use std::sync::mpsc as std_mpsc;
use tokio::sync::mpsc;

use crate::app::Message;
use crate::protocol;

const DEVICE_NAME: &str = "EIS4";

pub async fn connect_and_stream(
    output: &mut futures::channel::mpsc::Sender<Message>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    eprintln!("BLE: scanning for MIDI device '{DEVICE_NAME}'...");

    let (midi_in, in_port, midi_out, out_port) = loop {
        match find_midi_ports() {
            Some(found) => break found,
            None => {
                tokio::time::sleep(std::time::Duration::from_millis(500)).await;
            }
        }
    };

    eprintln!("BLE: found ports, connecting...");
    let _ = output.send(Message::BleStatus("Connecting MIDI...".into())).await;

    let (sysex_tx, sysex_rx) = std_mpsc::channel::<Vec<u8>>();

    let _in_conn: MidiInputConnection<()> = midi_in.connect(
        &in_port, "cue-in",
        move |_ts, data, _| {
            if let Some(sysex) = extract_sysex(data) {
                let _ = sysex_tx.send(sysex);
            }
        },
        (),
    ).map_err(|e| format!("MIDI input connect: {e}"))?;

    let mut out_conn: MidiOutputConnection = midi_out.connect(
        &out_port, "cue-out",
    ).map_err(|e| format!("MIDI output connect: {e}"))?;

    eprintln!("BLE: connected");
    let _ = output.send(Message::BleStatus("Connected".into())).await;

    let (cmd_tx, mut cmd_rx) = mpsc::unbounded_channel::<Vec<u8>>();
    let _ = output.send(Message::BleReady(cmd_tx)).await;

    loop {
        while let Ok(sysex) = sysex_rx.try_recv() {
            if let Some(msg) = protocol::parse_sysex(&sysex) {
                let _ = output.send(Message::BleData(msg)).await;
            }
        }

        loop {
            match cmd_rx.try_recv() {
                Ok(pkt) => {
                    if let Err(e) = out_conn.send(&pkt) {
                        eprintln!("BLE: MIDI send error: {e}");
                        return Err(e.into());
                    }
                }
                Err(mpsc::error::TryRecvError::Disconnected) => {
                    eprintln!("BLE: cmd channel closed");
                    return Ok(());
                }
                Err(mpsc::error::TryRecvError::Empty) => break,
            }
        }

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

fn find_midi_ports() -> Option<(
    MidiInput, midir::MidiInputPort,
    MidiOutput, midir::MidiOutputPort,
)> {
    let midi_in = MidiInput::new("cue-in").ok()?;
    let midi_out = MidiOutput::new("cue-out").ok()?;

    let in_ports = midi_in.ports();
    let out_ports = midi_out.ports();

    let in_names: Vec<_> = in_ports.iter()
        .filter_map(|p| midi_in.port_name(p).ok())
        .collect();
    let out_names: Vec<_> = out_ports.iter()
        .filter_map(|p| midi_out.port_name(p).ok())
        .collect();

    if !in_names.is_empty() || !out_names.is_empty() {
        eprintln!("BLE: MIDI ports — in: {:?}, out: {:?}", in_names, out_names);
    }

    let in_port = in_ports.into_iter().find(|p| {
        midi_in.port_name(p).map_or(false, |n| n.contains(DEVICE_NAME))
    })?;

    let out_port = out_ports.into_iter().find(|p| {
        midi_out.port_name(p).map_or(false, |n| n.contains(DEVICE_NAME))
    })?;

    Some((midi_in, in_port, midi_out, out_port))
}

fn extract_sysex(data: &[u8]) -> Option<Vec<u8>> {
    if data.first() != Some(&0xF0) { return None; }
    let end = data.iter().position(|&b| b == 0xF7)?;
    Some(data[1..end].to_vec())
}
