use std::sync::Arc;

use tokio::sync::Mutex;

use super::commands::{
    empty_request, parse_status, parse_string, parse_u8, parse_u16, parse_u32, parse_u64, read_request, slot_request,
    write_request,
};
use super::error::PineError;
use super::protocol::{EmulatorStatus, MemoryAddress, PineCommand, SaveStateSlot, decode_response};
use super::transport::{PineEndpoint, PineOptions, PineSlot, PineTransport, SharedTransport, validate_endpoint};

/// High-level Pine client for reading and writing emulator memory.
///
/// # Examples
///
/// ```rust
/// use pine_client::{MemoryAddress, PineClient, PineError};
///
/// async fn example() -> Result<(), PineError> {
///     let client = PineClient::new()?;
///     let _ = client.read_u8(MemoryAddress::new(0x0034_7D34)).await?;
///     Ok(())
/// }
/// ```
#[derive(Clone)]
pub struct PineClient {
    transport: Arc<SharedTransport>,
}

impl PineClient {
    /// Creates a Pine client for the default PCSX2 endpoint of the current platform.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::PineClient;
    ///
    /// let client = PineClient::new()?;
    /// let _ = client;
    /// # Ok::<(), pine_client::PineError>(())
    /// ```
    pub fn new() -> Result<Self, PineError> {
        Self::with_slot(PineSlot::default())
    }

    /// Creates a Pine client that targets the default PCSX2 endpoint for a slot.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{PineClient, PineSlot};
    ///
    /// let slot = PineSlot::new(28_012)?;
    /// let client = PineClient::with_slot(slot)?;
    /// let _ = client;
    /// # Ok::<(), pine_client::PineError>(())
    /// ```
    pub fn with_slot(slot: PineSlot) -> Result<Self, PineError> {
        Self::with_endpoint_and_options(PineEndpoint::pcsx2(slot), PineOptions::default())
    }

    /// Creates a Pine client with a custom endpoint.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{PineClient, PineEndpoint};
    ///
    /// let client = PineClient::with_endpoint(PineEndpoint::Tcp {
    ///     host: "127.0.0.1".to_string(),
    ///     port: 28_011,
    /// })?;
    /// let _ = client;
    /// # Ok::<(), pine_client::PineError>(())
    /// ```
    pub fn with_endpoint(endpoint: PineEndpoint) -> Result<Self, PineError> {
        Self::with_endpoint_and_options(endpoint, PineOptions::default())
    }

    /// Creates a Pine client with fully custom endpoint and reconnect settings.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use std::time::Duration;
    ///
    /// use pine_client::{PineClient, PineEndpoint, PineOptions};
    ///
    /// let client = PineClient::with_endpoint_and_options(
    ///     PineEndpoint::Tcp {
    ///         host: "127.0.0.1".to_string(),
    ///         port: 28_011,
    ///     },
    ///     PineOptions {
    ///         connect_timeout: Duration::from_secs(1),
    ///         io_timeout: Duration::from_secs(1),
    ///     },
    /// )?;
    /// let _ = client;
    /// # Ok::<(), pine_client::PineError>(())
    /// ```
    pub fn with_endpoint_and_options(endpoint: PineEndpoint, options: PineOptions) -> Result<Self, PineError> {
        validate_endpoint(&endpoint)?;
        Ok(Self {
            transport: Arc::new(Mutex::new(PineTransport::new(endpoint, options))),
        })
    }

