use std::fmt;

use super::error::PineError;

/// The default PCSX2 Pine slot used by PCSX2.
///
/// # Examples
///
/// ```rust
/// use pine_client::DEFAULT_PCSX2_SLOT;
///
/// assert_eq!(DEFAULT_PCSX2_SLOT, 28_011);
/// ```
pub const DEFAULT_PCSX2_SLOT: u16 = 28_011;

/// Maximum Pine request size, including the 4-byte length header.
pub(crate) const MAX_REQUEST_SIZE: usize = 650_000;

/// Maximum Pine response size, including the 4-byte length header.
pub(crate) const MAX_RESPONSE_SIZE: usize = 450_000;

/// A 32-bit address inside emulator memory.
///
/// # Examples
///
/// ```rust
/// use pine_client::MemoryAddress;
///
/// let address = MemoryAddress::new(0x0034_7D34);
/// assert_eq!(address.get(), 0x0034_7D34);
/// ```
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct MemoryAddress(u32);

impl MemoryAddress {
    /// Creates a memory address from a raw 32-bit value.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::MemoryAddress;
    ///
    /// let address = MemoryAddress::new(0x1234_5678);
    /// assert_eq!(address.get(), 0x1234_5678);
    /// ```
    #[must_use]
    pub const fn new(value: u32) -> Self {
        Self(value)
    }

    /// Returns the raw 32-bit address value.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::MemoryAddress;
    ///
    /// let address = MemoryAddress::new(0x1234_5678);
    /// assert_eq!(address.get(), 0x1234_5678);
    /// ```
    #[must_use]
    pub const fn get(self) -> u32 {
        self.0
    }
}

/// A save-state slot accepted by the Pine protocol.
///
/// # Examples
///
/// ```rust
/// use pine_client::SaveStateSlot;
///
/// let slot = SaveStateSlot::new(3);
/// assert_eq!(slot.get(), 3);
/// ```
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct SaveStateSlot(u8);

impl SaveStateSlot {
    /// Creates a save-state slot wrapper.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::SaveStateSlot;
    ///
    /// let slot = SaveStateSlot::new(5);
    /// assert_eq!(slot.get(), 5);
    /// ```
    #[must_use]
    pub const fn new(value: u8) -> Self {
        Self(value)
    }

    /// Returns the raw save-state slot value.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::SaveStateSlot;
    ///
    /// let slot = SaveStateSlot::new(5);
    /// assert_eq!(slot.get(), 5);
    /// ```
    #[must_use]
    pub const fn get(self) -> u8 {
        self.0
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub(crate) enum PineCommand {
    /// Reads an 8-bit value from emulator memory.
    Read8 = 0x00,
    /// Reads a 16-bit value from emulator memory.
    Read16 = 0x01,
    /// Reads a 32-bit value from emulator memory.
    Read32 = 0x02,
    /// Reads a 64-bit value from emulator memory.
    Read64 = 0x03,
    /// Writes an 8-bit value to emulator memory.
    Write8 = 0x04,
    /// Writes a 16-bit value to emulator memory.
    Write16 = 0x05,
    /// Writes a 32-bit value to emulator memory.
    Write32 = 0x06,
    /// Writes a 64-bit value to emulator memory.
    Write64 = 0x07,
    /// Returns the emulator version string.
    Version = 0x08,
    /// Saves a state to a slot.
    SaveState = 0x09,
    /// Loads a state from a slot.
    LoadState = 0x0A,
    /// Returns the current game title.
    Title = 0x0B,
    /// Returns the current game serial.
    Id = 0x0C,
    /// Returns the current game UUID/CRC.
    Uuid = 0x0D,
    /// Returns the current game version.
    GameVersion = 0x0E,
    /// Returns the emulator status.
    Status = 0x0F,
}

impl PineCommand {
    #[must_use]
    pub(crate) const fn opcode(self) -> u8 {
        self as u8
    }
}

/// The runtime state reported by the emulator.
///
/// # Examples
///
/// ```rust
/// use pine_client::EmulatorStatus;
///
/// assert_eq!(EmulatorStatus::Running.to_string(), "running");
/// ```
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum EmulatorStatus {
    /// The emulator is currently running the game loop.
    Running = 0,
    /// The emulator is paused.
    Paused = 1,
    /// The emulator is shut down or has no active VM.
    Shutdown = 2,
}

impl TryFrom<u32> for EmulatorStatus {
    type Error = PineError;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(Self::Running),
            1 => Ok(Self::Paused),
            2 => Ok(Self::Shutdown),
            other => Err(PineError::InvalidPacket {
                message: format!("unknown emulator status {other}"),
            }),
        }
    }
}

