use crate::ipc::{emit_event, OutTx};
use crate::rtp::{build_rtp_header, rtp_clock_increment};
use codec_lib::{codec_sample_rate, TranscodeState, PT_PCMA, PT_PCMU};
use spandsp::error::SpanDspError;
use spandsp::fax::FaxState;
use spandsp::spandsp_sys;
use spandsp::t30::{T30ModemSupport, T30State};
use spandsp::t38_core::T38TerminalOptions;
use spandsp::t38_terminal::T38Terminal;
use std::collections::VecDeque;
use std::ffi::c_void;
use std::fmt;
use std::net::SocketAddr;
use std::os::raw::c_int;
use std::path::Path;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use std::sync::{Arc, Mutex};
use tokio::net::UdpSocket;
use tokio::sync::watch;
use tokio::task::JoinHandle;
use tokio::time::{self, Duration};
use udptl::UdptlPacket;

#[derive(Debug)]
pub enum FaxEngineError {
    InvalidPath,
    SpanDsp(SpanDspError),
}

impl fmt::Display for FaxEngineError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidPath => f.write_str("fax file path must be valid UTF-8"),
            Self::SpanDsp(err) => err.fmt(f),
        }
    }
}

impl std::error::Error for FaxEngineError {}

impl From<SpanDspError> for FaxEngineError {
    fn from(value: SpanDspError) -> Self {
        Self::SpanDsp(value)
    }
}

pub type Result<T> = std::result::Result<T, FaxEngineError>;

#[derive(Debug, Clone, Copy)]
pub struct FaxSessionOptions {
    pub ecm: bool,
    pub supported_modems: T30ModemSupport,
}

