use crate::audio::io::AudioIO;
use crate::hw::{common, options::HwOptions, traits};
use crate::message::HwMidiEvent;
use crate::midi::io::MidiEvent;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{Host, HostId, SampleFormat, Stream, StreamConfig};
use midir::{Ignore, MidiInput, MidiInputConnection, MidiOutput, MidiOutputConnection};
use std::sync::mpsc::{self, Receiver, SyncSender};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tracing::error;

const MIDI_IN_PREFIX: &str = "winmidi:in:";
const MIDI_OUT_PREFIX: &str = "winmidi:out:";
const WASAPI_PREFIX: &str = "wasapi:";

impl Default for HwOptions {
    fn default() -> Self {
        Self {
            exclusive: false,
            period_frames: 1024,
            nperiods: 2,
            ignore_hwbuf: false,
            sync_mode: false,
            input_latency_frames: 0,
            output_latency_frames: 0,
        }
    }
}

pub struct HwDriver {
    input_stream: Option<Stream>,
    output_stream: Stream,
    input_rx: Option<Receiver<Vec<f32>>>,
    output_tx: SyncSender<Vec<f32>>,
    cycle_tick_rx: Receiver<()>,
    latest_input: Vec<f32>,
    audio_ins: Vec<Arc<AudioIO>>,
    audio_outs: Vec<Arc<AudioIO>>,
    output_gain_linear: f32,
    output_balance: f32,
    sample_rate: usize,
    period_frames: usize,
    input_channels: usize,
    output_channels: usize,
    playing: bool,
}

impl HwDriver {
    pub fn new_with_options(
        device: &str,
        input_device: Option<&str>,
        rate: i32,
        _bits: i32,
        options: HwOptions,
    ) -> Result<Self, String> {
        let (host, requested_name, backend_label) = select_backend_host_and_device(device)?;
        let output_device = select_output_device(&host, requested_name)
            .ok_or_else(|| format!("No matching {backend_label} output device for '{device}'"))?;
        let output_cfg = select_f32_output_config(&output_device, rate)?;

        let sample_rate = output_cfg.sample_rate as usize;
        let period_frames = options.period_frames.max(1);
        let output_channels = output_cfg.channels as usize;
        let audio_outs: Vec<Arc<AudioIO>> = (0..output_channels)
            .map(|_| Arc::new(AudioIO::new(period_frames)))
            .collect();

        let maybe_input_device = if let Some(input_name) = input_device {
            select_input_device(&host, input_name)
        } else {
            select_input_device(&host, requested_name)
        };
        let maybe_input_cfg = maybe_input_device
            .as_ref()
            .map(|d| select_f32_input_config(d, sample_rate as i32))
            .transpose()?;

        let input_channels = maybe_input_cfg
            .as_ref()
            .map(|cfg| cfg.channels as usize)
            .unwrap_or(0);
        let audio_ins: Vec<Arc<AudioIO>> = (0..input_channels)
            .map(|_| Arc::new(AudioIO::new(period_frames)))
            .collect();

        let (output_tx, output_rx) = mpsc::sync_channel::<Vec<f32>>(8);
        let (cycle_tick_tx, cycle_tick_rx) = mpsc::sync_channel::<()>(8);

        let output_stream = {
            let mut pending = Vec::<f32>::new();
            let mut pending_idx = 0usize;
            let mut frames_since_tick = 0usize;
            output_device
                .build_output_stream(
                    &output_cfg,
                    move |data: &mut [f32], _| {
                        crate::enable_flush_denormals_to_zero();
                        let channels = output_channels.max(1);
                        let callback_frames = data.len() / channels;
                        for sample in data.iter_mut() {
                            loop {
                                if pending_idx < pending.len() {
                                    *sample = pending[pending_idx];
                                    pending_idx += 1;
                                    break;
                                }
                                match output_rx.try_recv() {
                                    Ok(next) => {
                                        pending = next;
                                        pending_idx = 0;
                                    }
                                    Err(_) => {
                                        *sample = 0.0;
                                        break;
                                    }
                                }
                            }
                        }
                        frames_since_tick = frames_since_tick.saturating_add(callback_frames);
                        while frames_since_tick >= period_frames {
                            let _ = cycle_tick_tx.try_send(());
                            frames_since_tick -= period_frames;
                        }
                    },
                    move |e| error!("{backend_label} output stream error: {e}"),
                    None,
                )
                .map_err(|e| format!("Failed to build {backend_label} output stream: {e}"))?
        };
        output_stream
            .play()
            .map_err(|e| format!("Failed to start {backend_label} output stream: {e}"))?;

        let (input_stream, input_rx) =
            if let (Some(input_device), Some(input_cfg)) = (maybe_input_device, maybe_input_cfg) {
                let (input_tx, input_rx) = mpsc::sync_channel::<Vec<f32>>(8);
                let chunk_len = period_frames.saturating_mul(input_channels.max(1));
                let input_stream = {
                    let mut stash: Vec<f32> = Vec::with_capacity(chunk_len.saturating_mul(2));
                    input_device
                        .build_input_stream(
                            &input_cfg,
                            move |data: &[f32], _| {
                                crate::enable_flush_denormals_to_zero();
                                stash.extend_from_slice(data);
                                while stash.len() >= chunk_len {
                                    let chunk: Vec<f32> = stash.drain(..chunk_len).collect();
                                    let _ = input_tx.try_send(chunk);
                                }
                            },
                            move |e| error!("{backend_label} input stream error: {e}"),
                            None,
                        )
                        .map_err(|e| format!("Failed to build {backend_label} input stream: {e}"))?
                };
                input_stream
                    .play()
                    .map_err(|e| format!("Failed to start {backend_label} input stream: {e}"))?;
                (Some(input_stream), Some(input_rx))
            } else {
                (None, None)
            };

        Ok(Self {
            input_stream,
            output_stream,
            input_rx,
            output_tx,
            cycle_tick_rx,
            latest_input: vec![0.0; period_frames.saturating_mul(input_channels.max(1))],
            audio_ins,
            audio_outs,
            output_gain_linear: 1.0,
            output_balance: 0.0,
            sample_rate,
            period_frames,
            input_channels,
            output_channels,
            playing: false,
        })
    }

