use anyhow::{Context, Result};

use crate::uvc_control::model::{
    PendingRequest, STREAM_CTRL_SIZE_11, STREAM_CTRL_SIZE_15, UVC_DATA_SIZE,
    UVC_GET_CUR, UVC_GET_DEF, UVC_GET_INFO, UVC_GET_LEN, UVC_GET_MAX, UVC_GET_MIN,
    UVC_GET_RES, UVC_SET_CUR, UVC_VC_REQUEST_ERROR_CODE_CONTROL, UVC_VS_COMMIT_CONTROL,
    UVC_VS_PROBE_CONTROL, USB_DIR_IN, UvcInterfaces, UvcRequestData, UvcState,
    adjust_length, build_streaming_control, log_configfs_snapshot, read_le32, write_le32,
    uvc_frame_index_for_request, uvc_frame_size_for_index,
};

pub(crate) fn handle_setup(
    fd: i32,
    uvc_send_response: libc::c_ulong,
    state: &mut UvcState,
    pending: &mut Option<PendingRequest>,
    interfaces: UvcInterfaces,
    req: crate::uvc_control::model::UsbCtrlRequest,
    debug: bool,
) {
    let selector = (req.w_value >> 8) as u8;
    let interface_lo = (req.w_index & 0xff) as u8;
    let interface_hi = (req.w_index >> 8) as u8;
    let interface_raw = if interface_hi == interfaces.streaming || interface_hi == interfaces.control {
        interface_hi
    } else if interface_lo == interfaces.streaming || interface_lo == interfaces.control {
        interface_lo
    } else {
        interface_hi
    };
    let is_in = (req.b_request_type & USB_DIR_IN) != 0;
    if matches!(selector, UVC_VS_PROBE_CONTROL | UVC_VS_COMMIT_CONTROL) {
        maybe_update_ctrl_len(state, req.w_length, debug);
    }
    let interface = map_interface(interface_raw, selector, interfaces, debug);

    if !is_in && req.b_request == UVC_SET_CUR {
        let len = req.w_length as usize;
        if interface == interfaces.control {
            let payload = vec![0u8; len.min(UVC_DATA_SIZE)];
            let _ = send_response(fd, uvc_send_response, &payload);
            if debug {
                eprintln!(
                    "[lesavka-uvc] VC SET_CUR ack len={} iface={} sel={}",
                    req.w_length, interface, selector
                );
            }
            return;
        }
        if interface != interfaces.streaming {
            let _ = send_stall(fd, uvc_send_response);
            return;
        }
        if len > UVC_DATA_SIZE {
            eprintln!(
                "[lesavka-uvc] SET_CUR too large len={} (max={}); stalling",
                len, UVC_DATA_SIZE
            );
            let _ = send_stall(fd, uvc_send_response);
            return;
        }
        *pending = Some(PendingRequest {
            interface,
            selector,
            expected_len: len,
        });
        let payload = vec![0u8; len];
        let _ = send_response(fd, uvc_send_response, &payload);
        if debug {
            eprintln!(
                "[lesavka-uvc] SET_CUR queued len={} iface={} sel={}",
                req.w_length, interface, selector
            );
        }
        return;
    }

    if !is_in {
        let _ = send_stall(fd, uvc_send_response);
        return;
    }

    let payload = build_in_response(
        state,
        interfaces,
        interface,
        selector,
        req.b_request,
        req.w_length,
    );
    match payload {
        Some(bytes) => {
            if debug {
                eprintln!(
                    "[lesavka-uvc] send IN response rq=0x{:02x} sel={} len={}",
                    req.b_request,
                    selector,
                    bytes.len()
                );
            }
            let _ = send_response(fd, uvc_send_response, &bytes);
        }
        None => {
            let _ = send_stall(fd, uvc_send_response);
        }
    }
}

pub(crate) fn map_interface(raw: u8, selector: u8, interfaces: UvcInterfaces, debug: bool) -> u8 {
    let mapped = if matches!(selector, UVC_VS_PROBE_CONTROL | UVC_VS_COMMIT_CONTROL) {
        interfaces.streaming
    } else if selector == UVC_VC_REQUEST_ERROR_CODE_CONTROL {
        interfaces.control
    } else {
        raw
    };

    if debug && mapped != raw {
        eprintln!(
            "[lesavka-uvc] remapped interface {} -> {} for selector {selector}",
            raw, mapped
        );
    }

    mapped
}

pub(crate) fn maybe_update_ctrl_len(state: &mut UvcState, w_length: u16, debug: bool) {
    let want = w_length as usize;
    if !(want == STREAM_CTRL_SIZE_11 || want == STREAM_CTRL_SIZE_15) || state.ctrl_len == want {
        return;
    }

    state.ctrl_len = want;
    state.default = build_streaming_control(&state.cfg, state.ctrl_len);
    state.probe = state.default;
    state.commit = state.default;

    if debug {
        eprintln!("[lesavka-uvc] ctrl_len set to {}", state.ctrl_len);
    }
}

