//! Request-response protocol for Synor.

use async_trait::async_trait;
use borsh::{BorshDeserialize, BorshSerialize};
use futures::prelude::*;
use libp2p::request_response::{self, Codec, ProtocolSupport};
use libp2p::StreamProtocol;
use std::io;
use synor_types::{Block, BlockHeader, BlockId, Hash256, Transaction, TransactionId};

/// Protocol name for Synor request-response.
pub const SYNOR_PROTOCOL: &str = "/synor/req/1.0.0";

/// Synor protocol identifier.
#[derive(Clone, Debug)]
pub struct SynorProtocol;

impl AsRef<str> for SynorProtocol {
    fn as_ref(&self) -> &str {
        SYNOR_PROTOCOL
    }
}

/// Request types for the Synor protocol.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub enum SynorRequest {
    /// Request block by hash.
    GetBlock(BlockId),
    /// Request multiple blocks by hash.
    GetBlocks(Vec<BlockId>),
    /// Request block header by hash.
    GetHeader(BlockId),
    /// Request multiple headers.
    GetHeaders {
        /// Starting block hash.
        start: BlockId,
        /// Maximum number of headers.
        max_count: u32,
    },
    /// Request headers by blue score range.
    GetHeadersByBlueScore {
        /// Start blue score.
        start_blue_score: u64,
        /// End blue score (exclusive).
        end_blue_score: u64,
    },
    /// Request transaction by ID.
    GetTransaction(TransactionId),
    /// Request multiple transactions.
    GetTransactions(Vec<TransactionId>),
    /// Request UTXO data for an address.
    GetUtxos(Hash256),
    /// Request peer addresses for discovery.
    GetPeers,
    /// Request chain status.
    GetStatus,
    /// Request pruning point.
    GetPruningPoint,
    /// Request tips.
    GetTips,
    /// Relay addresses to peer.
    RelayAddresses(Vec<String>),
}

impl SynorRequest {
    /// Returns the request type name.
    pub fn request_type(&self) -> &'static str {
        match self {
            SynorRequest::GetBlock(_) => "get_block",
            SynorRequest::GetBlocks(_) => "get_blocks",
            SynorRequest::GetHeader(_) => "get_header",
            SynorRequest::GetHeaders { .. } => "get_headers",
            SynorRequest::GetHeadersByBlueScore { .. } => "get_headers_by_score",
            SynorRequest::GetTransaction(_) => "get_transaction",
            SynorRequest::GetTransactions(_) => "get_transactions",
            SynorRequest::GetUtxos(_) => "get_utxos",
            SynorRequest::GetPeers => "get_peers",
            SynorRequest::GetStatus => "get_status",
            SynorRequest::GetPruningPoint => "get_pruning_point",
            SynorRequest::GetTips => "get_tips",
            SynorRequest::RelayAddresses(_) => "relay_addresses",
        }
    }

    /// Serializes to bytes.
    pub fn to_bytes(&self) -> Vec<u8> {
        borsh::to_vec(self).expect("Serialization should not fail")
    }

    /// Deserializes from bytes.
    pub fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
        borsh::from_slice(bytes).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
    }
}

/// Response types for the Synor protocol.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub enum SynorResponse {
    /// Block response.
    Block(Option<Block>),
    /// Multiple blocks response.
    Blocks(Vec<Block>),
    /// Header response.
    Header(Option<BlockHeader>),
    /// Headers response.
    Headers(Vec<BlockHeader>),
    /// Transaction response.
    Transaction(Option<Transaction>),
    /// Multiple transactions response.
    Transactions(Vec<Transaction>),
    /// UTXOs response.
    Utxos(Vec<UtxoInfo>),
    /// Peer addresses response.
    Peers(Vec<String>),
    /// Status response.
    Status(ChainStatus),
    /// Pruning point response.
    PruningPoint(Option<BlockId>),
    /// Tips response.
    Tips(Vec<BlockId>),
    /// Acknowledgement (for relay messages).
    Ack,
    /// Error response.
    Error(String),
}

impl SynorResponse {
    /// Checks if this is an error response.
    pub fn is_error(&self) -> bool {
        matches!(self, SynorResponse::Error(_))
    }

    /// Serializes to bytes.
    pub fn to_bytes(&self) -> Vec<u8> {
        borsh::to_vec(self).expect("Serialization should not fail")
    }

    /// Deserializes from bytes.
    pub fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
        borsh::from_slice(bytes).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
    }
}

/// UTXO information for responses.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub struct UtxoInfo {
    /// Transaction ID.
    pub txid: TransactionId,
    /// Output index.
    pub index: u32,
    /// Amount in sompi.
    pub amount: u64,
    /// Block DAA score when created.
    pub daa_score: u64,
    /// Whether this is from a coinbase.
    pub is_coinbase: bool,
}

