use loco_rs::prelude::*;
use reqwest::Url;
use serde_json::Value;
use web_push::{
    ContentEncoding, HyperWebPushClient, SubscriptionInfo, Urgency, VapidSignatureBuilder,
    WebPushClient, WebPushMessageBuilder,
};

use crate::models::_entities::site_settings;

const ENV_PUBLIC_WEB_PUSH_VAPID_PUBLIC_KEY: &str = "PUBLIC_WEB_PUSH_VAPID_PUBLIC_KEY";
const ENV_LEGACY_WEB_PUSH_VAPID_PUBLIC_KEY: &str = "TERMI_WEB_PUSH_VAPID_PUBLIC_KEY";
const ENV_WEB_PUSH_VAPID_PRIVATE_KEY: &str = "TERMI_WEB_PUSH_VAPID_PRIVATE_KEY";
const ENV_WEB_PUSH_VAPID_SUBJECT: &str = "TERMI_WEB_PUSH_VAPID_SUBJECT";

fn env_value(name: &str) -> Option<String> {
    std::env::var(name)
        .ok()
        .map(|value| value.trim().to_string())
        .filter(|value| !value.is_empty())
}

fn configured_value(value: Option<&String>) -> Option<String> {
    value.and_then(|item| {
        let trimmed = item.trim();
        if trimmed.is_empty() {
            None
        } else {
            Some(trimmed.to_string())
        }
    })
}

pub fn public_key(settings: &site_settings::Model) -> Option<String> {
    configured_value(settings.web_push_vapid_public_key.as_ref())
        .or_else(|| env_value(ENV_PUBLIC_WEB_PUSH_VAPID_PUBLIC_KEY))
        .or_else(|| env_value(ENV_LEGACY_WEB_PUSH_VAPID_PUBLIC_KEY))
}

pub fn private_key(settings: &site_settings::Model) -> Option<String> {
    configured_value(settings.web_push_vapid_private_key.as_ref())
        .or_else(|| env_value(ENV_WEB_PUSH_VAPID_PRIVATE_KEY))
}

pub fn vapid_subject(settings: &site_settings::Model) -> Option<String> {
    configured_value(settings.web_push_vapid_subject.as_ref())
        .or_else(|| env_value(ENV_WEB_PUSH_VAPID_SUBJECT))
}

fn normalize_vapid_subject(value: Option<String>) -> Option<String> {
    value.and_then(|item| {
        let trimmed = item.trim();
        if trimmed.starts_with("mailto:") || trimmed.starts_with("https://") {
            Some(trimmed.to_string())
        } else {
            None
        }
    })
}

fn effective_vapid_subject(settings: &site_settings::Model, site_url: Option<&str>) -> String {
    normalize_vapid_subject(vapid_subject(settings))
        .or_else(|| normalize_vapid_subject(site_url.map(ToString::to_string)))
        .unwrap_or_else(|| "mailto:noreply@example.com".to_string())
}

fn subscription_endpoint_host(subscription_info: &SubscriptionInfo) -> String {
    Url::parse(&subscription_info.endpoint)
        .ok()
        .and_then(|url| url.host_str().map(ToString::to_string))
        .unwrap_or_else(|| "unknown".to_string())
}

pub fn public_key_configured(settings: &site_settings::Model) -> bool {
    public_key(settings).is_some()
}

pub fn private_key_configured(settings: &site_settings::Model) -> bool {
    private_key(settings).is_some()
}

pub fn is_enabled(settings: &site_settings::Model) -> bool {
    public_key_configured(settings) && private_key_configured(settings)
}

pub fn subscription_info_from_metadata(metadata: Option<&Value>) -> Result<SubscriptionInfo> {
    let subscription = metadata
        .and_then(Value::as_object)
        .and_then(|object| object.get("subscription"))
        .cloned()
        .ok_or_else(|| Error::BadRequest("browser push metadata 缺少 subscription".to_string()))?;

    serde_json::from_value::<SubscriptionInfo>(subscription)
        .map_err(|_| Error::BadRequest("browser push metadata 非法".to_string()))
}

pub async fn send_payload(
    settings: &site_settings::Model,
    subscription_info: &SubscriptionInfo,
    payload: &[u8],
    urgency: Option<Urgency>,
    ttl: u32,
    site_url: Option<&str>,
) -> Result<()> {
    let private_key = private_key(settings)
        .ok_or_else(|| Error::BadRequest("web push VAPID private key 未配置".to_string()))?;
    let subject = effective_vapid_subject(settings, site_url);
    let endpoint_host = subscription_endpoint_host(subscription_info);

    let mut signature_builder = VapidSignatureBuilder::from_base64(&private_key, subscription_info)
        .map_err(|error| Error::BadRequest(format!("web push vapid build failed: {error}")))?;
    signature_builder.add_claim("sub", subject.clone());
    let signature = signature_builder
        .build()
        .map_err(|error| Error::BadRequest(format!("web push vapid sign failed: {error}")))?;

    let mut builder = WebPushMessageBuilder::new(subscription_info);
    builder.set_ttl(ttl);
    if let Some(urgency) = urgency {
        builder.set_urgency(urgency);
    }
    builder.set_payload(ContentEncoding::Aes128Gcm, payload);
    builder.set_vapid_signature(signature);

    let client = HyperWebPushClient::new();
    let message = builder
        .build()
        .map_err(|error| Error::BadRequest(format!("web push message build failed: {error}")))?;

    client.send(message).await.map_err(|error| {
        Error::BadRequest(format!(
            "web push send failed: {error}; vapid subject={subject}; endpoint_host={endpoint_host}"
        ))
    })?;

    Ok(())
}
