use std::convert::TryInto;

use serde::de::Visitor;
use serde::de::{DeserializeSeed, EnumAccess, MapAccess, SeqAccess, Unexpected, VariantAccess};

#[cfg(feature = "napi6")]
use crate::JsBigint;
use crate::{type_of, NapiValue, Value, ValueType};
use crate::{
  Error, JsBoolean, JsBufferValue, JsNumber, JsObject, JsString, JsUnknown, Result, Status,
};

pub(crate) struct De<'env>(pub(crate) &'env Value);

#[doc(hidden)]
impl<'x, 'de, 'env> serde::de::Deserializer<'x> for &'de mut De<'env> {
  type Error = Error;

  fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
  where
    V: Visitor<'x>,
  {
    let js_value_type = unsafe { type_of!(self.0.env, self.0.value) }?;
    match js_value_type {
      ValueType::Null | ValueType::Undefined => visitor.visit_unit(),
      ValueType::Boolean => {
        let js_boolean = unsafe { JsBoolean::from_raw_unchecked(self.0.env, self.0.value) };
        visitor.visit_bool(js_boolean.get_value()?)
      }
      ValueType::Number => {
        let js_number: f64 =
          unsafe { JsNumber::from_raw_unchecked(self.0.env, self.0.value).try_into()? };
        if js_number.trunc() == js_number {
          visitor.visit_i64(js_number as i64)
        } else {
          visitor.visit_f64(js_number)
        }
      }
      ValueType::String => {
        let js_string = unsafe { JsString::from_raw_unchecked(self.0.env, self.0.value) };
        visitor.visit_str(js_string.into_utf8()?.as_str()?)
      }
      ValueType::Object => {
        let js_object = unsafe { JsObject::from_raw_unchecked(self.0.env, self.0.value) };
        if js_object.is_array()? {
          let mut deserializer =
            JsArrayAccess::new(&js_object, js_object.get_array_length_unchecked()?);
          visitor.visit_seq(&mut deserializer)
        } else if js_object.is_buffer()? {
          visitor.visit_bytes(&JsBufferValue::from_raw(self.0.env, self.0.value)?)
        } else {
          let mut deserializer = JsObjectAccess::new(&js_object)?;
          visitor.visit_map(&mut deserializer)
        }
      }
      #[cfg(feature = "napi6")]
      ValueType::Bigint => {
        let mut js_bigint = unsafe { JsBigint::from_raw(self.0.env, self.0.value)? };
        let (signed, v, _loss) = js_bigint.get_u128()?;
        if signed {
          visitor.visit_i128(-(v as i128))
        } else {
          visitor.visit_u128(v)
        }
      }
      ValueType::External | ValueType::Function | ValueType::Symbol => Err(Error::new(
        Status::InvalidArg,
        format!("typeof {:?} value could not be deserialized", js_value_type),
      )),
      ValueType::Unknown => unreachable!(),
    }
  }

  fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value>
  where
    V: Visitor<'x>,
  {
    visitor.visit_bytes(&JsBufferValue::from_raw(self.0.env, self.0.value)?)
  }

  fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value>
  where
    V: Visitor<'x>,
  {
    visitor.visit_bytes(&JsBufferValue::from_raw(self.0.env, self.0.value)?)
  }

  fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
  where
    V: Visitor<'x>,
  {
    match unsafe { type_of!(self.0.env, self.0.value) }? {
      ValueType::Undefined | ValueType::Null => visitor.visit_none(),
      _ => visitor.visit_some(self),
    }
  }

  fn deserialize_enum<V>(
    self,
    _name: &'static str,
    _variants: &'static [&'static str],
    visitor: V,
  ) -> Result<V::Value>
  where
    V: Visitor<'x>,
  {
    let js_value_type = unsafe { type_of!(self.0.env, self.0.value)? };
    match js_value_type {
      ValueType::String => visitor.visit_enum(JsEnumAccess::new(
        unsafe { JsString::from_raw_unchecked(self.0.env, self.0.value) }
          .into_utf8()?
          .into_owned()?,
        None,
      )),
      ValueType::Object => {
        let js_object = unsafe { JsObject::from_raw_unchecked(self.0.env, self.0.value) };
        let properties = js_object.get_property_names()?;
        let property_len = properties.get_array_length_unchecked()?;
        if property_len != 1 {
          Err(Error::new(
            Status::InvalidArg,
            format!(
              "object key length: {}, can not deserialize to Enum",
              property_len
            ),
          ))
        } else {
          let key = properties.get_element::<JsString>(0)?;
          let value: JsUnknown = js_object.get_property(&key)?;
          visitor.visit_enum(JsEnumAccess::new(
            key.into_utf8()?.into_owned()?,
            Some(&value.0),
          ))
        }
      }
      _ => Err(Error::new(
        Status::InvalidArg,
        format!(
          "{:?} type could not deserialize to Enum type",
          js_value_type
        ),
      )),
    }
  }

  fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
  where
    V: Visitor<'x>,
  {
    visitor.visit_unit()
  }

  forward_to_deserialize_any! {
     <V: Visitor<'x>>
      bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
      unit unit_struct seq tuple tuple_struct map struct identifier
      newtype_struct
  }
}

