use crate::api::MyceliumClient;
use crate::error::{AppError, AppResult};
use serde::{Deserialize, Serialize};

/// Destination of an outgoing message: either a fully resolved overlay IPv6
/// or the recipient's public key.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum MessageDestination {
    #[serde(rename = "ip")]
    Ip(String),
    #[serde(rename = "pk")]
    PublicKey(String),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PushMessageBody {
    pub dst: MessageDestination,
    /// base64-encoded topic bytes (≤ 340 chars).
    pub topic: String,
    /// base64-encoded payload bytes.
    pub payload: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PushMessageReceipt {
    /// 16-char hex id assigned by the daemon.
    pub id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IncomingMessage {
    pub id: String,
    pub src_ip: String,
    pub src_pk: String,
    pub dst_ip: String,
    pub dst_pk: String,
    pub topic: String,
    pub payload: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageStatus {
    /// Pass-through of the daemon's response. We deliberately keep it as a
    /// JSON Value because the upstream schema isn't pinned in the spec and
    /// fields can be added between releases.
    #[serde(flatten)]
    pub raw: serde_json::Value,
}

impl MyceliumClient {
    pub async fn send_message(&self, body: &PushMessageBody) -> AppResult<PushMessageReceipt> {
        let resp = self
            .http()
            .post(self.url("/messages"))
            .json(body)
            .send()
            .await?;
        Self::parse(resp).await
    }

    /// Long-poll the daemon for an incoming message. `timeout` is seconds and
    /// must be ≥ 0; the daemon returns 204/empty when nothing arrives within
    /// the window. Caller is responsible for swallowing the resulting Err.
    pub async fn pop_message(
        &self,
        peek: bool,
        timeout: u64,
        topic: Option<&str>,
    ) -> AppResult<Option<IncomingMessage>> {
        let mut req = self.http().get(self.url("/messages"));
        // The daemon expects query params; we hand-build to avoid url crate.
        let mut q: Vec<(&str, String)> =
            vec![("peek", peek.to_string()), ("timeout", timeout.to_string())];
        if let Some(t) = topic {
            q.push(("topic", t.to_string()));
        }
        req = req.query(&q);

        // The long-poll can run nearly as long as `timeout`; loosen the
        // client-default request timeout for this single call.
        req = req.timeout(std::time::Duration::from_secs(
            timeout.saturating_add(5).max(15),
        ));
        let resp = req.send().await?;
        let status = resp.status();
        if status == reqwest::StatusCode::NO_CONTENT {
            return Ok(None);
        }
        if !status.is_success() {
            let body = resp.text().await.unwrap_or_default();
            return Err(AppError::DaemonStatus {
                status: status.as_u16(),
                body,
            });
        }
        // Some implementations also return 200 with empty body to signal
        // "nothing to read". Try to parse and tolerate empty.
        let bytes = resp.bytes().await?;
        if bytes.is_empty() {
            return Ok(None);
        }
        let msg: IncomingMessage = serde_json::from_slice(&bytes)
            .map_err(|e| AppError::Other(format!("failed to parse incoming message: {e}")))?;
        Ok(Some(msg))
    }

    pub async fn reply_message(
        &self,
        id: &str,
        body: &PushMessageBody,
    ) -> AppResult<PushMessageReceipt> {
        let resp = self
            .http()
            .post(self.url(&format!("/messages/reply/{id}")))
            .json(body)
            .send()
            .await?;
        Self::parse(resp).await
    }

    pub async fn message_status(&self, id: &str) -> AppResult<MessageStatus> {
        let resp = self
            .http()
            .get(self.url(&format!("/messages/status/{id}")))
            .send()
            .await?;
        Self::parse(resp).await
    }
}
