// file: kb_lib/src/db/dtos/observed_token.rs

//! Observed token DTO.

/// Application-facing observed token DTO.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ObservedTokenDto {
    /// Optional numeric primary key.
    pub id: std::option::Option<i64>,
    /// Token mint address.
    pub mint: std::string::String,
    /// Optional token symbol.
    pub symbol: std::option::Option<std::string::String>,
    /// Optional token display name.
    pub name: std::option::Option<std::string::String>,
    /// Optional decimals value.
    pub decimals: std::option::Option<u8>,
    /// Token program id.
    pub token_program: std::string::String,
    /// Local status.
    pub status: crate::ObservedTokenStatus,
    /// First seen timestamp.
    pub first_seen_at: chrono::DateTime<chrono::Utc>,
    /// Last seen timestamp.
    pub last_seen_at: chrono::DateTime<chrono::Utc>,
    /// Last update timestamp.
    pub updated_at: chrono::DateTime<chrono::Utc>,
}

impl ObservedTokenDto {
    /// Creates a new observed token DTO with current timestamps.
    pub fn new(
        mint: std::string::String,
        symbol: std::option::Option<std::string::String>,
        name: std::option::Option<std::string::String>,
        decimals: std::option::Option<u8>,
        token_program: std::string::String,
        status: crate::ObservedTokenStatus,
    ) -> Self {
        let now = chrono::Utc::now();
        return Self {
            id: None,
            mint,
            symbol,
            name,
            decimals,
            token_program,
            status,
            first_seen_at: now,
            last_seen_at: now,
            updated_at: now,
        };
    }
}

impl TryFrom<crate::ObservedTokenEntity> for ObservedTokenDto {
    type Error = crate::Error;

    fn try_from(entity: crate::ObservedTokenEntity) -> Result<Self, Self::Error> {
        let status_result = crate::ObservedTokenStatus::from_i16(entity.status);
        let status = match status_result {
            Ok(status) => status,
            Err(error) => return Err(error),
        };
        let first_seen_at_result = chrono::DateTime::parse_from_rfc3339(&entity.first_seen_at);
        let first_seen_at = match first_seen_at_result {
            Ok(first_seen_at) => first_seen_at.with_timezone(&chrono::Utc),
            Err(error) => {
                return Err(crate::Error::Db(format!(
                    "cannot parse observed token first_seen_at '{}': {}",
                    entity.first_seen_at, error
                )));
            },
        };
        let last_seen_at_result = chrono::DateTime::parse_from_rfc3339(&entity.last_seen_at);
        let last_seen_at = match last_seen_at_result {
            Ok(last_seen_at) => last_seen_at.with_timezone(&chrono::Utc),
            Err(error) => {
                return Err(crate::Error::Db(format!(
                    "cannot parse observed token last_seen_at '{}': {}",
                    entity.last_seen_at, error
                )));
            },
        };
        let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
        let updated_at = match updated_at_result {
            Ok(updated_at) => updated_at.with_timezone(&chrono::Utc),
            Err(error) => {
                return Err(crate::Error::Db(format!(
                    "cannot parse observed token updated_at '{}': {}",
                    entity.updated_at, error
                )));
            },
        };
        let decimals = match entity.decimals {
            Some(decimals) => {
                let decimals_u8_result = u8::try_from(decimals);
                match decimals_u8_result {
                    Ok(decimals_u8) => Some(decimals_u8),
                    Err(error) => {
                        return Err(crate::Error::Db(format!(
                            "cannot convert observed token decimals '{}' to u8: {}",
                            decimals, error
                        )));
                    },
                }
            },
            None => None,
        };
        return Ok(Self {
            id: Some(entity.id),
            mint: entity.mint,
            symbol: entity.symbol,
            name: entity.name,
            decimals,
            token_program: entity.token_program,
            status,
            first_seen_at,
            last_seen_at,
            updated_at,
        });
    }
}
