//! [IpAddr], [Ipv4Addr], and [Ipv6Addr] utilities

use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};

use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr};
use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};

type SeaOrmRepr = String;

/// Wrapper around [IpAddr]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
pub struct IpAddress(pub IpAddr);

/// Wrapper around [IpAddr]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
pub struct Ipv4Address(pub Ipv4Addr);

/// Wrapper around [IpAddr]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
pub struct Ipv6Address(pub Ipv6Addr);

macro_rules! impl_addr {
	($t:ty, $inner:ty, $repr:ty) => {
		impl From<$inner> for $t {
			fn from(value: $inner) -> Self {
				Self(value)
			}
		}

		impl From<$t> for $inner {
			fn from(value: $t) -> Self {
				value.0
			}
		}

		impl ValueType for $t {
			fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
				<SeaOrmRepr as ValueType>::try_from(v)
					.and_then(|s| s.parse().map_err(|_| ValueTypeErr))
					.map(Self)
			}

			fn type_name() -> String {
				core::any::type_name::<Self>().to_string()
			}

			fn array_type() -> ArrayType {
				SeaOrmRepr::array_type()
			}

			fn column_type() -> ColumnType {
				SeaOrmRepr::column_type()
			}
		}

		impl From<$t> for Value {
			fn from(value: $t) -> Self {
				value.0.to_string().into()
			}
		}

		impl TryGetable for $t {
			fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
				SeaOrmRepr::try_get_by(res, index)
					.and_then(|s| s.parse().map_err(|_| TryGetError::Null(Self::type_name())))
					.map(Self)
			}
		}

		impl TryFromU64 for $t {
			fn try_from_u64(n: u64) -> Result<Self, DbErr> {
				SeaOrmRepr::try_from_u64(n)
					.and_then(|s| {
						s.parse()
							.map_err(|_| DbErr::ConvertFromU64(core::any::type_name::<Self>()))
					})
					.map(Self)
			}
		}

		impl Nullable for $t {
			fn null() -> Value {
				SeaOrmRepr::null()
			}
		}
	};
}

impl_addr!(IpAddress, IpAddr, SeaOrmRepr);
impl_addr!(Ipv4Address, Ipv4Addr, SeaOrmRepr);
impl_addr!(Ipv6Address, Ipv6Addr, SeaOrmRepr);

#[cfg(test)]
mod tests {
	mod ipaddress {
		use sea_orm::{
			ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
			EnumIter, PrimaryKeyTrait,
		};

		use super::super::IpAddress;

		#[allow(dead_code)]
		#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
		#[sea_orm(table_name = "ipaddress")]
		pub struct Model {
			#[sea_orm(primary_key, auto_increment = false)]
			id: u8,
			#[sea_orm(primary_key, auto_increment = false)]
			addr: IpAddress,
			nullable: Option<IpAddress>,
		}

		impl ActiveModelBehavior for ActiveModel {}

		#[allow(dead_code)]
		#[derive(Debug, EnumIter, DeriveRelation)]
		pub enum Relation {}
	}
	mod ipv4address {
		use sea_orm::{
			ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
			EnumIter, PrimaryKeyTrait,
		};

		use super::super::Ipv4Address;

		#[allow(dead_code)]
		#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
		#[sea_orm(table_name = "ipv4address")]
		pub struct Model {
			#[sea_orm(primary_key, auto_increment = false)]
			id: u8,
			#[sea_orm(primary_key, auto_increment = false)]
			addr: Ipv4Address,
			nullable: Option<Ipv4Address>,
		}

		impl ActiveModelBehavior for ActiveModel {}

		#[allow(dead_code)]
		#[derive(Debug, EnumIter, DeriveRelation)]
		pub enum Relation {}
	}
	mod ipv6address {
		use sea_orm::{
			ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
			EnumIter, PrimaryKeyTrait,
		};

		use super::super::Ipv6Address;

		#[allow(dead_code)]
		#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
		#[sea_orm(table_name = "ipv6address")]
		pub struct Model {
			#[sea_orm(primary_key, auto_increment = false)]
			id: u8,
			#[sea_orm(primary_key, auto_increment = false)]
			addr: Ipv6Address,
			nullable: Option<Ipv6Address>,
		}

		impl ActiveModelBehavior for ActiveModel {}

		#[allow(dead_code)]
		#[derive(Debug, EnumIter, DeriveRelation)]
		pub enum Relation {}
	}
}