pub(crate) fn handle_data(
    _fd: i32,
    _uvc_send_response: libc::c_ulong,
    state: &mut UvcState,
    pending: &mut Option<PendingRequest>,
    interfaces: UvcInterfaces,
    data: UvcRequestData,
    debug: bool,
) {
    let Some(pending_request) = pending.take() else {
        if debug {
            eprintln!("[lesavka-uvc] DATA with no pending request; ignoring");
        }
        return;
    };

    if data.length < 0 {
        return;
    }

    let len = data.length as usize;
    if debug && pending_request.expected_len != 0 && len != pending_request.expected_len {
        eprintln!(
            "[lesavka-uvc] DATA len mismatch: expected={} got={}",
            pending_request.expected_len, len
        );
    }
    let slice = &data.data[..len.min(data.data.len())];
    if debug && slice.len() >= STREAM_CTRL_SIZE_11 {
        let interval = read_le32(slice, 4);
        let payload = read_le32(slice, 22);
        eprintln!(
            "[lesavka-uvc] data ctrl fmt={} frame={} interval={} payload={}",
            slice[2], slice[3], interval, payload
        );
    }

    if pending_request.interface == interfaces.streaming
        && matches!(
            pending_request.selector,
            UVC_VS_PROBE_CONTROL | UVC_VS_COMMIT_CONTROL
        )
    {
        let sanitized = sanitize_streaming_control(slice, state);
        if pending_request.selector == UVC_VS_PROBE_CONTROL {
            state.probe = sanitized;
            if debug {
                let interval = read_le32(&state.probe, 4);
                let payload = read_le32(&state.probe, 22);
                eprintln!(
                    "[lesavka-uvc] probe set interval={} payload={}",
                    interval, payload
                );
                log_configfs_snapshot(state, "probe");
            }
        } else {
            state.commit = sanitized;
            if debug {
                let interval = read_le32(&state.commit, 4);
                let payload = read_le32(&state.commit, 22);
                eprintln!(
                    "[lesavka-uvc] commit set interval={} payload={}",
                    interval, payload
                );
                log_configfs_snapshot(state, "commit");
            }
        }
    }
}

pub(crate) fn build_in_response(
    state: &UvcState,
    interfaces: UvcInterfaces,
    interface: u8,
    selector: u8,
    request: u8,
    w_length: u16,
) -> Option<Vec<u8>> {
    let payload = match interface {
        _ if interface == interfaces.streaming => build_streaming_response(state, selector, request),
        _ if interface == interfaces.control => build_control_response(selector, request),
        _ => None,
    }?;

    Some(adjust_length(payload, w_length))
}

pub(crate) fn build_streaming_response(
    state: &UvcState,
    selector: u8,
    request: u8,
) -> Option<Vec<u8>> {
    let current = match selector {
        UVC_VS_PROBE_CONTROL => state.probe,
        UVC_VS_COMMIT_CONTROL => state.commit,
        _ => return None,
    };

    match request {
        UVC_GET_INFO => Some(vec![0x03]),
        UVC_GET_LEN => Some((state.ctrl_len as u16).to_le_bytes().to_vec()),
        UVC_GET_CUR => Some(current[..state.ctrl_len].to_vec()),
        UVC_GET_MIN | UVC_GET_MAX | UVC_GET_DEF | UVC_GET_RES => {
            Some(state.default[..state.ctrl_len].to_vec())
        }
        _ => None,
    }
}

pub(crate) fn build_control_response(selector: u8, request: u8) -> Option<Vec<u8>> {
    match request {
        UVC_GET_INFO => Some(vec![0x03]),
        UVC_GET_LEN => Some(1u16.to_le_bytes().to_vec()),
        UVC_GET_CUR | UVC_GET_MIN | UVC_GET_MAX | UVC_GET_DEF | UVC_GET_RES => {
            if selector == UVC_VC_REQUEST_ERROR_CODE_CONTROL {
                Some(vec![0x00])
            } else {
                Some(vec![0x00])
            }
        }
        _ => None,
    }
}

pub(crate) fn sanitize_streaming_control(
    data: &[u8],
    state: &UvcState,
) -> [u8; crate::uvc_control::model::STREAM_CTRL_SIZE_MAX] {
    let mut out = state.default;
    if data.len() >= STREAM_CTRL_SIZE_11 {
        let format_index = data[2];
        let frame_index = data[3];
        let interval = read_le32(data, 4);
        let host_payload = read_le32(data, 22);

        if format_index == 1 {
            out[2] = 1;
        }
        let frame_index = uvc_frame_index_for_request(frame_index, &state.cfg);
        out[3] = frame_index;
        write_le32(
            &mut out[18..22],
            uvc_frame_size_for_index(frame_index, state.cfg.frame_size),
        );
        let _host_requested_interval = interval;
        write_le32(&mut out[4..8], state.cfg.interval);
        if host_payload > 0 {
            let payload = host_payload.min(state.cfg.max_packet);
            write_le32(&mut out[22..26], payload);
        }
    }
    out
}

