use std::mem;

use mm1_address::address::Address;
use mm1_address::subnet::NetAddress;
use mm1_core::tracing::TraceId;
pub use mm1_proto_network_management::protocols::{ForeignTypeKey, LocalTypeKey};
use serde::{Deserialize, Serialize};

use crate::common::RouteMetric;

static_assertions::assert_eq_size!(Header<LocalTypeKey>, [u8; 40]);
static_assertions::assert_eq_align!(Header<LocalTypeKey>, u64);
static_assertions::assert_eq_size!(Header<ForeignTypeKey>, [u8; 40]);
static_assertions::assert_eq_align!(Header<ForeignTypeKey>, u64);
static_assertions::assert_eq_size!(LocalTypeKey, u64);
static_assertions::assert_eq_size!(ForeignTypeKey, u64);

pub(crate) const HEADER_FRAME_SIZE: usize = mem::size_of::<Header<u64>>();

#[derive(Debug, Clone, Copy, Serialize, Deserialize, derive_more::From)]
#[repr(C)]
pub(crate) enum Header<TypeKey> {
    Hello(Hello),
    KeepAlive,
    SubnetDistance(SubnetDistance<TypeKey>),
    DeclareType(DeclareType<TypeKey>),
    TransmitMessage(TransmitMessage<TypeKey>),
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[repr(C)]
pub(crate) struct Hello(pub(crate) [u8; 30]);

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[repr(C)]
pub(crate) struct SubnetDistance<TypeKey> {
    pub(crate) net_address: NetAddress,
    pub(crate) type_handle: TypeKey,
    pub(crate) metric:      Option<RouteMetric>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[repr(C)]
pub(crate) struct DeclareType<TypeKey> {
    pub(crate) message_type:  TypeKey,
    pub(crate) type_name_len: u16,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[repr(C)]
pub(crate) struct TransmitMessage<TypeKey> {
    pub(crate) dst_address:  Address,
    pub(crate) message_type: TypeKey,
    pub(crate) trace_id:     TraceId,
    pub(crate) payload_size: u16,
}

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

    use super::*;

    #[test_case(Hello(Default::default()).into())]
    #[test_case(SubnetDistance { net_address: "<a:1>/64".parse().unwrap(), type_handle: 222, metric: Some(1) }.into())]
    #[test_case(SubnetDistance { net_address: "<a:1>/64".parse().unwrap(), type_handle: 222, metric: None }.into())]
    #[test_case(DeclareType { message_type: u64::MAX, type_name_len: 222, }.into())]
    #[test_case(TransmitMessage { dst_address: "<a:1>".parse().unwrap(), trace_id: Default::default(), message_type: 222, payload_size: 55555, }.into())]
    fn encoded_header_fits_into_header_frame(input: Header<u64>) {
        let mut buf = [128u8; HEADER_FRAME_SIZE];
        bincode::serde::encode_into_slice(input, buf.as_mut(), bincode::config::standard())
            .expect("encode");
        let (..): (Header<u64>, _) =
            bincode::serde::decode_from_slice(buf.as_ref(), bincode::config::standard())
                .expect("decode");
    }
}
