use hex_literal::hex;
use rosenpass_util::zerocopy::RefMaker;
use zerocopy::ByteSlice;

use crate::RosenpassError::{self, InvalidApiMessageType};

pub type RawMsgType = u128;

// constants generated by gen-ipc-msg-types:
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Ping Request
pub const PING_REQUEST: RawMsgType =
    RawMsgType::from_le_bytes(hex!("2397 3ecc c441 704d    0b02 ea31 45d3 4999"));
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Ping Response
pub const PING_RESPONSE: RawMsgType =
    RawMsgType::from_le_bytes(hex!("4ec7 f6f0 2bbc ba64    48f1 da14 c7cf 0260"));

// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Supply Keypair Request
const SUPPLY_KEYPAIR_REQUEST: RawMsgType =
    RawMsgType::from_le_bytes(hex!("ac91 a5a6 4f4b 21d0    ac7f 9b55 74f7 3529"));
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Supply Keypair Response
const SUPPLY_KEYPAIR_RESPONSE: RawMsgType =
    RawMsgType::from_le_bytes(hex!("f2dc 49bd e261 5f10    40b7 3c16 ec61 edb9"));

// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Add Listen Socket Request
const ADD_LISTEN_SOCKET_REQUEST: RawMsgType =
    RawMsgType::from_le_bytes(hex!("3f21 434f 87cc a08c    02c4 61e4 0816 c7da"));
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Add Listen Socket Response
const ADD_LISTEN_SOCKET_RESPONSE: RawMsgType =
    RawMsgType::from_le_bytes(hex!("45d5 0f0d 93f0 6105    98f2 9469 5dfd 5f36"));

// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Add Psk Broker Request
const ADD_PSK_BROKER_REQUEST: RawMsgType =
    RawMsgType::from_le_bytes(hex!("d798 b8dc bd61 5cab    8df1 c63d e4eb a2d1"));
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Add Psk Broker Response
const ADD_PSK_BROKER_RESPONSE: RawMsgType =
    RawMsgType::from_le_bytes(hex!("bd25 e418 ffb0 6930    248b 217e 2fae e353"));

/// Message properties global to the message type
pub trait MessageAttributes {
    /// Get the size of the message
    ///
    /// # Exampleds
    fn message_size(&self) -> usize;
}

/// API request message types as an enum
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum RequestMsgType {
    Ping,
    SupplyKeypair,
    AddListenSocket,
    AddPskBroker,
}

/// API response messages types as an enum
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum ResponseMsgType {
    Ping,
    SupplyKeypair,
    AddListenSocket,
    AddPskBroker,
}

impl MessageAttributes for RequestMsgType {
    fn message_size(&self) -> usize {
        match self {
            Self::Ping => std::mem::size_of::<super::PingRequest>(),
            Self::SupplyKeypair => std::mem::size_of::<super::SupplyKeypairRequest>(),
            Self::AddListenSocket => std::mem::size_of::<super::AddListenSocketRequest>(),
            Self::AddPskBroker => std::mem::size_of::<super::AddPskBrokerRequest>(),
        }
    }
}

impl MessageAttributes for ResponseMsgType {
    fn message_size(&self) -> usize {
        match self {
            Self::Ping => std::mem::size_of::<super::PingResponse>(),
            Self::SupplyKeypair => std::mem::size_of::<super::SupplyKeypairResponse>(),
            Self::AddListenSocket => std::mem::size_of::<super::AddListenSocketResponse>(),
            Self::AddPskBroker => std::mem::size_of::<super::AddPskBrokerResponse>(),
        }
    }
}

impl TryFrom<RawMsgType> for RequestMsgType {
    type Error = RosenpassError;

    fn try_from(value: RawMsgType) -> Result<Self, Self::Error> {
        use RequestMsgType as E;
        Ok(match value {
            self::PING_REQUEST => E::Ping,
            self::SUPPLY_KEYPAIR_REQUEST => E::SupplyKeypair,
            self::ADD_LISTEN_SOCKET_REQUEST => E::AddListenSocket,
            self::ADD_PSK_BROKER_REQUEST => E::AddPskBroker,
            _ => return Err(InvalidApiMessageType(value)),
        })
    }
}

impl From<RequestMsgType> for RawMsgType {
    fn from(val: RequestMsgType) -> Self {
        use RequestMsgType as E;
        match val {
            E::Ping => self::PING_REQUEST,
            E::SupplyKeypair => self::SUPPLY_KEYPAIR_REQUEST,
            E::AddListenSocket => self::ADD_LISTEN_SOCKET_REQUEST,
            E::AddPskBroker => self::ADD_PSK_BROKER_REQUEST,
        }
    }
}

impl TryFrom<RawMsgType> for ResponseMsgType {
    type Error = RosenpassError;

    fn try_from(value: RawMsgType) -> Result<Self, Self::Error> {
        use ResponseMsgType as E;
        Ok(match value {
            self::PING_RESPONSE => E::Ping,
            self::SUPPLY_KEYPAIR_RESPONSE => E::SupplyKeypair,
            self::ADD_LISTEN_SOCKET_RESPONSE => E::AddListenSocket,
            self::ADD_PSK_BROKER_RESPONSE => E::AddPskBroker,
            _ => return Err(InvalidApiMessageType(value)),
        })
    }
}

impl From<ResponseMsgType> for RawMsgType {
    fn from(val: ResponseMsgType) -> Self {
        use ResponseMsgType as E;
        match val {
            E::Ping => self::PING_RESPONSE,
            E::SupplyKeypair => self::SUPPLY_KEYPAIR_RESPONSE,
            E::AddListenSocket => self::ADD_LISTEN_SOCKET_RESPONSE,
            E::AddPskBroker => self::ADD_PSK_BROKER_RESPONSE,
        }
    }
}

/// Extension trait for [RawMsgType].
///
/// We are using an extension trait rather than just using methods
/// because [RawMsgType] is a type alias, so we can not define methods
/// on it.
pub trait RawMsgTypeExt {
    /// Try to convert this to a [RequestMsgType]; alias for the appropriate [TryFrom]
    /// implementation
    fn into_request_msg_type(self) -> Result<RequestMsgType, RosenpassError>;
    /// Try to convert this to a [ResponseMsgType]; alias for the appropriate [TryFrom]
    /// implementation
    fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError>;
}

impl RawMsgTypeExt for RawMsgType {
    fn into_request_msg_type(self) -> Result<RequestMsgType, RosenpassError> {
        self.try_into()
    }

    fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError> {
        self.try_into()
    }
}

/// Extension trait for [rosenpass_util::zerocopy::RefMaker].
pub trait RefMakerRawMsgTypeExt {
    /// Parse a request message type from bytes
    fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType>;
    /// Parse a response message type from bytes
    fn parse_response_msg_type(self) -> anyhow::Result<ResponseMsgType>;
}

impl<B: ByteSlice> RefMakerRawMsgTypeExt for RefMaker<B, RawMsgType> {
    fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType> {
        Ok(self.parse()?.read().try_into()?)
    }

    fn parse_response_msg_type(self) -> anyhow::Result<ResponseMsgType> {
        Ok(self.parse()?.read().try_into()?)
    }
}
