use http::header::{ACCEPT, CONTENT_TYPE};
use http::StatusCode;
use url::Url;

use crate::http::CONTENT_TYPE_FORM_URLENCODED;
use crate::standards::websub::{Result, WebSubError};

#[derive(Debug, Clone)]
pub struct PublishResponse {
    pub status: StatusCode,
}

#[tracing::instrument(skip(client))]
pub async fn publish(
    client: &impl crate::http::Client,
    hub: &Url,
    topic: &Url,
) -> Result<PublishResponse> {
    let body = url::form_urlencoded::Serializer::new(String::new())
        .append_pair("hub.mode", "publish")
        .append_pair("hub.url", topic.as_str())
        .finish();

    let request = http::Request::builder()
        .method(http::Method::POST)
        .uri(hub.as_str())
        .header(ACCEPT, "*/*")
        .header(CONTENT_TYPE, CONTENT_TYPE_FORM_URLENCODED)
        .body(crate::http::Body::Bytes(body.into_bytes()))
        .map_err(|err| WebSubError::Http(err.to_string()))?;

    let response = client.send_request(request).await.map_err(|err| WebSubError::Network(Box::new(err)))?;
    let status = response.status();

    if status == http::StatusCode::ACCEPTED || status == http::StatusCode::NO_CONTENT || status == http::StatusCode::OK {
        Ok(PublishResponse { status })
    } else {
        Err(WebSubError::Http(format!("Hub rejected publish request: {}", status)))
    }
}
