use crate::errors;

/// A global id that can be used to identify a customer.
///
/// The format will be `<cell_id>_<entity_prefix>_<time_ordered_id>`.
///
/// Example: `0a_cus_uu1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p`
#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, diesel::expression::AsExpression)]
#[diesel(sql_type = diesel::sql_types::Text)]
pub struct GlobalCustomerId(super::GlobalId);

// Database related implementations so that this field can be used directly in the database tables
crate::impl_queryable_id_type!(GlobalCustomerId);

// NOTE: We intentionally do NOT use `impl_to_sql_from_sql_global_id_type!` here because that
// macro generates a strict `FromSql` that validates GlobalId format. During the V1/V2 coexistence
// period, V1-created rows may store a V1-format CustomerId (e.g. `cus_xxx`).
// We use `new_unchecked` to accept any string value.
impl<DB> diesel::serialize::ToSql<diesel::sql_types::Text, DB> for GlobalCustomerId
where
    DB: diesel::backend::Backend,
    super::GlobalId: diesel::serialize::ToSql<diesel::sql_types::Text, DB>,
{
    fn to_sql<'b>(
        &'b self,
        out: &mut diesel::serialize::Output<'b, '_, DB>,
    ) -> diesel::serialize::Result {
        self.0.to_sql(out)
    }
}

impl<DB> diesel::deserialize::FromSql<diesel::sql_types::Text, DB> for GlobalCustomerId
where
    DB: diesel::backend::Backend,
    String: diesel::deserialize::FromSql<diesel::sql_types::Text, DB>,
{
    fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
        let string_val = String::from_sql(value)?;
        Ok(Self::new_unchecked(string_val))
    }
}

impl GlobalCustomerId {
    /// Get string representation of the id
    pub fn get_string_repr(&self) -> &str {
        self.0.get_string_repr()
    }

    /// Generate a new GlobalCustomerId from a cell id
    pub fn generate(cell_id: &crate::id_type::CellId) -> Self {
        let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Customer);
        Self(global_id)
    }

    /// Create a new GlobalCustomerId without validation
    /// WARNING: This should only be used when the input is already trusted/validated
    pub fn new_unchecked(input_string: String) -> Self {
        let alphanumeric_id = crate::id_type::AlphaNumericId::new_unchecked(input_string);
        let length_id = crate::id_type::LengthId::new_unchecked(alphanumeric_id);
        Self(super::GlobalId(length_id))
    }
}

impl<'de> serde::Deserialize<'de> for GlobalCustomerId {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let deserialized_string = String::deserialize(deserializer)?;
        Ok(Self::new_unchecked(deserialized_string))
    }
}

impl TryFrom<GlobalCustomerId> for crate::id_type::CustomerId {
    type Error = error_stack::Report<errors::ValidationError>;

    fn try_from(value: GlobalCustomerId) -> Result<Self, Self::Error> {
        Self::try_from(std::borrow::Cow::from(value.get_string_repr().to_owned()))
    }
}

impl crate::id_type::TargetingKey for GlobalCustomerId {
    fn targeting_key_value(&self) -> &str {
        self.get_string_repr()
    }
}

#[cfg(feature = "v2")]
impl crate::events::ApiEventMetric for GlobalCustomerId {
    fn get_api_event_type(&self) -> Option<crate::events::ApiEventsType> {
        Some(crate::events::ApiEventsType::Customer {
            customer_id: Some(self.clone()),
        })
    }
}
