use std::{
    collections::{BTreeMap, HashMap},
    net::{IpAddr, Ipv6Addr, SocketAddrV6},
    num::{NonZeroU32, NonZeroU64},
};

use arrayvec::{ArrayString, ArrayVec};
#[cfg(feature = "std")]
use bytes::Bytes;
#[cfg(feature = "rust_decimal")]
use rust_decimal::Decimal;
use serde_json::Value;
#[cfg(feature = "smallvec")]
use smallvec::SmallVec;
use uuid::Uuid;

use super::{EnumType, Fields, StructType, Typ};

pub trait GetType {
    const TYPE: Typ;
}

impl GetType for bool {
    const TYPE: Typ = Typ::Bool;
}

impl GetType for u8 {
    const TYPE: Typ = Typ::U8;
}

impl GetType for u16 {
    const TYPE: Typ = Typ::U16;
}

impl GetType for u32 {
    const TYPE: Typ = Typ::U32;
}

impl GetType for u64 {
    const TYPE: Typ = Typ::U64;
}

impl GetType for usize {
    const TYPE: Typ = Typ::U32;
}

impl GetType for i32 {
    const TYPE: Typ = Typ::I32;
}

impl GetType for i64 {
    const TYPE: Typ = Typ::I64;
}

impl GetType for f32 {
    const TYPE: Typ = Typ::F32;
}

impl GetType for f64 {
    const TYPE: Typ = Typ::F64;
}

impl GetType for NonZeroU32 {
    const TYPE: Typ = Typ::U32;
}

impl GetType for NonZeroU64 {
    const TYPE: Typ = Typ::U64;
}

impl GetType for String {
    const TYPE: Typ = Typ::Str;
}

impl GetType for time::Duration {
    const TYPE: Typ = Typ::I64;
}

impl GetType for time::OffsetDateTime {
    const TYPE: Typ = Typ::Datetime;
}

impl GetType for time::PrimitiveDateTime {
    const TYPE: Typ = Typ::Datetime;
}

impl GetType for time::Date {
    const TYPE: Typ = Typ::I32;
}

impl GetType for time::Time {
    const TYPE: Typ = Typ::U32;
}

impl<T: GetType> GetType for Vec<T> {
    const TYPE: Typ = Typ::Vec(&T::TYPE);
}

impl<T: GetType, const CAP: usize> GetType for ArrayVec<T, CAP> {
    const TYPE: Typ = Typ::Vec(&T::TYPE);
}

impl<const CAP: usize> GetType for ArrayString<CAP> {
    const TYPE: Typ = Typ::Str;
}

#[cfg(feature = "smallvec")]
impl<T: GetType, const CAP: usize> GetType for SmallVec<[T; CAP]> {
    const TYPE: Typ = Typ::Vec(&T::TYPE);
}

#[cfg(feature = "rust_decimal")]
impl GetType for Decimal {
    const TYPE: Typ = Typ::Decimal;
}

impl<T: GetType> GetType for Option<T> {
    const TYPE: Typ = Typ::Optional(&T::TYPE);
}

impl<T: GetType> GetType for Box<T> {
    const TYPE: Typ = T::TYPE;
}

impl<T1: GetType, T2: GetType> GetType for (T1, T2) {
    const TYPE: Typ = {
        let fields = &[T1::TYPE, T2::TYPE];

        Typ::Struct(StructType {
            name: "",
            fields: Fields::Unnamed(fields),
        })
    };
}

impl<T1: GetType, T2: GetType, T3: GetType> GetType for (T1, T2, T3) {
    const TYPE: Typ = {
        let fields = &[T1::TYPE, T2::TYPE, T3::TYPE];

        Typ::Struct(StructType {
            name: "",
            fields: Fields::Unnamed(fields),
        })
    };
}

impl<T1: GetType, T2: GetType, T3: GetType, T4: GetType> GetType for (T1, T2, T3, T4) {
    const TYPE: Typ = {
        let fields = &[T1::TYPE, T2::TYPE, T3::TYPE, T4::TYPE];

        Typ::Struct(StructType {
            name: "",
            fields: Fields::Unnamed(fields),
        })
    };
}

impl<K: GetType, V: GetType> GetType for HashMap<K, V> {
    const TYPE: Typ = Typ::Vec(&<(K, V)>::TYPE);
}

impl<K: GetType, V: GetType> GetType for BTreeMap<K, V> {
    const TYPE: Typ = Typ::Vec(&<(K, V)>::TYPE);
}

impl<const CAP: usize> GetType for [u8; CAP] {
    const TYPE: Typ = Typ::ArrayBytes(CAP as u32);
}

impl GetType for Ipv6Addr {
    const TYPE: Typ = Typ::ArrayBytes(16);
}

impl GetType for IpAddr {
    const TYPE: Typ = {
        let variants = &[
            (0, ("Ipv4Addr", Typ::ArrayBytes(4))),
            (1, ("Ipv6Addr", Ipv6Addr::TYPE)),
        ];

        Typ::Enum(EnumType {
            name: "IpAddr",
            variants,
        })
    };
}

impl GetType for SocketAddrV6 {
    const TYPE: Typ = {
        let fields = Fields::Unnamed(&[Ipv6Addr::TYPE, u16::TYPE]);
        let socket_addr_v6 = StructType {
            name: "SocketAddrV6",
            fields,
        };
        Typ::Struct(socket_addr_v6)
    };
}

// impl<const CAP: usize, T: GetType> GetType for [T; CAP] {
//     const TYPE: Typ = Typ::Array(CAP as u32, &T::TYPE);
// }

#[cfg(feature = "std")]
impl GetType for Bytes {
    const TYPE: Typ = Typ::Bytes;
}

impl GetType for Value {
    const TYPE: Typ = Typ::RustJson;
}

impl GetType for Uuid {
    const TYPE: Typ = Typ::ArrayBytes(16);
}

#[cfg(feature = "solana")]
impl GetType for solana_pubkey::Pubkey {
    const TYPE: Typ = Typ::ArrayBytes(32);
}

#[cfg(feature = "solana")]
impl GetType for solana_signature::Signature {
    const TYPE: Typ = Typ::ArrayBytes(64);
}

#[cfg(feature = "compact_str")]
impl GetType for compact_str::CompactString {
    const TYPE: Typ = Typ::Str;
}

#[cfg(feature = "smol_str")]
impl GetType for smol_str::SmolStr {
    const TYPE: Typ = Typ::Str;
}