    /// Connects eagerly instead of waiting for the first command.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     client.connect().await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn connect(&self) -> Result<(), PineError> {
        let mut transport = self.transport.lock().await;
        transport.connect().await
    }

    /// Drops the current socket connection.
    ///
    /// The next request will reconnect automatically.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     client.disconnect().await;
    ///     Ok(())
    /// }
    /// ```
    ///
    pub async fn disconnect(&self) {
        let mut transport = self.transport.lock().await;
        transport.disconnect();
    }

    /// Returns the configured endpoint.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     let _endpoint = client.endpoint().await;
    ///     Ok(())
    /// }
    /// ```
    pub async fn endpoint(&self) -> PineEndpoint {
        let transport = self.transport.lock().await;
        transport.endpoint()
    }

    /// Returns whether a socket is currently open.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     let _connected = client.is_connected().await;
    ///     Ok(())
    /// }
    /// ```
    pub async fn is_connected(&self) -> bool {
        let transport = self.transport.lock().await;
        transport.is_connected()
    }

    /// Reads an 8-bit value from emulator memory.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{MemoryAddress, PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     let _value = client.read_u8(MemoryAddress::new(0x0034_7D34)).await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn read_u8(&self, address: MemoryAddress) -> Result<u8, PineError> {
        let payload = self.send(read_request(PineCommand::Read8, address)?).await?;
        parse_u8(&payload)
    }

    /// Reads a 16-bit value from emulator memory.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{MemoryAddress, PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     let _value = client.read_u16(MemoryAddress::new(0x0034_7D34)).await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn read_u16(&self, address: MemoryAddress) -> Result<u16, PineError> {
        let payload = self.send(read_request(PineCommand::Read16, address)?).await?;
        parse_u16(&payload)
    }

    /// Reads a 32-bit value from emulator memory.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{MemoryAddress, PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     let _value = client.read_u32(MemoryAddress::new(0x0034_7D34)).await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn read_u32(&self, address: MemoryAddress) -> Result<u32, PineError> {
        let payload = self.send(read_request(PineCommand::Read32, address)?).await?;
        parse_u32(&payload)
    }

    /// Reads a 64-bit value from emulator memory.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{MemoryAddress, PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     let _value = client.read_u64(MemoryAddress::new(0x0034_7D34)).await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn read_u64(&self, address: MemoryAddress) -> Result<u64, PineError> {
        let payload = self.send(read_request(PineCommand::Read64, address)?).await?;
        parse_u64(&payload)
    }

    /// Reads consecutive bytes from emulator memory into an external buffer.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{MemoryAddress, PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     let mut buffer = [0_u8; 4];
    ///     client
    ///         .read_buffer(MemoryAddress::new(0x0034_7D34), &mut buffer)
    ///         .await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn read_buffer(&self, address: MemoryAddress, buffer: &mut [u8]) -> Result<(), PineError> {
        for (offset, byte) in buffer.iter_mut().enumerate() {
            *byte = self.read_u8(offset_address(address, offset)?).await?;
        }

        Ok(())
    }

    /// Writes an 8-bit value to emulator memory.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{MemoryAddress, PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     client.write_u8(MemoryAddress::new(0x0034_7D34), 0x12).await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn write_u8(&self, address: MemoryAddress, value: u8) -> Result<(), PineError> {
        self.send(write_request(PineCommand::Write8, address, [value])?).await?;
        Ok(())
    }

    /// Writes a 16-bit value to emulator memory.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{MemoryAddress, PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     client
    ///         .write_u16(MemoryAddress::new(0x0034_7D34), 0x1234)
    ///         .await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn write_u16(&self, address: MemoryAddress, value: u16) -> Result<(), PineError> {
        self.send(write_request(PineCommand::Write16, address, value.to_le_bytes())?)
            .await?;
        Ok(())
    }

    /// Writes a 32-bit value to emulator memory.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{MemoryAddress, PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     client
    ///         .write_u32(MemoryAddress::new(0x0034_7D34), 0x1234_5678)
    ///         .await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn write_u32(&self, address: MemoryAddress, value: u32) -> Result<(), PineError> {
        self.send(write_request(PineCommand::Write32, address, value.to_le_bytes())?)
            .await?;
        Ok(())
    }

    /// Writes a 64-bit value to emulator memory.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{MemoryAddress, PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     client
    ///         .write_u64(MemoryAddress::new(0x0034_7D34), 0x1234_5678_9ABC_DEF0)
    ///         .await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn write_u64(&self, address: MemoryAddress, value: u64) -> Result<(), PineError> {
        self.send(write_request(PineCommand::Write64, address, value.to_le_bytes())?)
            .await?;
        Ok(())
    }

    /// Writes consecutive bytes from an external buffer into emulator memory.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{MemoryAddress, PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     client
    ///         .write_buffer(MemoryAddress::new(0x0034_7D34), &[0x11, 0x22, 0x33, 0x44])
    ///         .await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn write_buffer(&self, address: MemoryAddress, buffer: &[u8]) -> Result<(), PineError> {
        for (offset, value) in buffer.iter().copied().enumerate() {
            self.write_u8(offset_address(address, offset)?, value).await?;
        }

        Ok(())
    }

    /// Fetches the emulator version string.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     let _version = client.version().await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn version(&self) -> Result<String, PineError> {
        self.read_string_command(PineCommand::Version).await
    }

    /// Fetches the current game title.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     let _title = client.title().await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn title(&self) -> Result<String, PineError> {
        self.read_string_command(PineCommand::Title).await
    }

    /// Fetches the current game serial.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     let _serial = client.id().await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn id(&self) -> Result<String, PineError> {
        self.read_string_command(PineCommand::Id).await
    }

    /// Fetches the current game UUID/CRC string.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     let _uuid = client.uuid().await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn uuid(&self) -> Result<String, PineError> {
        self.read_string_command(PineCommand::Uuid).await
    }

    /// Fetches the current game version string.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     let _game_version = client.game_version().await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn game_version(&self) -> Result<String, PineError> {
        self.read_string_command(PineCommand::GameVersion).await
    }

    /// Fetches the emulator runtime state.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{PineClient, PineError};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     let _status = client.status().await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn status(&self) -> Result<EmulatorStatus, PineError> {
        let payload = self.send(empty_request(PineCommand::Status)?).await?;
        parse_status(&payload)
    }

    /// Saves a state into the given slot.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{PineClient, PineError, SaveStateSlot};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     client.save_state(SaveStateSlot::new(0)).await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn save_state(&self, slot: SaveStateSlot) -> Result<(), PineError> {
        self.send(slot_request(PineCommand::SaveState, slot)?).await?;
        Ok(())
    }

    /// Loads a state from the given slot.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pine_client::{PineClient, PineError, SaveStateSlot};
    ///
    /// async fn example() -> Result<(), PineError> {
    ///     let client = PineClient::new()?;
    ///     client.load_state(SaveStateSlot::new(0)).await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn load_state(&self, slot: SaveStateSlot) -> Result<(), PineError> {
        self.send(slot_request(PineCommand::LoadState, slot)?).await?;
        Ok(())
    }

    async fn read_string_command(&self, opcode: PineCommand) -> Result<String, PineError> {
        let payload = self.send(empty_request(opcode)?).await?;
        parse_string(&payload)
    }

    async fn send(&self, packet: Vec<u8>) -> Result<Vec<u8>, PineError> {
        let opcode = request_opcode(&packet)?;
        let mut transport = self.transport.lock().await;
        let response = transport.round_trip(&packet).await?;
        decode_response(&response, opcode).map(<[u8]>::to_vec)
    }
}

fn request_opcode(packet: &[u8]) -> Result<u8, PineError> {
    packet.get(4).copied().ok_or_else(|| PineError::InvalidPacket {
        message: "request shorter than Pine header".to_string(),
    })
}

fn offset_address(address: MemoryAddress, offset: usize) -> Result<MemoryAddress, PineError> {
    let offset = u32::try_from(offset).map_err(|_| PineError::InvalidPacket {
        message: "buffer offset does not fit in u32".to_string(),
    })?;

    let value = address
        .get()
        .checked_add(offset)
        .ok_or_else(|| PineError::InvalidPacket {
            message: "memory address overflow".to_string(),
        })?;

    Ok(MemoryAddress::new(value))
}

#[cfg(test)]
mod tests {
    use super::{MemoryAddress, offset_address};

    #[test]
    fn offset_address_adds_offset() {
        let next = offset_address(MemoryAddress::new(0x1000), 4).expect("address is valid");
        assert_eq!(next.get(), 0x1004);
    }

    #[test]
    fn offset_address_rejects_overflow() {
        assert!(offset_address(MemoryAddress::new(u32::MAX), 1).is_err());
    }
}