/// Chain status information.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub struct ChainStatus {
    /// Genesis block hash.
    pub genesis_hash: Hash256,
    /// Number of tips.
    pub tip_count: u32,
    /// Highest blue score.
    pub best_blue_score: u64,
    /// Current DAA score.
    pub daa_score: u64,
    /// Pruning point hash.
    pub pruning_point: Option<BlockId>,
    /// Current difficulty bits.
    pub difficulty_bits: u32,
    /// Total blocks.
    pub total_blocks: u64,
    /// Median time past.
    pub median_time: u64,
}

/// Codec for the Synor protocol.
#[derive(Clone, Default)]
pub struct SynorCodec;

#[async_trait]
impl Codec for SynorCodec {
    type Protocol = StreamProtocol;
    type Request = SynorRequest;
    type Response = SynorResponse;

    async fn read_request<T>(&mut self, _: &Self::Protocol, io: &mut T) -> io::Result<Self::Request>
    where
        T: AsyncRead + Unpin + Send,
    {
        let mut len_buf = [0u8; 4];
        io.read_exact(&mut len_buf).await?;
        let len = u32::from_be_bytes(len_buf) as usize;

        if len > crate::MAX_MESSAGE_SIZE {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "Request too large",
            ));
        }

        let mut buf = vec![0u8; len];
        io.read_exact(&mut buf).await?;

        SynorRequest::from_bytes(&buf)
    }

    async fn read_response<T>(
        &mut self,
        _: &Self::Protocol,
        io: &mut T,
    ) -> io::Result<Self::Response>
    where
        T: AsyncRead + Unpin + Send,
    {
        let mut len_buf = [0u8; 4];
        io.read_exact(&mut len_buf).await?;
        let len = u32::from_be_bytes(len_buf) as usize;

        if len > crate::MAX_MESSAGE_SIZE {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "Response too large",
            ));
        }

        let mut buf = vec![0u8; len];
        io.read_exact(&mut buf).await?;

        SynorResponse::from_bytes(&buf)
    }

    async fn write_request<T>(
        &mut self,
        _: &Self::Protocol,
        io: &mut T,
        req: Self::Request,
    ) -> io::Result<()>
    where
        T: AsyncWrite + Unpin + Send,
    {
        let buf = req.to_bytes();
        let len = (buf.len() as u32).to_be_bytes();
        io.write_all(&len).await?;
        io.write_all(&buf).await?;
        io.flush().await?;
        Ok(())
    }

    async fn write_response<T>(
        &mut self,
        _: &Self::Protocol,
        io: &mut T,
        res: Self::Response,
    ) -> io::Result<()>
    where
        T: AsyncWrite + Unpin + Send,
    {
        let buf = res.to_bytes();
        let len = (buf.len() as u32).to_be_bytes();
        io.write_all(&len).await?;
        io.write_all(&buf).await?;
        io.flush().await?;
        Ok(())
    }
}

/// Creates a request-response behaviour for the Synor protocol.
pub fn create_request_response_behaviour() -> request_response::Behaviour<SynorCodec> {
    request_response::Behaviour::new(
        [(StreamProtocol::new(SYNOR_PROTOCOL), ProtocolSupport::Full)],
        request_response::Config::default(),
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_request_serialization() {
        let req = SynorRequest::GetBlock(BlockId::from_bytes([1u8; 32]));
        let bytes = req.to_bytes();
        let decoded = SynorRequest::from_bytes(&bytes).unwrap();

        match decoded {
            SynorRequest::GetBlock(id) => assert_eq!(id.as_bytes(), &[1u8; 32]),
            _ => panic!("Wrong request type"),
        }
    }

    #[test]
    fn test_response_serialization() {
        let resp = SynorResponse::Tips(vec![
            BlockId::from_bytes([1u8; 32]),
            BlockId::from_bytes([2u8; 32]),
        ]);
        let bytes = resp.to_bytes();
        let decoded = SynorResponse::from_bytes(&bytes).unwrap();

        match decoded {
            SynorResponse::Tips(tips) => assert_eq!(tips.len(), 2),
            _ => panic!("Wrong response type"),
        }
    }

    #[test]
    fn test_chain_status() {
        let status = ChainStatus {
            genesis_hash: Hash256::from([0u8; 32]),
            tip_count: 3,
            best_blue_score: 1000,
            daa_score: 1000,
            pruning_point: None,
            difficulty_bits: 0x1d00ffff,
            total_blocks: 5000,
            median_time: 1700000000,
        };

        let bytes = borsh::to_vec(&status).unwrap();
        let decoded: ChainStatus = borsh::from_slice(&bytes).unwrap();

        assert_eq!(decoded.best_blue_score, 1000);
    }
}