impl Default for FaxSessionOptions {
    fn default() -> Self {
        Self {
            ecm: true,
            supported_modems: T30ModemSupport::default(),
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub struct T38SessionOptions {
    pub fax: FaxSessionOptions,
    pub config: T38TerminalOptions,
    pub use_tep: bool,
    pub fill_bit_removal: bool,
}

impl Default for T38SessionOptions {
    fn default() -> Self {
        Self {
            fax: FaxSessionOptions::default(),
            config: T38TerminalOptions::default(),
            use_tep: false,
            fill_bit_removal: false,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FaxTransferStats {
    pub bit_rate: i32,
    pub error_correcting_mode: bool,
    pub pages_tx: i32,
    pub pages_rx: i32,
    pub image_size: i32,
    pub bad_rows: i32,
    pub longest_bad_row_run: i32,
    pub ecm_retries: i32,
    pub current_status: i32,
    pub rtp_events: i32,
    pub rtn_events: i32,
}

impl From<spandsp_sys::t30_stats_t> for FaxTransferStats {
    fn from(value: spandsp_sys::t30_stats_t) -> Self {
        Self {
            bit_rate: value.bit_rate,
            error_correcting_mode: value.error_correcting_mode != 0,
            pages_tx: value.pages_tx,
            pages_rx: value.pages_rx,
            image_size: value.image_size,
            bad_rows: value.bad_rows,
            longest_bad_row_run: value.longest_bad_row_run,
            ecm_retries: value.error_correcting_mode_retries,
            current_status: value.current_status,
            rtp_events: value.rtp_events,
            rtn_events: value.rtn_events,
        }
    }
}

fn codec_name(codec_pt: u8) -> &'static str {
    match codec_pt {
        PT_PCMU => "PCMU",
        PT_PCMA => "PCMA",
        _ => "unknown",
    }
}

fn completion_label(code: i32) -> Option<String> {
    T30State::completion_code(code).map(|err| format!("{err:?}"))
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct T38OutboundPacket {
    pub ifp: Vec<u8>,
    pub repeat_count: usize,
}

struct FaxCallbackState {
    completion_code: AtomicI32,
    completed: AtomicBool,
}

impl FaxCallbackState {
    fn new() -> Self {
        Self {
            completion_code: AtomicI32::new(i32::MIN),
            completed: AtomicBool::new(false),
        }
    }

    fn completion_code(&self) -> Option<i32> {
        let value = self.completion_code.load(Ordering::SeqCst);
        (value != i32::MIN).then_some(value)
    }

    fn is_completed(&self) -> bool {
        self.completed.load(Ordering::SeqCst)
    }
}

struct T38PacketQueue {
    packets: Mutex<VecDeque<T38OutboundPacket>>,
}

impl T38PacketQueue {
    fn new() -> Self {
        Self {
            packets: Mutex::new(VecDeque::new()),
        }
    }

    fn drain(&self) -> Vec<T38OutboundPacket> {
        let mut guard = self.packets.lock().unwrap();
        guard.drain(..).collect()
    }
}

unsafe extern "C" fn t30_phase_e_handler(user_data: *mut c_void, completion_code: c_int) {
    if user_data.is_null() {
        return;
    }
    let state = unsafe { &*(user_data as *const FaxCallbackState) };
    state
        .completion_code
        .store(completion_code, Ordering::SeqCst);
    state.completed.store(true, Ordering::SeqCst);
}

unsafe extern "C" fn t38_tx_packet_handler(
    _core: *mut spandsp_sys::t38_core_state_t,
    user_data: *mut c_void,
    buf: *const u8,
    len: c_int,
    count: c_int,
) -> c_int {
    if user_data.is_null() || buf.is_null() || len <= 0 {
        return -1;
    }

    let queue = unsafe { &*(user_data as *const T38PacketQueue) };
    let packet = unsafe { std::slice::from_raw_parts(buf, len as usize) };
    queue.packets.lock().unwrap().push_back(T38OutboundPacket {
        ifp: packet.to_vec(),
        repeat_count: count.max(1) as usize,
    });
    0
}

fn path_to_str(path: &Path) -> Result<&str> {
    path.to_str().ok_or(FaxEngineError::InvalidPath)
}

fn configure_t30(
    t30: &T30State,
    file: &Path,
    receive: bool,
    options: FaxSessionOptions,
    callback_state: *mut c_void,
) -> Result<()> {
    if receive {
        t30.set_rx_file(path_to_str(file)?, -1)?;
    } else {
        t30.set_tx_file(path_to_str(file)?, -1, -1)?;
    }
    t30.set_supported_modems(options.supported_modems)?;
    t30.set_ecm_capability(options.ecm)?;
    unsafe {
        t30.set_phase_e_handler_raw(Some(t30_phase_e_handler), callback_state);
    }
    Ok(())
}

pub struct AnalogFaxSession {
    fax: FaxState,
    callback_state: Box<FaxCallbackState>,
}

impl AnalogFaxSession {
    pub fn new_tx(file: &Path, calling_party: bool, options: FaxSessionOptions) -> Result<Self> {
        let fax = FaxState::new(calling_party)?;
        let callback_state = Box::new(FaxCallbackState::new());
        configure_t30(
            &fax.get_t30_state()?,
            file,
            false,
            options,
            callback_state.as_ref() as *const FaxCallbackState as *mut c_void,
        )?;
        Ok(Self {
            fax,
            callback_state,
        })
    }

    pub fn new_rx(file: &Path, calling_party: bool, options: FaxSessionOptions) -> Result<Self> {
        let fax = FaxState::new(calling_party)?;
        let callback_state = Box::new(FaxCallbackState::new());
        configure_t30(
            &fax.get_t30_state()?,
            file,
            true,
            options,
            callback_state.as_ref() as *const FaxCallbackState as *mut c_void,
        )?;
        Ok(Self {
            fax,
            callback_state,
        })
    }

    pub fn rx(&mut self, samples: &mut [i16]) -> usize {
        self.fax.rx(samples)
    }

    pub fn tx(&mut self, samples: &mut [i16]) -> usize {
        self.fax.tx(samples)
    }

    pub fn set_transmit_on_idle(&mut self, enabled: bool) {
        self.fax.set_transmit_on_idle(enabled);
    }

    pub fn call_active(&self) -> Result<bool> {
        Ok(self.fax.get_t30_state()?.call_active())
    }

    pub fn stats(&self) -> Result<FaxTransferStats> {
        Ok(self.fax.get_t30_state()?.get_transfer_statistics().into())
    }

    pub fn completion_code(&self) -> Option<i32> {
        self.callback_state.completion_code()
    }

    pub fn is_completed(&self) -> bool {
        self.callback_state.is_completed()
    }
}

pub struct T38FaxSession {
    terminal: T38Terminal,
    packet_queue: Box<T38PacketQueue>,
    callback_state: Box<FaxCallbackState>,
}

impl T38FaxSession {
    pub fn new_tx(file: &Path, calling_party: bool, options: T38SessionOptions) -> Result<Self> {
        Self::new(file, calling_party, false, options)
    }

    pub fn new_rx(file: &Path, calling_party: bool, options: T38SessionOptions) -> Result<Self> {
        Self::new(file, calling_party, true, options)
    }

    fn new(
        file: &Path,
        calling_party: bool,
        receive: bool,
        options: T38SessionOptions,
    ) -> Result<Self> {
        let packet_queue = Box::new(T38PacketQueue::new());
        let callback_state = Box::new(FaxCallbackState::new());
        let terminal = unsafe {
            T38Terminal::new_raw(
                calling_party,
                Some(t38_tx_packet_handler),
                packet_queue.as_ref() as *const T38PacketQueue as *mut c_void,
            )?
        };
        terminal.set_config(options.config);
        terminal.set_tep_mode(options.use_tep);
        terminal.set_fill_bit_removal(options.fill_bit_removal);
        configure_t30(
            &terminal.get_t30_state()?,
            file,
            receive,
            options.fax,
            callback_state.as_ref() as *const FaxCallbackState as *mut c_void,
        )?;

        Ok(Self {
            terminal,
            packet_queue,
            callback_state,
        })
    }

    pub fn send_timeout(&mut self, elapsed_samples: i32) -> i32 {
        self.terminal.send_timeout(elapsed_samples)
    }

    pub fn rx_ifp_packet(&self, ifp: &[u8], seq_no: u16) -> Result<()> {
        self.terminal
            .get_t38_core_state()?
            .rx_ifp_packet(ifp, seq_no)
            .map_err(Into::into)
    }

    pub fn drain_outbound_packets(&self) -> Vec<T38OutboundPacket> {
        self.packet_queue.drain()
    }

    pub fn call_active(&self) -> Result<bool> {
        Ok(self.terminal.get_t30_state()?.call_active())
    }

    pub fn stats(&self) -> Result<FaxTransferStats> {
        Ok(self
            .terminal
            .get_t30_state()?
            .get_transfer_statistics()
            .into())
    }

    pub fn completion_code(&self) -> Option<i32> {
        self.callback_state.completion_code()
    }

    pub fn is_completed(&self) -> bool {
        self.callback_state.is_completed()
    }
}

pub fn spawn_outbound_audio_fax(
    call_id: String,
    leg_id: String,
    file_path: String,
    media_socket: Arc<UdpSocket>,
    remote_media: SocketAddr,
    codec_pt: u8,
    out_tx: OutTx,
    mut cancel_rx: watch::Receiver<bool>,
) -> JoinHandle<()> {
    tokio::spawn(async move {
        if codec_pt != PT_PCMU && codec_pt != PT_PCMA {
            emit_event(
                &out_tx,
                "fax_failed",
                serde_json::json!({
                    "call_id": call_id,
                    "leg_id": leg_id,
                    "direction": "outbound",
                    "transport": "audio",
                    "file_path": file_path,
                    "error": format!("unsupported fax codec PT{codec_pt}; outbound fax currently requires PCMU or PCMA"),
                }),
            );
            return;
        }

        let mut session = match AnalogFaxSession::new_tx(
            Path::new(&file_path),
            true,
            FaxSessionOptions::default(),
        ) {
            Ok(session) => session,
            Err(error) => {
                emit_event(
                    &out_tx,
                    "fax_failed",
                    serde_json::json!({
                        "call_id": call_id,
                        "leg_id": leg_id,
                        "direction": "outbound",
                        "transport": "audio",
                        "file_path": file_path,
                        "error": error.to_string(),
                    }),
                );
                return;
            }
        };

        emit_event(
            &out_tx,
            "fax_started",
            serde_json::json!({
                "call_id": call_id,
                "leg_id": leg_id,
                "direction": "outbound",
                "transport": "audio",
                "file_path": file_path,
                "codec": codec_name(codec_pt),
                "remote_media": format!("{}:{}", remote_media.ip(), remote_media.port()),
            }),
        );

        let mut interval = time::interval(Duration::from_millis(20));
        let mut transcoder = match TranscodeState::new() {
            Ok(state) => state,
            Err(error) => {
                emit_event(
                    &out_tx,
                    "fax_failed",
                    serde_json::json!({
                        "call_id": call_id,
                        "leg_id": leg_id,
                        "direction": "outbound",
                        "transport": "audio",
                        "file_path": file_path,
                        "error": format!("codec init: {error}"),
                    }),
                );
                return;
            }
        };

        let mut seq: u16 = 0;
        let mut ts: u32 = 0;
        let ssrc: u32 = rand::random();
        let target_rate = codec_sample_rate(codec_pt);
        let mut recv_buf = vec![0u8; 2048];

        loop {
            tokio::select! {
                _ = cancel_rx.changed() => {
                    break;
                }
                recv = media_socket.recv_from(&mut recv_buf) => {
                    match recv {
                        Ok((len, _src)) => {
                            if len <= 12 {
                                continue;
                            }
                            let payload_pt = recv_buf[1] & 0x7F;
                            if payload_pt == 101 {
                                continue;
                            }
                            let payload = &recv_buf[12..len];
                            let (pcm, sample_rate) = match transcoder.decode_to_pcm(payload, payload_pt) {
                                Ok(decoded) => decoded,
                                Err(_) => continue,
                            };
                            let mut pcm_8k = if sample_rate != 8000 {
                                match transcoder.resample(&pcm, sample_rate, 8000) {
                                    Ok(resampled) => resampled,
                                    Err(_) => continue,
                                }
                            } else {
                                pcm
                            };
                            let _ = session.rx(&mut pcm_8k);
                        }
                        Err(_) => break,
                    }
                }
                _ = interval.tick() => {
                    let mut tx_pcm_8k = vec![0i16; 160];
                    let generated = session.tx(&mut tx_pcm_8k);
                    if generated < tx_pcm_8k.len() {
                        tx_pcm_8k[generated..].fill(0);
                    }

                    let tx_pcm = if target_rate != 8000 {
                        match transcoder.resample(&tx_pcm_8k, 8000, target_rate) {
                            Ok(resampled) => resampled,
                            Err(error) => {
                                emit_event(
                                    &out_tx,
                                    "fax_failed",
                                    serde_json::json!({
                                        "call_id": call_id,
                                        "leg_id": leg_id,
                                        "direction": "outbound",
                                        "transport": "audio",
                                        "file_path": file_path,
                                        "error": format!("tx resample: {error}"),
                                    }),
                                );
                                return;
                            }
                        }
                    } else {
                        tx_pcm_8k
                    };

                    let encoded = match transcoder.encode_from_pcm(&tx_pcm, codec_pt) {
                        Ok(encoded) => encoded,
                        Err(error) => {
                            emit_event(
                                &out_tx,
                                "fax_failed",
                                serde_json::json!({
                                    "call_id": call_id,
                                    "leg_id": leg_id,
                                    "direction": "outbound",
                                    "transport": "audio",
                                    "file_path": file_path,
                                    "error": format!("tx encode: {error}"),
                                }),
                            );
                            return;
                        }
                    };

                    let header = build_rtp_header(codec_pt, seq, ts, ssrc);
                    let mut packet = header.to_vec();
                    packet.extend_from_slice(&encoded);
                    if media_socket.send_to(&packet, remote_media).await.is_err() {
                        break;
                    }

                    seq = seq.wrapping_add(1);
                    ts = ts.wrapping_add(rtp_clock_increment(codec_pt));

                    if session.is_completed() {
                        break;
                    }
                }
            }
        }

        let stats = match session.stats() {
            Ok(stats) => stats,
            Err(error) => {
                emit_event(
                    &out_tx,
                    "fax_failed",
                    serde_json::json!({
                        "call_id": call_id,
                        "leg_id": leg_id,
                        "direction": "outbound",
                        "transport": "audio",
                        "file_path": file_path,
                        "error": format!("stats: {error}"),
                    }),
                );
                return;
            }
        };

        let completion_code = session.completion_code();
        let success = completion_code.unwrap_or(-1) == 0;
        emit_event(
            &out_tx,
            "fax_completed",
            serde_json::json!({
                "call_id": call_id,
                "leg_id": leg_id,
                "direction": "outbound",
                "transport": "audio",
                "file_path": file_path,
                "codec": codec_name(codec_pt),
                "success": success,
                "completion_code": completion_code,
                "completion_label": completion_code.and_then(completion_label),
                "stats": {
                    "bit_rate": stats.bit_rate,
                    "error_correcting_mode": stats.error_correcting_mode,
                    "pages_tx": stats.pages_tx,
                    "pages_rx": stats.pages_rx,
                    "image_size": stats.image_size,
                    "bad_rows": stats.bad_rows,
                    "longest_bad_row_run": stats.longest_bad_row_run,
                    "ecm_retries": stats.ecm_retries,
                    "current_status": stats.current_status,
                    "rtp_events": stats.rtp_events,
                    "rtn_events": stats.rtn_events,
                },
            }),
        );
    })
}

pub fn spawn_inbound_audio_fax(
    call_id: String,
    leg_id: String,
    fax_box_id: String,
    caller_number: String,
    file_path: String,
    media_socket: Arc<UdpSocket>,
    remote_media: SocketAddr,
    codec_pt: u8,
    out_tx: OutTx,
    mut cancel_rx: watch::Receiver<bool>,
) -> JoinHandle<()> {
    tokio::spawn(async move {
        if codec_pt != PT_PCMU && codec_pt != PT_PCMA {
            emit_event(
                &out_tx,
                "fax_failed",
                serde_json::json!({
                    "call_id": call_id,
                    "leg_id": leg_id,
                    "direction": "inbound",
                    "transport": "audio",
                    "fax_box_id": fax_box_id,
                    "caller_number": caller_number,
                    "file_path": file_path,
                    "error": format!("unsupported fax codec PT{codec_pt}; inbound fax currently requires PCMU or PCMA"),
                }),
            );
            return;
        }

        let mut session = match AnalogFaxSession::new_rx(
            Path::new(&file_path),
            false,
            FaxSessionOptions::default(),
        ) {
            Ok(session) => session,
            Err(error) => {
                emit_event(
                    &out_tx,
                    "fax_failed",
                    serde_json::json!({
                        "call_id": call_id,
                        "leg_id": leg_id,
                        "direction": "inbound",
                        "transport": "audio",
                        "fax_box_id": fax_box_id,
                        "caller_number": caller_number,
                        "file_path": file_path,
                        "error": error.to_string(),
                    }),
                );
                return;
            }
        };

        emit_event(
            &out_tx,
            "fax_started",
            serde_json::json!({
                "call_id": call_id,
                "leg_id": leg_id,
                "direction": "inbound",
                "transport": "audio",
                "fax_box_id": fax_box_id,
                "caller_number": caller_number,
                "file_path": file_path,
                "codec": codec_name(codec_pt),
                "remote_media": format!("{}:{}", remote_media.ip(), remote_media.port()),
            }),
        );

        let mut interval = time::interval(Duration::from_millis(20));
        let mut transcoder = match TranscodeState::new() {
            Ok(state) => state,
            Err(error) => {
                emit_event(
                    &out_tx,
                    "fax_failed",
                    serde_json::json!({
                        "call_id": call_id,
                        "leg_id": leg_id,
                        "direction": "inbound",
                        "transport": "audio",
                        "fax_box_id": fax_box_id,
                        "caller_number": caller_number,
                        "file_path": file_path,
                        "error": format!("codec init: {error}"),
                    }),
                );
                return;
            }
        };

        let mut seq: u16 = 0;
        let mut ts: u32 = 0;
        let ssrc: u32 = rand::random();
        let target_rate = codec_sample_rate(codec_pt);
        let mut recv_buf = vec![0u8; 2048];

        loop {
            tokio::select! {
                _ = cancel_rx.changed() => {
                    break;
                }
                recv = media_socket.recv_from(&mut recv_buf) => {
                    match recv {
                        Ok((len, _src)) => {
                            if len <= 12 {
                                continue;
                            }
                            let payload_pt = recv_buf[1] & 0x7F;
                            if payload_pt == 101 {
                                continue;
                            }
                            let payload = &recv_buf[12..len];
                            let (pcm, sample_rate) = match transcoder.decode_to_pcm(payload, payload_pt) {
                                Ok(decoded) => decoded,
                                Err(_) => continue,
                            };
                            let mut pcm_8k = if sample_rate != 8000 {
                                match transcoder.resample(&pcm, sample_rate, 8000) {
                                    Ok(resampled) => resampled,
                                    Err(_) => continue,
                                }
                            } else {
                                pcm
                            };
                            let _ = session.rx(&mut pcm_8k);
                        }
                        Err(_) => break,
                    }
                }
                _ = interval.tick() => {
                    let mut tx_pcm_8k = vec![0i16; 160];
                    let generated = session.tx(&mut tx_pcm_8k);
                    if generated < tx_pcm_8k.len() {
                        tx_pcm_8k[generated..].fill(0);
                    }

                    let tx_pcm = if target_rate != 8000 {
                        match transcoder.resample(&tx_pcm_8k, 8000, target_rate) {
                            Ok(resampled) => resampled,
                            Err(error) => {
                                emit_event(
                                    &out_tx,
                                    "fax_failed",
                                    serde_json::json!({
                                        "call_id": call_id,
                                        "leg_id": leg_id,
                                        "direction": "inbound",
                                        "transport": "audio",
                                        "fax_box_id": fax_box_id,
                                        "caller_number": caller_number,
                                        "file_path": file_path,
                                        "error": format!("tx resample: {error}"),
                                    }),
                                );
                                return;
                            }
                        }
                    } else {
                        tx_pcm_8k
                    };

                    let encoded = match transcoder.encode_from_pcm(&tx_pcm, codec_pt) {
                        Ok(encoded) => encoded,
                        Err(error) => {
                            emit_event(
                                &out_tx,
                                "fax_failed",
                                serde_json::json!({
                                    "call_id": call_id,
                                    "leg_id": leg_id,
                                    "direction": "inbound",
                                    "transport": "audio",
                                    "fax_box_id": fax_box_id,
                                    "caller_number": caller_number,
                                    "file_path": file_path,
                                    "error": format!("tx encode: {error}"),
                                }),
                            );
                            return;
                        }
                    };

                    let header = build_rtp_header(codec_pt, seq, ts, ssrc);
                    let mut packet = header.to_vec();
                    packet.extend_from_slice(&encoded);
                    if media_socket.send_to(&packet, remote_media).await.is_err() {
                        break;
                    }

                    seq = seq.wrapping_add(1);
                    ts = ts.wrapping_add(rtp_clock_increment(codec_pt));

                    if session.is_completed() {
                        break;
                    }
                }
            }
        }

        let stats = match session.stats() {
            Ok(stats) => stats,
            Err(error) => {
                emit_event(
                    &out_tx,
                    "fax_failed",
                    serde_json::json!({
                        "call_id": call_id,
                        "leg_id": leg_id,
                        "direction": "inbound",
                        "transport": "audio",
                        "fax_box_id": fax_box_id,
                        "caller_number": caller_number,
                        "file_path": file_path,
                        "error": format!("stats: {error}"),
                    }),
                );
                return;
            }
        };

        let completion_code = session.completion_code();
        let success = completion_code.unwrap_or(-1) == 0;
        emit_event(
            &out_tx,
            "fax_completed",
            serde_json::json!({
                "call_id": call_id,
                "leg_id": leg_id,
                "direction": "inbound",
                "transport": "audio",
                "fax_box_id": fax_box_id,
                "caller_number": caller_number,
                "file_path": file_path,
                "codec": codec_name(codec_pt),
                "success": success,
                "completion_code": completion_code,
                "completion_label": completion_code.and_then(completion_label),
                "stats": {
                    "bit_rate": stats.bit_rate,
                    "error_correcting_mode": stats.error_correcting_mode,
                    "pages_tx": stats.pages_tx,
                    "pages_rx": stats.pages_rx,
                    "image_size": stats.image_size,
                    "bad_rows": stats.bad_rows,
                    "longest_bad_row_run": stats.longest_bad_row_run,
                    "ecm_retries": stats.ecm_retries,
                    "current_status": stats.current_status,
                    "rtp_events": stats.rtp_events,
                    "rtn_events": stats.rtn_events,
                },
            }),
        );
    })
}

pub fn spawn_outbound_t38_fax(
    call_id: String,
    leg_id: String,
    file_path: String,
    media_socket: Arc<UdpSocket>,
    remote_media: SocketAddr,
    out_tx: OutTx,
    mut cancel_rx: watch::Receiver<bool>,
) -> JoinHandle<()> {
    tokio::spawn(async move {
        let mut session = match T38FaxSession::new_tx(
            Path::new(&file_path),
            true,
            T38SessionOptions::default(),
        ) {
            Ok(session) => session,
            Err(error) => {
                emit_event(
                    &out_tx,
                    "fax_failed",
                    serde_json::json!({
                        "call_id": call_id,
                        "leg_id": leg_id,
                        "direction": "outbound",
                        "transport": "t38",
                        "file_path": file_path,
                        "error": error.to_string(),
                    }),
                );
                return;
            }
        };

        emit_event(
            &out_tx,
            "fax_started",
            serde_json::json!({
                "call_id": call_id,
                "leg_id": leg_id,
                "direction": "outbound",
                "transport": "t38",
                "file_path": file_path,
                "codec": "T.38",
                "remote_media": format!("{}:{}", remote_media.ip(), remote_media.port()),
            }),
        );

        let mut interval = time::interval(Duration::from_millis(20));
        let mut udptl_seq: u16 = 0;
        let mut recv_buf = vec![0u8; 2048];

        loop {
            tokio::select! {
                _ = cancel_rx.changed() => {
                    break;
                }
                recv = media_socket.recv_from(&mut recv_buf) => {
                    match recv {
                        Ok((len, _src)) => {
                            let packet = match UdptlPacket::decode(&recv_buf[..len]) {
                                Ok(packet) => packet,
                                Err(_) => continue,
                            };
                            let _ = session.rx_ifp_packet(&packet.primary_ifp, packet.seq_number);
                        }
                        Err(_) => break,
                    }
                }
                _ = interval.tick() => {
                    let _ = session.send_timeout(160);
                    for outbound in session.drain_outbound_packets() {
                        for _ in 0..outbound.repeat_count.max(1) {
                            let encoded = UdptlPacket::with_redundancy(
                                udptl_seq,
                                outbound.ifp.clone(),
                                vec![],
                            )
                            .encode();
                            if media_socket.send_to(&encoded, remote_media).await.is_err() {
                                break;
                            }
                            udptl_seq = udptl_seq.wrapping_add(1);
                        }
                    }

                    if session.is_completed() {
                        break;
                    }
                }
            }
        }

        let stats = match session.stats() {
            Ok(stats) => stats,
            Err(error) => {
                emit_event(
                    &out_tx,
                    "fax_failed",
                    serde_json::json!({
                        "call_id": call_id,
                        "leg_id": leg_id,
                        "direction": "outbound",
                        "transport": "t38",
                        "file_path": file_path,
                        "error": format!("stats: {error}"),
                    }),
                );
                return;
            }
        };

        let completion_code = session.completion_code();
        let success = completion_code.unwrap_or(-1) == 0;
        emit_event(
            &out_tx,
            "fax_completed",
            serde_json::json!({
                "call_id": call_id,
                "leg_id": leg_id,
                "direction": "outbound",
                "transport": "t38",
                "file_path": file_path,
                "codec": "T.38",
                "success": success,
                "completion_code": completion_code,
                "completion_label": completion_code.and_then(completion_label),
                "stats": {
                    "bit_rate": stats.bit_rate,
                    "error_correcting_mode": stats.error_correcting_mode,
                    "pages_tx": stats.pages_tx,
                    "pages_rx": stats.pages_rx,
                    "image_size": stats.image_size,
                    "bad_rows": stats.bad_rows,
                    "longest_bad_row_run": stats.longest_bad_row_run,
                    "ecm_retries": stats.ecm_retries,
                    "current_status": stats.current_status,
                    "rtp_events": stats.rtp_events,
                    "rtn_events": stats.rtn_events,
                },
            }),
        );
    })
}

pub fn spawn_inbound_t38_fax(
    call_id: String,
    leg_id: String,
    fax_box_id: String,
    caller_number: String,
    file_path: String,
    media_socket: Arc<UdpSocket>,
    remote_media: SocketAddr,
    out_tx: OutTx,
    mut cancel_rx: watch::Receiver<bool>,
) -> JoinHandle<()> {
    tokio::spawn(async move {
        let mut session = match T38FaxSession::new_rx(
            Path::new(&file_path),
            false,
            T38SessionOptions::default(),
        ) {
            Ok(session) => session,
            Err(error) => {
                emit_event(
                    &out_tx,
                    "fax_failed",
                    serde_json::json!({
                        "call_id": call_id,
                        "leg_id": leg_id,
                        "direction": "inbound",
                        "transport": "t38",
                        "fax_box_id": fax_box_id,
                        "caller_number": caller_number,
                        "file_path": file_path,
                        "error": error.to_string(),
                    }),
                );
                return;
            }
        };

        emit_event(
            &out_tx,
            "fax_started",
            serde_json::json!({
                "call_id": call_id,
                "leg_id": leg_id,
                "direction": "inbound",
                "transport": "t38",
                "fax_box_id": fax_box_id,
                "caller_number": caller_number,
                "file_path": file_path,
                "codec": "T.38",
                "remote_media": format!("{}:{}", remote_media.ip(), remote_media.port()),
            }),
        );

        let mut interval = time::interval(Duration::from_millis(20));
        let mut udptl_seq: u16 = 0;
        let mut recv_buf = vec![0u8; 2048];

        loop {
            tokio::select! {
                _ = cancel_rx.changed() => {
                    break;
                }
                recv = media_socket.recv_from(&mut recv_buf) => {
                    match recv {
                        Ok((len, _src)) => {
                            let packet = match UdptlPacket::decode(&recv_buf[..len]) {
                                Ok(packet) => packet,
                                Err(_) => continue,
                            };
                            let _ = session.rx_ifp_packet(&packet.primary_ifp, packet.seq_number);
                        }
                        Err(_) => break,
                    }
                }
                _ = interval.tick() => {
                    let _ = session.send_timeout(160);
                    for outbound in session.drain_outbound_packets() {
                        for _ in 0..outbound.repeat_count.max(1) {
                            let encoded = UdptlPacket::with_redundancy(
                                udptl_seq,
                                outbound.ifp.clone(),
                                vec![],
                            )
                            .encode();
                            if media_socket.send_to(&encoded, remote_media).await.is_err() {
                                break;
                            }
                            udptl_seq = udptl_seq.wrapping_add(1);
                        }
                    }

                    if session.is_completed() {
                        break;
                    }
                }
            }
        }

        let stats = match session.stats() {
            Ok(stats) => stats,
            Err(error) => {
                emit_event(
                    &out_tx,
                    "fax_failed",
                    serde_json::json!({
                        "call_id": call_id,
                        "leg_id": leg_id,
                        "direction": "inbound",
                        "transport": "t38",
                        "fax_box_id": fax_box_id,
                        "caller_number": caller_number,
                        "file_path": file_path,
                        "error": format!("stats: {error}"),
                    }),
                );
                return;
            }
        };

        let completion_code = session.completion_code();
        let success = completion_code.unwrap_or(-1) == 0;
        emit_event(
            &out_tx,
            "fax_completed",
            serde_json::json!({
                "call_id": call_id,
                "leg_id": leg_id,
                "direction": "inbound",
                "transport": "t38",
                "fax_box_id": fax_box_id,
                "caller_number": caller_number,
                "file_path": file_path,
                "codec": "T.38",
                "success": success,
                "completion_code": completion_code,
                "completion_label": completion_code.and_then(completion_label),
                "stats": {
                    "bit_rate": stats.bit_rate,
                    "error_correcting_mode": stats.error_correcting_mode,
                    "pages_tx": stats.pages_tx,
                    "pages_rx": stats.pages_rx,
                    "image_size": stats.image_size,
                    "bad_rows": stats.bad_rows,
                    "longest_bad_row_run": stats.longest_bad_row_run,
                    "ecm_retries": stats.ecm_retries,
                    "current_status": stats.current_status,
                    "rtp_events": stats.rtp_events,
                    "rtn_events": stats.rtn_events,
                },
            }),
        );
    })
}
