use std::fmt;
use std::str::FromStr;

use serde::de;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::TypeError;
use crate::checked::{CheckedString, CheckedStringRule};

macro_rules! impl_jiff_text_type {
    ($type:ty, $inner:ty, $name:literal, $example:literal) => {
        impl $type {
            pub fn new(value: impl AsRef<str>) -> Result<Self, TypeError> {
                CheckedString::new(value.as_ref().to_owned()).map(Self)
            }

            pub fn from_jiff(value: $inner) -> Self {
                Self(CheckedString::from_valid(value.to_string()))
            }

            pub fn to_jiff(&self) -> Result<$inner, TypeError> {
                parse_jiff::<$inner>(self.as_str(), $name, $example)
            }

            pub fn as_str(&self) -> &str {
                self.0.as_str()
            }

            pub fn into_string(self) -> String {
                self.0.into_string()
            }
        }

        impl From<$inner> for $type {
            fn from(value: $inner) -> Self {
                Self::from_jiff(value)
            }
        }

        impl FromStr for $type {
            type Err = TypeError;

            fn from_str(value: &str) -> Result<Self, Self::Err> {
                Self::new(value)
            }
        }

        impl fmt::Display for $type {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                f.write_str(self.as_str())
            }
        }

        impl Serialize for $type {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: Serializer,
            {
                serializer.serialize_str(self.as_str())
            }
        }

        impl<'de> Deserialize<'de> for $type {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: Deserializer<'de>,
            {
                let value = String::deserialize(deserializer)?;
                Self::new(value).map_err(de::Error::custom)
            }
        }
    };
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
    feature = "wire",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent, no_pg_array))]
pub struct Timestamp(CheckedString<TimestampRule>);

impl Timestamp {
    pub fn now() -> Self {
        Self::from_jiff(jiff::Timestamp::now())
    }
}

impl_jiff_text_type!(
    Timestamp,
    jiff::Timestamp,
    "Timestamp",
    "2026-06-07T12:00:00Z"
);

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
    feature = "wire",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent, no_pg_array))]
pub struct CivilDate(CheckedString<CivilDateRule>);

impl_jiff_text_type!(CivilDate, jiff::civil::Date, "CivilDate", "2026-06-07");

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
    feature = "wire",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent, no_pg_array))]
pub struct CivilTime(CheckedString<CivilTimeRule>);

impl_jiff_text_type!(CivilTime, jiff::civil::Time, "CivilTime", "12:00:00");

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
    feature = "wire",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent, no_pg_array))]
pub struct CivilDateTime(CheckedString<CivilDateTimeRule>);

impl_jiff_text_type!(
    CivilDateTime,
    jiff::civil::DateTime,
    "CivilDateTime",
    "2026-06-07T12:00:00"
);

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
    feature = "wire",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent, no_pg_array))]
pub struct ZonedDateTime(CheckedString<ZonedDateTimeRule>);

impl ZonedDateTime {
    pub fn now() -> Self {
        Self::from_jiff(jiff::Zoned::now())
    }
}

impl_jiff_text_type!(
    ZonedDateTime,
    jiff::Zoned,
    "ZonedDateTime",
    "2026-06-07T12:00:00-04:00[America/New_York]"
);

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
    feature = "wire",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent, no_pg_array))]
pub struct Span(CheckedString<SpanRule>);

impl_jiff_text_type!(Span, jiff::Span, "Span", "2h 30m");

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
    feature = "wire",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent, no_pg_array))]
pub struct SignedDuration(CheckedString<SignedDurationRule>);

impl_jiff_text_type!(
    SignedDuration,
    jiff::SignedDuration,
    "SignedDuration",
    "PT2H30M"
);

macro_rules! impl_jiff_rule {
    ($rule:ident, $inner:ty, $name:literal, $example:literal) => {
        #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
        struct $rule;

        impl CheckedStringRule for $rule {
            fn check(value: String) -> Result<String, TypeError> {
                parse_jiff::<$inner>(&value, $name, $example)?;
                Ok(value)
            }
        }
    };
}

impl_jiff_rule!(
    TimestampRule,
    jiff::Timestamp,
    "Timestamp",
    "2026-06-07T12:00:00Z"
);
impl_jiff_rule!(CivilDateRule, jiff::civil::Date, "CivilDate", "2026-06-07");
impl_jiff_rule!(CivilTimeRule, jiff::civil::Time, "CivilTime", "12:00:00");
impl_jiff_rule!(
    CivilDateTimeRule,
    jiff::civil::DateTime,
    "CivilDateTime",
    "2026-06-07T12:00:00"
);
impl_jiff_rule!(
    ZonedDateTimeRule,
    jiff::Zoned,
    "ZonedDateTime",
    "2026-06-07T12:00:00-04:00[America/New_York]"
);
impl_jiff_rule!(SpanRule, jiff::Span, "Span", "2h 30m");
impl_jiff_rule!(
    SignedDurationRule,
    jiff::SignedDuration,
    "SignedDuration",
    "PT2H30M"
);

fn parse_jiff<T>(
    value: &str,
    type_name: &'static str,
    example: &'static str,
) -> Result<T, TypeError>
where
    T: FromStr,
    T::Err: fmt::Display,
{
    value.parse::<T>().map_err(|error| {
        TypeError::new(
            type_name,
            format!("invalid Jiff time value: {error}"),
            format!("provide a value like {example}"),
        )
    })
}
