use tokio_postgres::types::{accepts, to_sql_checked, FromSql, ToSql};

use crate::utils::bytes::NinBytes;
use crate::utils::fixed::Fixed128;

use super::amount::Amount;
use super::block::BlockNumber;
use super::inscriptions::{Genesis, InscriptionNumber, Outpoint};

macro_rules! impl_sql_conv {
    ($T:ty as ($($ACCEPTS:ident)|*), from: $FROM:expr, into: $INTO:expr $(,)?) => {
        impl ToSql for $T {
            fn to_sql(&self, ty: &tokio_postgres::types::Type, out: &mut bytes::BytesMut) -> Result<tokio_postgres::types::IsNull, Box<dyn std::error::Error + Sync + Send>>
            where
                Self: Sized,
            {
                ($INTO)(self, ty, out)
            }

            accepts!($($ACCEPTS),*);
            to_sql_checked!();
        }
        impl<'a> FromSql<'a> for $T {
            fn from_sql(ty: &tokio_postgres::types::Type, raw: &'a [u8]) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
                ($FROM)(ty, raw)
            }

            accepts!($($ACCEPTS),*);
        }
    };
}

// Outpoints are represented by 36-size BYTEA in postgres
impl_sql_conv! {
    Outpoint as (BYTEA),
    from: |ty, raw|{
        let bytes = <&[u8]>::from_sql(ty, raw)?;
        let bytes = <[u8; 36]>::try_from(bytes)?;
        Ok(Outpoint::from_bytes(bytes))
    },
    into: |this: &Self, ty, out|{
        this.as_bytes().to_sql(ty, out)
    }
}

// Genesis is a wrapper around Outpoint
impl_sql_conv! {
    Genesis as (BYTEA),
    from: |ty, raw|{
        Ok(Genesis(Outpoint::from_sql(ty, raw)?))
    },
    into: |this: &Self, ty, out|{
        this.0.to_sql(ty, out)
    }
}

// InscriptionNumber is represented by i64 in pg
impl_sql_conv! {
    InscriptionNumber as (INT8),
    from: |ty, raw|{
        Ok(InscriptionNumber(i64::from_sql(ty, raw)? as _))
    },
    into: |this: &Self, ty, out|{
        (this.0 as i64).to_sql(ty, out)
    }
}

// BlockNumber is represented by i32 in pg
impl_sql_conv! {
    BlockNumber as (INT4),
    from: |ty, raw|{
        Ok(BlockNumber(i32::from_sql(ty, raw)? as _))
    },
    into: |this: &Self, ty, out|{
        (this.0 as i32).to_sql(ty, out)
    }
}

// Amount is represented by i64 in pg
impl_sql_conv! {
    Amount as (INT8),
    from: |ty, raw|{
        Ok(Amount(i64::from_sql(ty, raw)? as _))
    },
    into: |this: &Self, ty, out|{
        (this.0 as i64).to_sql(ty, out)
    }
}

// Fixed128 is represented by TEXT in pg
impl<const PRECISION: u8> ToSql for Fixed128<PRECISION> {
    fn to_sql(&self, ty: &tokio_postgres::types::Type, out: &mut bytes::BytesMut) -> Result<tokio_postgres::types::IsNull, Box<dyn std::error::Error + Sync + Send>>
    where
        Self: Sized,
    {
        self.to_string().to_sql(ty, out)
    }
    accepts!(TEXT);
    to_sql_checked!();
}
// Fixed128 is represented by TEXT in pg
impl<'a, const PRECISION: u8> FromSql<'a> for Fixed128<PRECISION> {
    fn from_sql(ty: &tokio_postgres::types::Type, raw: &'a [u8]) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
        Ok(String::from_sql(ty, raw)?.parse::<Self>()?)
    }

    accepts!(TEXT);
}