    pub fn input_channels(&self) -> usize {
        self.input_channels
    }

    pub fn output_channels(&self) -> usize {
        self.output_channels
    }

    pub fn sample_rate(&self) -> i32 {
        self.sample_rate as i32
    }

    pub fn cycle_samples(&self) -> usize {
        self.period_frames
    }

    pub fn input_port(&self, idx: usize) -> Option<Arc<AudioIO>> {
        self.audio_ins.get(idx).cloned()
    }

    pub fn output_port(&self, idx: usize) -> Option<Arc<AudioIO>> {
        self.audio_outs.get(idx).cloned()
    }

    pub fn set_output_gain_balance(&mut self, gain: f32, balance: f32) {
        self.output_gain_linear = gain.max(0.0);
        self.output_balance = balance.clamp(-1.0, 1.0);
    }

    pub fn output_meter_db(&self, gain: f32, balance: f32) -> Vec<f32> {
        common::output_meter_db(&self.audio_outs, gain, balance)
    }

    pub fn output_meter_linear(&self, gain: f32, balance: f32) -> Vec<f32> {
        common::output_meter_linear(&self.audio_outs, gain, balance)
    }

    pub fn run_cycle(&mut self) -> Result<(), String> {
        self.cycle_tick_rx
            .recv_timeout(Duration::from_millis(500))
            .map_err(|_| "Timed out waiting for WASAPI callback".to_string())?;

        let input_frames = self.period_frames;
        let input_channels = self.input_channels.max(1);
        if let Some(rx) = &self.input_rx {
            while let Ok(chunk) = rx.try_recv() {
                self.latest_input = chunk;
            }
        }

        for (ch_idx, io_port) in self.audio_ins.iter().enumerate() {
            let dst = io_port.buffer.lock();
            for frame in 0..input_frames.min(dst.len()) {
                let src_idx = frame * input_channels + ch_idx;
                let sample = self.latest_input.get(src_idx).copied().unwrap_or(0.0);
                dst[frame] = sample;
            }
            *io_port.finished.lock() = true;
        }

        let frames = self.period_frames;
        let channels = self.output_channels;
        let gain = self.output_gain_linear;
        let balance = self.output_balance;
        let mut interleaved = vec![0.0_f32; frames.saturating_mul(channels)];
        if self.playing {
            for (ch_idx, io_port) in self.audio_outs.iter().enumerate() {
                io_port.process();
                let src = io_port.buffer.lock();
                let b = common::channel_balance_gain(channels, ch_idx, balance);
                for (frame, src_sample) in src.iter().enumerate().take(frames) {
                    let idx = frame * channels + ch_idx;
                    interleaved[idx] = *src_sample * gain * b;
                }
            }
        }

        let _ = self.output_tx.try_send(interleaved);
        Ok(())
    }

    pub fn run_assist_step(&mut self) -> Result<bool, String> {
        Ok(false)
    }

    pub fn channel(&mut self) -> &mut Self {
        self
    }

    pub fn set_playing(&mut self, playing: bool) {
        self.playing = playing;
    }
}

unsafe impl Send for HwDriver {}

fn select_output_device(host: &cpal::Host, requested: &str) -> Option<cpal::Device> {
    if requested.eq_ignore_ascii_case("default") || requested.is_empty() {
        return host.default_output_device();
    }
    let devices = host.output_devices().ok()?;
    for dev in devices {
        if let Ok(desc) = dev.description()
            && desc.name().eq_ignore_ascii_case(requested)
        {
            return Some(dev);
        }
    }
    None
}

