use nodecraft::{Node, NodeId, NodeIdRef};

use super::{
  super::{merge, skip},
  Data, DataRef, DecodeError, EncodeError,
};

#[cfg(feature = "hostaddr")]
const _: () = {
  use super::super::WireType;
  use nodecraft::{
    Domain, DomainBuffer, HostAddr, HostAddrBuffer,
    hostaddr::{self, Host},
  };
  use std::net::SocketAddr;

  impl<'a> DataRef<'a, Domain> for DomainBuffer {
    fn decode(buf: &'a [u8]) -> Result<(usize, Self), DecodeError> {
      Self::try_from(buf)
        .map(|domain| (buf.len(), domain))
        .map_err(|e| DecodeError::custom(e.as_str()))
    }
  }

  impl Data for Domain {
    type Ref<'a> = DomainBuffer;

    fn from_ref(val: Self::Ref<'_>) -> Result<Self, DecodeError>
    where
      Self: Sized,
    {
      Ok(val.into())
    }

    fn encoded_len(&self) -> usize {
      let s = self.as_inner();
      if s.ends_with('.') {
        s.len()
      } else {
        s.len() + 1
      }
    }

    fn encode(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
      let s = self.as_inner();
      if s.ends_with('.') {
        let len = s.len();
        if buf.len() < len {
          return Err(EncodeError::insufficient_buffer(len, buf.len()));
        }
        buf[..len].copy_from_slice(s.as_bytes());
        return Ok(len);
      }

      let len = s.len();
      let encoded_len = len + 1;
      if buf.len() < encoded_len {
        return Err(EncodeError::insufficient_buffer(encoded_len, buf.len()));
      }
      buf[..len].copy_from_slice(s.as_bytes());
      buf[len] = b'.';
      Ok(encoded_len)
    }
  }

  const HOST_ADDR_SOCKET_TAG: u8 = 1;
  const HOST_ADDR_SOCKET_BYTE: u8 = merge(WireType::LengthDelimited, HOST_ADDR_SOCKET_TAG);
  const HOST_ADDR_DOMAIN_TAG: u8 = 2;
  const HOST_ADDR_DOMAIN_BYTE: u8 = merge(WireType::LengthDelimited, HOST_ADDR_DOMAIN_TAG);

  impl<'a> DataRef<'a, HostAddr> for HostAddrBuffer {
    fn decode(buf: &'a [u8]) -> Result<(usize, Self), DecodeError> {
      let mut offset = 0;
      let buf_len = buf.len();
      let mut value = None;

      while offset < buf_len {
        match buf[offset] {
          HOST_ADDR_SOCKET_BYTE => {
            if value.is_some() {
              return Err(DecodeError::duplicate_field(
                "HostAddr",
                "addr",
                HOST_ADDR_SOCKET_TAG,
              ));
            }
            offset += 1;
            let (bytes_read, addr) =
              <SocketAddr as DataRef<SocketAddr>>::decode_length_delimited(&buf[offset..])?;
            offset += bytes_read;
            value = Some(Self::from(addr));
          }
          HOST_ADDR_DOMAIN_BYTE => {
            if value.is_some() {
              return Err(DecodeError::duplicate_field(
                "HostAddr",
                "domain",
                HOST_ADDR_DOMAIN_TAG,
              ));
            }

            offset += 1;
            let (bytes_read, domain) =
              <DomainBuffer as DataRef<Domain>>::decode_length_delimited(&buf[offset..])?;
            let required = offset + bytes_read + 2;
            if required > buf_len {
              return Err(DecodeError::buffer_underflow());
            }
            let port = u16::from_be_bytes(buf[offset + bytes_read..required].try_into().unwrap());
            offset += bytes_read + 2;
            value = Some(Self::from_domain(domain).with_port(port));
          }
          _ => offset += skip("HostAddr", &buf[offset..])?,
        }
      }

      let value = value.ok_or_else(|| DecodeError::missing_field("HostAddr", "value"))?;
      Ok((offset, value))
    }
  }

  impl Data for HostAddr {
    type Ref<'a> = HostAddrBuffer;

    fn from_ref(val: Self::Ref<'_>) -> Result<Self, DecodeError>
    where
      Self: Sized,
    {
      let val: hostaddr::HostAddr<hostaddr::Domain<hostaddr::Buffer>> = val.into();
      let (val, port) = val.into_components();

      Ok(match val {
        Host::Domain(domain) => Self::from_domain(domain.into()).maybe_with_port(port),
        Host::Ip(addr) => Self::from_ip_addr(addr).maybe_with_port(port),
      })
    }

    fn encoded_len(&self) -> usize {
      let val: hostaddr::HostAddr<&hostaddr::Domain<smol_str::SmolStr>> = self.into();
      match val.host() {
        Host::Ip(addr) => {
          1 + SocketAddr::new(*addr, val.port().unwrap_or(0)).encoded_len_with_length_delimited()
        }
        Host::Domain(domain) => 1 + 2 + domain.encoded_len_with_length_delimited(),
      }
    }

    fn encode(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
      let src_len = buf.len();
      let val: hostaddr::HostAddr<&hostaddr::Domain<smol_str::SmolStr>> = self.into();
      let Some(port) = val.port() else {
        return Err(EncodeError::custom("missing port number in HostAddr"));
      };
      match val.host() {
        Host::Ip(addr) => {
          if src_len < 1 {
            return Err(EncodeError::insufficient_buffer(1, src_len));
          }
          buf[0] = HOST_ADDR_SOCKET_BYTE;
          let offset = SocketAddr::new(*addr, port).encode_length_delimited(&mut buf[1..])?;
          Ok(1 + offset)
        }
        Host::Domain(domain) => {
          buf[0] = HOST_ADDR_DOMAIN_BYTE;
          let offset = domain.encode_length_delimited(&mut buf[1..])?;
          buf[1 + offset..1 + offset + 2].copy_from_slice(&port.to_be_bytes());
          Ok(1 + offset + 2)
        }
      }
    }
  }
};

impl<'a, const N: usize> DataRef<'a, NodeId<N>> for NodeIdRef<'a, N> {
  fn decode(buf: &'a [u8]) -> Result<(usize, Self), DecodeError> {
    NodeIdRef::try_from(buf)
      .map(|node_id| (buf.len(), node_id))
      .map_err(|e| DecodeError::custom(e.to_string()))
  }
}

impl<const N: usize> Data for NodeId<N> {
  type Ref<'a> = NodeIdRef<'a, N>;

  fn from_ref(val: Self::Ref<'_>) -> Result<Self, DecodeError> {
    Ok(Self::new(val).expect("reference must be a valid node id"))
  }

  fn encoded_len(&self) -> usize {
    self.as_bytes().len()
  }

  fn encode(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
    let val = self.as_bytes();
    let len = val.len();
    if buf.len() < len {
      return Err(EncodeError::insufficient_buffer(len, buf.len()));
    }
    buf[..len].copy_from_slice(val);
    Ok(len)
  }
}

const _: () = {
  const NODE_ID_TAG: u8 = 1;
  const NODE_ADDR_TAG: u8 = 2;

  #[inline]
  const fn node_id_byte<I: Data>() -> u8 {
    merge(I::WIRE_TYPE, NODE_ID_TAG)
  }

  #[inline]
  const fn node_addr_byte<A: Data>() -> u8 {
    merge(A::WIRE_TYPE, NODE_ADDR_TAG)
  }

  impl<'a, I, A> DataRef<'a, Node<I, A>> for Node<I::Ref<'a>, A::Ref<'a>>
  where
    I: Data,
    A: Data,
  {
    fn decode(src: &'a [u8]) -> Result<(usize, Self), DecodeError>
    where
      Self: Sized,
    {
      let mut offset = 0;
      let mut id = None;
      let mut address = None;

      while offset < src.len() {
        match src[offset] {
          b if b == node_id_byte::<I>() => {
            if id.is_some() {
              return Err(DecodeError::duplicate_field("Node", "id", NODE_ID_TAG));
            }

            offset += 1;
            let (bytes_read, value) = I::Ref::decode_length_delimited(&src[offset..])?;
            offset += bytes_read;
            id = Some(value);
          }
          b if b == node_addr_byte::<A>() => {
            if address.is_some() {
              return Err(DecodeError::duplicate_field(
                "Node",
                "address",
                NODE_ADDR_TAG,
              ));
            }

            offset += 1;
            let (bytes_read, value) = A::Ref::decode_length_delimited(&src[offset..])?;
            offset += bytes_read;
            address = Some(value);
          }
          _ => offset += skip("Node", &src[offset..])?,
        }
      }

      let id = id.ok_or_else(|| DecodeError::missing_field("Node", "id"))?;
      let address = address.ok_or_else(|| DecodeError::missing_field("Node", "address"))?;
      Ok((offset, Self::new(id, address)))
    }
  }

  impl<I, A> Data for Node<I, A>
  where
    I: Data,
    A: Data,
  {
    type Ref<'a> = Node<I::Ref<'a>, A::Ref<'a>>;

    fn from_ref(val: Self::Ref<'_>) -> Result<Self, DecodeError> {
      let (id, address) = val.into_components();
      I::from_ref(id).and_then(|id| A::from_ref(address).map(|address| Self::new(id, address)))
    }

    fn encoded_len(&self) -> usize {
      1 + self.id().encoded_len_with_length_delimited()
        + 1
        + self.address().encoded_len_with_length_delimited()
    }

    fn encode(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
      let src_len = buf.len();
      if src_len == 0 {
        return Err(EncodeError::insufficient_buffer(
          self.encoded_len(),
          src_len,
        ));
      }

      let mut offset = 0;
      buf[offset] = node_id_byte::<I>();
      offset += 1;
      offset += self
        .id()
        .encode_length_delimited(&mut buf[offset..])
        .map_err(|e| e.update(self.encoded_len(), src_len))?;

      if offset >= src_len {
        return Err(EncodeError::insufficient_buffer(
          self.encoded_len(),
          src_len,
        ));
      }
      buf[offset] = node_addr_byte::<A>();
      offset += 1;
      offset += self
        .address()
        .encode_length_delimited(&mut buf[offset..])
        .map_err(|e| e.update(self.encoded_len(), src_len))?;

      #[cfg(debug_assertions)]
      super::super::debug_assert_write_eq::<Self>(offset, self.encoded_len());
      Ok(offset)
    }
  }
};
