use std::sync::Arc;

use anyhow::{bail, Context};
use base64ct::{Base64UrlUnpadded, Encoding};
use serde::Serialize;
use web_push_native::{
    jwt_simple::algorithms::ES256KeyPair, p256::PublicKey, Auth, WebPushBuilder,
};

#[derive(Clone)]
pub struct WebPushService {
    http: reqwest::Client,
    vapid_key_pair: Arc<ES256KeyPair>,
    sender_email: String,
}

#[derive(Debug, Clone, Serialize)]
pub struct PushPayload {
    pub title: String,
    pub body: String,
    pub url: Option<String>,
    pub icon: Option<String>,
}

impl WebPushService {
    /// Create a new WebPush service with VAPID keys.
    /// - `_vapid_public_key`: Base64url-encoded P-256 public key (derived from private key)
    /// - `vapid_private_key`: Base64url-encoded P-256 private key
    /// - `sender_email`: Contact email for VAPID (e.g. "mailto:admin@example.com")
    pub fn new(
        _vapid_public_key: String,
        vapid_private_key: String,
        sender_email: String,
    ) -> anyhow::Result<Self> {
        // The VAPID private key bytes are used to create the ES256KeyPair.
        // The public key is derived from it, so we don't need to validate the public key separately.
        let key_bytes = Base64UrlUnpadded::decode_vec(&vapid_private_key)
            .context("Failed to decode VAPID private key")?;
        let vapid_key_pair =
            ES256KeyPair::from_bytes(&key_bytes).context("Invalid VAPID private key")?;

        Ok(Self {
            http: reqwest::Client::builder()
                .timeout(std::time::Duration::from_secs(10))
                .build()
                .context("Failed to build HTTP client")?,
            vapid_key_pair: Arc::new(vapid_key_pair),
            sender_email,
        })
    }

    /// Send a push notification to a browser subscription.
    pub async fn send(
        &self,
        endpoint: &str,
        p256dh: &str,
        auth: &str,
        payload: &PushPayload,
    ) -> anyhow::Result<()> {
        let endpoint_uri: http::Uri = endpoint
            .parse()
            .with_context(|| format!("Invalid endpoint URL: {}", endpoint))?;

        let ua_public_bytes = Base64UrlUnpadded::decode_vec(p256dh)
            .with_context(|| format!("Failed to decode p256dh: {}", p256dh))?;
        let ua_public = PublicKey::from_sec1_bytes(&ua_public_bytes)
            .with_context(|| "Invalid p256dh key")?;

        let auth_bytes = Base64UrlUnpadded::decode_vec(auth)
            .with_context(|| format!("Failed to decode auth: {}", auth))?;
        let ua_auth = Auth::clone_from_slice(&auth_bytes);

        let payload_bytes = serde_json::to_vec(payload)?;

        let request = WebPushBuilder::new(endpoint_uri, ua_public, ua_auth)
            .with_vapid(&self.vapid_key_pair, &self.sender_email)
            .build(payload_bytes)?;

        let reqwest_request = reqwest::Request::try_from(request)
            .context("Failed to convert web-push request")?;
        let response = self.http.execute(reqwest_request).await?;

        let status = response.status();
        if !status.is_success() && status.as_u16() != 201 {
            let body = response.text().await.unwrap_or_default();
            bail!("WebPush failed: {} - {}", status, body);
        }

        Ok(())
    }
}