fn select_input_device(host: &cpal::Host, requested: &str) -> Option<cpal::Device> {
    let requested = requested
        .strip_prefix(WASAPI_PREFIX)
        .unwrap_or(requested)
        .trim();
    if requested.eq_ignore_ascii_case("default") || requested.is_empty() {
        return host.default_input_device();
    }
    let Ok(devices) = host.input_devices() else {
        return host.default_input_device();
    };
    let mut fuzzy_match: Option<cpal::Device> = None;
    let requested_lc = requested.to_lowercase();
    for dev in devices {
        if let Ok(desc) = dev.description() {
            let name = desc.name();
            if name.eq_ignore_ascii_case(requested) {
                return Some(dev);
            }
            if fuzzy_match.is_none() {
                let name_lc = name.to_lowercase();
                if name_lc.contains(&requested_lc) || requested_lc.contains(&name_lc) {
                    fuzzy_match = Some(dev);
                }
            }
        }
    }
    fuzzy_match.or_else(|| host.default_input_device())
}

fn select_backend_host_and_device(device: &str) -> Result<(Host, &str, &'static str), String> {
    let requested = device.strip_prefix(WASAPI_PREFIX).unwrap_or(device).trim();
    let host = cpal::host_from_id(HostId::Wasapi).unwrap_or_else(|_| cpal::default_host());
    Ok((host, requested, "WASAPI"))
}

fn select_f32_output_config(
    device: &cpal::Device,
    requested_rate: i32,
) -> Result<StreamConfig, String> {
    let mut selected = None;
    let ranges = device
        .supported_output_configs()
        .map_err(|e| format!("Failed to query output stream configs: {e}"))?;
    for range in ranges {
        if range.sample_format() != SampleFormat::F32 {
            continue;
        }
        let min = range.min_sample_rate();
        let max = range.max_sample_rate();
        let rate = requested_rate.max(1) as u32;
        let cfg = if rate >= min && rate <= max {
            range.with_sample_rate(rate).config()
        } else {
            range.with_max_sample_rate().config()
        };
        selected = Some(cfg);
        break;
    }
    selected.ok_or_else(|| "No F32 WASAPI output stream configuration was found".to_string())
}

fn select_f32_input_config(
    device: &cpal::Device,
    requested_rate: i32,
) -> Result<StreamConfig, String> {
    let mut selected = None;
    let ranges = device
        .supported_input_configs()
        .map_err(|e| format!("Failed to query input stream configs: {e}"))?;
    for range in ranges {
        if range.sample_format() != SampleFormat::F32 {
            continue;
        }
        let min = range.min_sample_rate();
        let max = range.max_sample_rate();
        let rate = requested_rate.max(1) as u32;
        let cfg = if rate >= min && rate <= max {
            range.with_sample_rate(rate).config()
        } else {
            range.with_max_sample_rate().config()
        };
        selected = Some(cfg);
        break;
    }
    selected.ok_or_else(|| "No F32 WASAPI input stream configuration was found".to_string())
}

pub fn list_midi_input_devices() -> Vec<String> {
    let Ok(midi_in) = MidiInput::new("maolan-midi-list-in") else {
        return Vec::new();
    };
    let mut out = Vec::new();
    for (idx, port) in midi_in.ports().iter().enumerate() {
        if let Ok(name) = midi_in.port_name(port) {
            out.push(format!("{MIDI_IN_PREFIX}{idx}:{name}"));
        }
    }
    out
}

pub fn list_midi_output_devices() -> Vec<String> {
    let Ok(midi_out) = MidiOutput::new("maolan-midi-list-out") else {
        return Vec::new();
    };
    let mut out = Vec::new();
    for (idx, port) in midi_out.ports().iter().enumerate() {
        if let Ok(name) = midi_out.port_name(port) {
            out.push(format!("{MIDI_OUT_PREFIX}{idx}:{name}"));
        }
    }
    out
}

struct MidiInputDevice {
    device: String,
    connection: MidiInputConnection<()>,
}

struct MidiOutputDevice {
    device: String,
    connection: MidiOutputConnection,
}

#[derive(Default)]
pub struct MidiHub {
    inputs: Vec<MidiInputDevice>,
    outputs: Vec<MidiOutputDevice>,
    input_events: Arc<Mutex<Vec<HwMidiEvent>>>,
}

