use quickcheck::{Arbitrary, Gen};

use std::{borrow::Cow, boxed::Box, rc::Rc, string::String, sync::Arc, vec::Vec};

use crate::{Buffer, EmailAddr};

const ATEXT: &[u8] =
  b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-/=?^_`{|}~";

fn arbitrary_atext(g: &mut Gen) -> char {
  ATEXT[usize::arbitrary(g) % ATEXT.len()] as char
}

fn arbitrary_alnum(g: &mut Gen) -> char {
  let idx = usize::arbitrary(g) % 62;
  match idx {
    0..=25 => (b'a' + idx as u8) as char,
    26..=51 => (b'A' + (idx as u8 - 26)) as char,
    _ => (b'0' + (idx as u8 - 52)) as char,
  }
}

fn arbitrary_label_char(g: &mut Gen) -> char {
  if bool::arbitrary(g) {
    arbitrary_alnum(g)
  } else {
    '-'
  }
}

fn arbitrary_local(g: &mut Gen) -> String {
  let segment_count = (usize::arbitrary(g) % 3) + 1;
  let mut local = String::new();

  for segment in 0..segment_count {
    if segment > 0 {
      local.push('.');
    }

    let len = (usize::arbitrary(g) % 12) + 1;
    for _ in 0..len {
      local.push(arbitrary_atext(g));
    }
  }

  local
}

fn arbitrary_domain(g: &mut Gen) -> String {
  let label_count = (usize::arbitrary(g) % 3) + 1;
  let mut domain = String::new();

  for label in 0..label_count {
    if label > 0 {
      domain.push('.');
    }

    let len = (usize::arbitrary(g) % 20) + 1;
    domain.push(arbitrary_alnum(g));

    for _ in 1..len.saturating_sub(1) {
      domain.push(arbitrary_label_char(g));
    }

    if len > 1 {
      domain.push(arbitrary_alnum(g));
    }
  }

  domain
}

fn arbitrary_email(g: &mut Gen) -> EmailAddr<Buffer> {
  let mut email = arbitrary_local(g);
  email.push('@');
  email.push_str(&arbitrary_domain(g));
  EmailAddr::<Buffer>::try_from(email.as_str())
    .unwrap_or_else(|_| EmailAddr::<Buffer>::try_from("user@example.com").unwrap())
}

macro_rules! impl_arbitrary_from_buffer {
  ($($ty:ty),+ $(,)?) => {
    $(
      impl Arbitrary for EmailAddr<$ty> {
        fn arbitrary(g: &mut Gen) -> Self {
          let addr = arbitrary_email(g);
          EmailAddr::from_inner(addr.storage.into())
        }
      }
    )+
  };
}

impl Arbitrary for EmailAddr<Buffer> {
  fn arbitrary(g: &mut Gen) -> Self {
    arbitrary_email(g)
  }
}

impl_arbitrary_from_buffer!(
  String,
  Box<str>,
  Rc<str>,
  Arc<str>,
  Vec<u8>,
  Box<[u8]>,
  Rc<[u8]>,
  Arc<[u8]>,
  Cow<'static, str>,
  Cow<'static, [u8]>,
);

#[cfg(feature = "smol_str_0_3")]
impl_arbitrary_from_buffer!(smol_str_0_3::SmolStr);

#[cfg(feature = "bytes_1")]
impl_arbitrary_from_buffer!(bytes_1::Bytes);

#[cfg(feature = "tinyvec_1")]
impl<const N: usize> Arbitrary for EmailAddr<tinyvec_1::TinyVec<[u8; N]>> {
  fn arbitrary(g: &mut Gen) -> Self {
    let addr = arbitrary_email(g);
    EmailAddr::from_inner(addr.storage.into())
  }
}

#[cfg(feature = "smallvec_1")]
impl<const N: usize> Arbitrary for EmailAddr<smallvec_1::SmallVec<[u8; N]>> {
  fn arbitrary(g: &mut Gen) -> Self {
    let addr = arbitrary_email(g);
    EmailAddr::from_inner(addr.storage.into())
  }
}

#[cfg(feature = "triomphe_0_1")]
const _: () = {
  use triomphe_0_1::Arc;

  impl_arbitrary_from_buffer!(Arc<str>, Arc<[u8]>);
};
