use super::error::PineError;
use super::protocol::{EmulatorStatus, MemoryAddress, PineCommand, SaveStateSlot, encode_request};

pub(crate) fn read_request(opcode: PineCommand, address: MemoryAddress) -> Result<Vec<u8>, PineError> {
    encode_request(opcode, &address.get().to_le_bytes())
}

pub(crate) fn write_request<const SIZE: usize>(
    opcode: PineCommand,
    address: MemoryAddress,
    value: [u8; SIZE],
) -> Result<Vec<u8>, PineError> {
    let mut body = Vec::with_capacity(4 + SIZE);
    body.extend_from_slice(&address.get().to_le_bytes());
    body.extend_from_slice(&value);
    encode_request(opcode, &body)
}

pub(crate) fn empty_request(opcode: PineCommand) -> Result<Vec<u8>, PineError> {
    encode_request(opcode, &[])
}

pub(crate) fn slot_request(opcode: PineCommand, slot: SaveStateSlot) -> Result<Vec<u8>, PineError> {
    encode_request(opcode, &[slot.get()])
}

pub(crate) fn parse_u8(payload: &[u8]) -> Result<u8, PineError> {
    payload
        .first()
        .copied()
        .ok_or_else(|| invalid_payload("expected 1 byte"))
}

pub(crate) fn parse_u16(payload: &[u8]) -> Result<u16, PineError> {
    let bytes = exact_bytes::<2>(payload)?;
    Ok(u16::from_le_bytes(bytes))
}

pub(crate) fn parse_u32(payload: &[u8]) -> Result<u32, PineError> {
    let bytes = exact_bytes::<4>(payload)?;
    Ok(u32::from_le_bytes(bytes))
}

pub(crate) fn parse_u64(payload: &[u8]) -> Result<u64, PineError> {
    let bytes = exact_bytes::<8>(payload)?;
    Ok(u64::from_le_bytes(bytes))
}

pub(crate) fn parse_status(payload: &[u8]) -> Result<EmulatorStatus, PineError> {
    EmulatorStatus::try_from(parse_u32(payload)?)
}

pub(crate) fn parse_string(payload: &[u8]) -> Result<String, PineError> {
    let length_bytes = payload
        .get(..4)
        .ok_or_else(|| invalid_payload("expected 4-byte string length"))?;
    let length = u32::from_le_bytes([length_bytes[0], length_bytes[1], length_bytes[2], length_bytes[3]]);
    let length = usize::try_from(length).map_err(|_| invalid_payload("string length overflow"))?;

    if length == 0 {
        return Err(invalid_payload("string length must include a null terminator"));
    }

    let string_bytes = payload
        .get(4..4 + length)
        .ok_or_else(|| invalid_payload("string payload shorter than declared length"))?;
    let Some((&0, content)) = string_bytes.split_last() else {
        return Err(invalid_payload("string payload is missing the null terminator"));
    };

    String::from_utf8(content.to_vec()).map_err(|error| PineError::InvalidPacket {
        message: format!("string payload is not valid UTF-8: {error}"),
    })
}

fn exact_bytes<const SIZE: usize>(payload: &[u8]) -> Result<[u8; SIZE], PineError> {
    let bytes = payload
        .get(..SIZE)
        .ok_or_else(|| invalid_payload(&format!("expected {SIZE} bytes")))?;

    if payload.len() != SIZE {
        return Err(invalid_payload(&format!(
            "expected exactly {SIZE} bytes, got {}",
            payload.len()
        )));
    }

    let mut result = [0_u8; SIZE];
    result.copy_from_slice(bytes);
    Ok(result)
}

fn invalid_payload(message: &str) -> PineError {
    PineError::InvalidPacket {
        message: message.to_string(),
    }
}

#[cfg(test)]
mod tests {
    use super::{parse_string, parse_u8, parse_u16};

    #[test]
    fn parse_u8_rejects_empty_payload() {
        assert!(parse_u8(&[]).is_err());
    }

    #[test]
    fn parse_u16_reads_little_endian_value() {
        assert_eq!(parse_u16(&[0x34, 0x12]).expect("payload is valid"), 0x1234);
    }

    #[test]
    fn parse_string_rejects_missing_null_terminator() {
        let payload = [2, 0, 0, 0, b'O', b'K'];
        assert!(parse_string(&payload).is_err());
    }
}