impl MidiHub {
    pub fn open_input(&mut self, device: &str) -> Result<(), String> {
        if self.inputs.iter().any(|d| d.device == device) {
            return Ok(());
        }

        let index = parse_prefixed_index(device, MIDI_IN_PREFIX)?;
        let mut midi_in = MidiInput::new("maolan-midi-in")
            .map_err(|e| format!("Failed to initialize MIDI input: {e}"))?;
        midi_in.ignore(Ignore::None);
        let ports = midi_in.ports();
        let port = ports
            .get(index)
            .ok_or_else(|| format!("MIDI input device index out of range: {index}"))?
            .clone();

        let event_device = device.to_string();
        let queue = self.input_events.clone();
        let connection = midi_in
            .connect(
                &port,
                "maolan-midi-input",
                move |_stamp, data, _| {
                    if data.is_empty() {
                        return;
                    }
                    if let Ok(mut events) = queue.lock() {
                        events.push(HwMidiEvent {
                            device: event_device.clone(),
                            event: MidiEvent::new(0, data.to_vec()),
                        });
                    }
                },
                (),
            )
            .map_err(|e| format!("Failed to open MIDI input '{device}': {e}"))?;

        self.inputs.push(MidiInputDevice {
            device: device.to_string(),
            connection,
        });
        Ok(())
    }

    pub fn open_output(&mut self, device: &str) -> Result<(), String> {
        if self.outputs.iter().any(|d| d.device == device) {
            return Ok(());
        }

        let index = parse_prefixed_index(device, MIDI_OUT_PREFIX)?;
        let midi_out = MidiOutput::new("maolan-midi-out")
            .map_err(|e| format!("Failed to initialize MIDI output: {e}"))?;
        let ports = midi_out.ports();
        let port = ports
            .get(index)
            .ok_or_else(|| format!("MIDI output device index out of range: {index}"))?
            .clone();
        let connection = midi_out
            .connect(&port, "maolan-midi-output")
            .map_err(|e| format!("Failed to open MIDI output '{device}': {e}"))?;

        self.outputs.push(MidiOutputDevice {
            device: device.to_string(),
            connection,
        });
        Ok(())
    }

    pub fn read_events_into(&mut self, out: &mut Vec<HwMidiEvent>) {
        out.clear();
        let Ok(mut queue) = self.input_events.lock() else {
            return;
        };
        out.extend(queue.drain(..));
    }

    pub fn write_events(&mut self, events: &[HwMidiEvent]) {
        if events.is_empty() {
            return;
        }
        for output in &mut self.outputs {
            for event in events {
                if event.device != output.device || event.event.data.is_empty() {
                    continue;
                }
                if let Err(err) = output.connection.send(&event.event.data) {
                    error!("MIDI write error on {}: {}", output.device, err);
                    break;
                }
            }
        }
    }

    pub fn write_events_blocking(&mut self, events: &[HwMidiEvent], _timeout: Duration) {
        self.write_events(events);
    }

    pub fn output_devices(&self) -> Vec<String> {
        self.outputs
            .iter()
            .map(|output| output.device.clone())
            .collect()
    }
}

impl Drop for HwDriver {
    fn drop(&mut self) {
        if let Some(stream) = &self.input_stream {
            let _ = stream.pause();
        }
        let _ = self.output_stream.pause();
    }
}

impl Drop for MidiHub {
    fn drop(&mut self) {
        while let Some(input) = self.inputs.pop() {
            let _ = input.connection.close();
        }
        while let Some(output) = self.outputs.pop() {
            let _ = output.connection.close();
        }
    }
}

fn parse_prefixed_index(device: &str, prefix: &str) -> Result<usize, String> {
    let rest = device
        .strip_prefix(prefix)
        .ok_or_else(|| format!("Unsupported MIDI device id '{device}'"))?;
    let index_str = rest.split(':').next().unwrap_or("");
    index_str
        .parse::<usize>()
        .map_err(|_| format!("Invalid MIDI device id '{device}'"))
}

impl traits::HwWorkerDriver for HwDriver {
    fn cycle_samples(&self) -> usize {
        self.cycle_samples()
    }

    fn sample_rate(&self) -> i32 {
        self.sample_rate()
    }

    fn run_cycle_for_worker(&mut self) -> Result<(), String> {
        self.run_cycle()
    }

    fn run_assist_step_for_worker(&mut self) -> Result<bool, String> {
        self.run_assist_step()
    }
}

impl traits::HwDevice for HwDriver {
    fn input_channels(&self) -> usize {
        self.input_channels()
    }

    fn output_channels(&self) -> usize {
        self.output_channels()
    }

    fn sample_rate(&self) -> i32 {
        self.sample_rate()
    }

    fn latency_ranges(&self) -> ((usize, usize), (usize, usize)) {
        ((0, 0), (0, 0))
    }
}

impl traits::HwMidiHub for MidiHub {
    fn read_events_into(&mut self, out: &mut Vec<HwMidiEvent>) {
        self.read_events_into(out);
    }

    fn write_events(&mut self, events: &[HwMidiEvent]) {
        self.write_events(events);
    }
}
