use std::str::FromStr;

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize, de::DeserializeOwned};

es_entity::entity_id! { InboxEventId }

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InboxIdempotencyKey(String);

impl InboxIdempotencyKey {
    pub fn new(key: impl Into<String>) -> Self {
        Self(key.into())
    }

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

impl<T: std::fmt::Display> From<T> for InboxIdempotencyKey {
    fn from(value: T) -> Self {
        Self(value.to_string())
    }
}

impl AsRef<str> for InboxIdempotencyKey {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

impl From<InboxEventId> for job::JobId {
    fn from(id: InboxEventId) -> Self {
        job::JobId::from(id.0)
    }
}

impl From<job::JobId> for InboxEventId {
    fn from(id: job::JobId) -> Self {
        let uuid: sqlx::types::Uuid = id.into();
        InboxEventId::from(uuid)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)]
#[sqlx(type_name = "InboxEventStatus", rename_all = "snake_case")]
pub enum InboxEventStatus {
    Pending,
    Processing,
    Completed,
    Failed,
}

impl InboxEventStatus {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Pending => "pending",
            Self::Processing => "processing",
            Self::Completed => "completed",
            Self::Failed => "failed",
        }
    }
}

impl FromStr for InboxEventStatus {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "pending" => Ok(Self::Pending),
            "processing" => Ok(Self::Processing),
            "completed" => Ok(Self::Completed),
            "failed" => Ok(Self::Failed),
            _ => Err(format!("Unknown inbox event status: {}", s)),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InboxEvent {
    pub id: InboxEventId,
    pub idempotency_key: Option<String>,
    pub payload: serde_json::Value,
    pub status: InboxEventStatus,
    pub error: Option<String>,
    pub recorded_at: DateTime<Utc>,
    pub processed_at: Option<DateTime<Utc>>,
}

impl InboxEvent {
    pub fn payload<T>(&self) -> Result<T, serde_json::Error>
    where
        T: DeserializeOwned,
    {
        serde_json::from_value(self.payload.clone())
    }
}