pub(crate) fn send_response(fd: i32, req: libc::c_ulong, payload: &[u8]) -> Result<()> {
    let mut resp = UvcRequestData {
        length: payload.len() as i32,
        data: [0u8; UVC_DATA_SIZE],
    };
    let n = payload.len().min(UVC_DATA_SIZE);
    resp.data[..n].copy_from_slice(&payload[..n]);

    let rc = unsafe { libc::ioctl(fd, req, &resp) };
    if rc < 0 {
        let error = std::io::Error::last_os_error();
        eprintln!("[lesavka-uvc] send_response failed: {error}");
        return Err(error).context("UVCIOC_SEND_RESPONSE");
    }
    Ok(())
}

pub(crate) fn send_stall(fd: i32, req: libc::c_ulong) -> Result<()> {
    let resp = UvcRequestData {
        length: -1,
        data: [0u8; UVC_DATA_SIZE],
    };
    let rc = unsafe { libc::ioctl(fd, req, &resp) };
    if rc < 0 {
        let error = std::io::Error::last_os_error();
        eprintln!("[lesavka-uvc] send_stall failed: {error}");
        return Err(error).context("UVCIOC_SEND_RESPONSE(stall)");
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::{
        build_control_response, build_streaming_response, map_interface,
        maybe_update_ctrl_len, sanitize_streaming_control,
    };
    use crate::uvc_control::model::{
        STREAM_CTRL_SIZE_11, STREAM_CTRL_SIZE_15, UVC_GET_CUR, UVC_GET_INFO,
        UVC_GET_LEN, UVC_VC_REQUEST_ERROR_CODE_CONTROL, UVC_VS_PROBE_CONTROL,
        UvcConfig, UvcInterfaces, UvcState, write_le32,
    };

    fn sample_state() -> UvcState {
        UvcState::new(UvcConfig {
            width: 1280,
            height: 720,
            fps: 25,
            interval: 400_000,
            max_packet: 512,
            frame_size: 1024,
        })
    }

    #[test]
    fn map_interface_prefers_streaming_for_probe_requests() {
        let interfaces = UvcInterfaces {
            control: 0,
            streaming: 1,
        };
        assert_eq!(map_interface(0, UVC_VS_PROBE_CONTROL, interfaces, false), 1);
        assert_eq!(map_interface(1, UVC_VC_REQUEST_ERROR_CODE_CONTROL, interfaces, false), 0);
    }

    #[test]
    fn maybe_update_ctrl_len_rebuilds_defaults() {
        let mut state = sample_state();
        maybe_update_ctrl_len(&mut state, STREAM_CTRL_SIZE_11 as u16, false);
        assert_eq!(state.ctrl_len, STREAM_CTRL_SIZE_11);
        maybe_update_ctrl_len(&mut state, STREAM_CTRL_SIZE_15 as u16, false);
        assert_eq!(state.ctrl_len, STREAM_CTRL_SIZE_15);
    }

    #[test]
    fn build_control_response_handles_standard_queries() {
        assert_eq!(build_control_response(0, UVC_GET_INFO), Some(vec![0x03]));
        assert_eq!(build_control_response(0, UVC_GET_LEN), Some(vec![1, 0]));
        assert_eq!(
            build_control_response(UVC_VC_REQUEST_ERROR_CODE_CONTROL, UVC_GET_CUR),
            Some(vec![0x00])
        );
    }

    #[test]
    fn build_streaming_response_uses_current_and_default_profiles() {
        let state = sample_state();
        let cur = build_streaming_response(&state, UVC_VS_PROBE_CONTROL, UVC_GET_CUR)
            .expect("current profile should exist");
        let len = build_streaming_response(&state, UVC_VS_PROBE_CONTROL, UVC_GET_LEN)
            .expect("length should exist");
        assert_eq!(cur.len(), state.ctrl_len);
        assert_eq!(len, (state.ctrl_len as u16).to_le_bytes().to_vec());
    }

    #[test]
    fn sanitize_streaming_control_clamps_payload_to_max_packet() {
        let state = sample_state();
        let mut data = vec![0u8; STREAM_CTRL_SIZE_11];
        data[2] = 1;
        data[3] = 1;
        write_le32(&mut data[4..8], 123_456);
        write_le32(&mut data[22..26], 9_999);
        let sanitized = sanitize_streaming_control(&data, &state);
        assert_eq!(sanitized[2], 1);
        assert_eq!(sanitized[3], 1);
        assert_eq!(crate::uvc_control::model::read_le32(&sanitized, 4), 123_456);
        assert_eq!(crate::uvc_control::model::read_le32(&sanitized, 22), 512);
    }
}