impl fmt::Display for EmulatorStatus {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        let value = match self {
            Self::Running => "running",
            Self::Paused => "paused",
            Self::Shutdown => "shutdown",
        };
        formatter.write_str(value)
    }
}

pub(crate) fn encode_request(opcode: PineCommand, body: &[u8]) -> Result<Vec<u8>, PineError> {
    let header_len = 5; // 4 bytes size + 1 byte opcode
    let body_len = body.len();
    if body_len > MAX_REQUEST_SIZE.saturating_sub(header_len) {
        return Err(PineError::InvalidPacket {
            message: format!("request exceeds Pine limit: {}", header_len + body_len),
        });
    }
    let packet_size = header_len + body_len;
    let packet_size_u32 = packet_size as u32; // 已知不会溢出 u32
    let mut packet = Vec::with_capacity(packet_size);
    packet.extend_from_slice(&packet_size_u32.to_le_bytes());
    packet.push(opcode.opcode());
    packet.extend_from_slice(body);
    Ok(packet)
}

pub(crate) fn decode_response(packet: &[u8], opcode: u8) -> Result<&[u8], PineError> {
    if packet.len() < 5 {
        return Err(PineError::InvalidPacket {
            message: "response shorter than minimum Pine header".to_string(),
        });
    }

    let declared_size = u32::from_le_bytes([packet[0], packet[1], packet[2], packet[3]]);
    let declared_size = usize::try_from(declared_size).map_err(|_| PineError::InvalidPacket {
        message: "response size does not fit usize".to_string(),
    })?;

    if declared_size != packet.len() {
        return Err(PineError::InvalidPacket {
            message: format!(
                "response size mismatch: declared {declared_size}, actual {}",
                packet.len()
            ),
        });
    }

    if declared_size > MAX_RESPONSE_SIZE {
        return Err(PineError::InvalidPacket {
            message: format!("response exceeds Pine limit: {declared_size}"),
        });
    }

    match packet[4] {
        0x00 => Ok(&packet[5..]),
        0xFF => Err(PineError::CommandFailed { opcode }),
        other => Err(PineError::InvalidPacket {
            message: format!("unknown response code {other:#04x}"),
        }),
    }
}

#[cfg(test)]
mod tests {
    use crate::PineError;

    use super::{DEFAULT_PCSX2_SLOT, EmulatorStatus, MemoryAddress, PineCommand, decode_response, encode_request};

    #[test]
    fn default_slot_is_stable() {
        assert_eq!(DEFAULT_PCSX2_SLOT, 28_011);
    }

    #[test]
    fn memory_address_round_trips_value() {
        let address = MemoryAddress::new(0x1234_5678);
        assert_eq!(address.get(), 0x1234_5678);
    }

    #[test]
    fn encode_request_writes_size_header_and_opcode() {
        let packet = encode_request(PineCommand::Read8, &[0xAA, 0xBB]).expect("request is valid");
        assert_eq!(&packet[..4], &(7_u32).to_le_bytes());
        assert_eq!(packet[4], PineCommand::Read8.opcode());
        assert_eq!(&packet[5..], &[0xAA, 0xBB]);
    }

    #[test]
    fn decode_response_rejects_size_mismatch() {
        let packet = [6, 0, 0, 0, 0x00, 0x7F, 0x00];
        let error = decode_response(&packet, PineCommand::Read8.opcode()).expect_err("packet size should be rejected");
        assert!(matches!(error, PineError::InvalidPacket { .. }));
    }

    #[test]
    fn emulator_status_parses_known_values() {
        assert_eq!(
            EmulatorStatus::try_from(0).expect("status is valid"),
            EmulatorStatus::Running
        );
        assert!(EmulatorStatus::try_from(42).is_err());
    }
}