#[doc(hidden)]
pub(crate) struct JsEnumAccess<'env> {
  variant: String,
  value: Option<&'env Value>,
}

#[doc(hidden)]
impl<'env> JsEnumAccess<'env> {
  fn new(variant: String, value: Option<&'env Value>) -> Self {
    Self { variant, value }
  }
}

#[doc(hidden)]
impl<'de, 'env> EnumAccess<'de> for JsEnumAccess<'env> {
  type Error = Error;
  type Variant = JsVariantAccess<'env>;

  fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)>
  where
    V: DeserializeSeed<'de>,
  {
    use serde::de::IntoDeserializer;
    let variant = self.variant.into_deserializer();
    let variant_access = JsVariantAccess { value: self.value };
    seed.deserialize(variant).map(|v| (v, variant_access))
  }
}

#[doc(hidden)]
pub(crate) struct JsVariantAccess<'env> {
  value: Option<&'env Value>,
}

#[doc(hidden)]
impl<'de, 'env> VariantAccess<'de> for JsVariantAccess<'env> {
  type Error = Error;
  fn unit_variant(self) -> Result<()> {
    match self.value {
      Some(val) => {
        let mut deserializer = De(val);
        serde::de::Deserialize::deserialize(&mut deserializer)
      }
      None => Ok(()),
    }
  }

  fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
  where
    T: DeserializeSeed<'de>,
  {
    match self.value {
      Some(val) => {
        let mut deserializer = De(val);
        seed.deserialize(&mut deserializer)
      }
      None => Err(serde::de::Error::invalid_type(
        Unexpected::UnitVariant,
        &"newtype variant",
      )),
    }
  }

  fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
  where
    V: Visitor<'de>,
  {
    match self.value {
      Some(js_value) => {
        let js_object = unsafe { JsObject::from_raw(js_value.env, js_value.value)? };
        if js_object.is_array()? {
          let mut deserializer =
            JsArrayAccess::new(&js_object, js_object.get_array_length_unchecked()?);
          visitor.visit_seq(&mut deserializer)
        } else {
          Err(serde::de::Error::invalid_type(
            Unexpected::Other("JsValue"),
            &"tuple variant",
          ))
        }
      }
      None => Err(serde::de::Error::invalid_type(
        Unexpected::UnitVariant,
        &"tuple variant",
      )),
    }
  }

  fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
  where
    V: Visitor<'de>,
  {
    match self.value {
      Some(js_value) => {
        if let Ok(val) = unsafe { JsObject::from_raw(js_value.env, js_value.value) } {
          let mut deserializer = JsObjectAccess::new(&val)?;
          visitor.visit_map(&mut deserializer)
        } else {
          Err(serde::de::Error::invalid_type(
            Unexpected::Other("JsValue"),
            &"struct variant",
          ))
        }
      }
      _ => Err(serde::de::Error::invalid_type(
        Unexpected::UnitVariant,
        &"struct variant",
      )),
    }
  }
}

#[doc(hidden)]
struct JsArrayAccess<'env> {
  input: &'env JsObject,
  idx: u32,
  len: u32,
}

#[doc(hidden)]
impl<'env> JsArrayAccess<'env> {
  fn new(input: &'env JsObject, len: u32) -> Self {
    Self { input, idx: 0, len }
  }
}

#[doc(hidden)]
impl<'de, 'env> SeqAccess<'de> for JsArrayAccess<'env> {
  type Error = Error;

  fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
  where
    T: DeserializeSeed<'de>,
  {
    if self.idx >= self.len {
      return Ok(None);
    }
    let v = self.input.get_element::<JsUnknown>(self.idx)?;
    self.idx += 1;

    let mut de = De(&v.0);
    seed.deserialize(&mut de).map(Some)
  }
}

#[doc(hidden)]
pub(crate) struct JsObjectAccess<'env> {
  value: &'env JsObject,
  properties: JsObject,
  idx: u32,
  property_len: u32,
}

#[doc(hidden)]
impl<'env> JsObjectAccess<'env> {
  fn new(value: &'env JsObject) -> Result<Self> {
    let properties = value.get_property_names()?;
    let property_len = properties.get_array_length_unchecked()?;
    Ok(Self {
      value,
      properties,
      idx: 0,
      property_len,
    })
  }
}

#[doc(hidden)]
impl<'de, 'env> MapAccess<'de> for JsObjectAccess<'env> {
  type Error = Error;

  fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
  where
    K: DeserializeSeed<'de>,
  {
    if self.idx >= self.property_len {
      return Ok(None);
    }

    let prop_name = self.properties.get_element::<JsUnknown>(self.idx)?;

    let mut de = De(&prop_name.0);
    seed.deserialize(&mut de).map(Some)
  }

  fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
  where
    V: DeserializeSeed<'de>,
  {
    if self.idx >= self.property_len {
      return Err(Error::new(
        Status::InvalidArg,
        format!("Index:{} out of range: {}", self.property_len, self.idx),
      ));
    }
    let prop_name = self.properties.get_element::<JsString>(self.idx)?;
    let value: JsUnknown = self.value.get_property(&prop_name)?;

    self.idx += 1;
    let mut de = De(&value.0);
    let res = seed.deserialize(&mut de)?;
    Ok(res)
  }
}
