use loco_rs::prelude::*;
use serde::{Deserialize, Serialize};

use axum::http::header;

use crate::services::{abuse_guard, admin_audit, subscriptions, turnstile};

#[derive(Clone, Debug, Deserialize)]
pub struct PublicSubscriptionPayload {
    pub email: String,
    #[serde(default, alias = "displayName")]
    pub display_name: Option<String>,
    #[serde(default)]
    pub source: Option<String>,
    #[serde(default, alias = "turnstileToken")]
    pub turnstile_token: Option<String>,
    #[serde(default, alias = "captchaToken")]
    pub captcha_token: Option<String>,
    #[serde(default, alias = "captchaAnswer")]
    pub captcha_answer: Option<String>,
}

#[derive(Clone, Debug, Deserialize)]
pub struct PublicBrowserPushSubscriptionPayload {
    pub subscription: serde_json::Value,
    #[serde(default)]
    pub source: Option<String>,
    #[serde(default, alias = "turnstileToken")]
    pub turnstile_token: Option<String>,
    #[serde(default, alias = "captchaToken")]
    pub captcha_token: Option<String>,
    #[serde(default, alias = "captchaAnswer")]
    pub captcha_answer: Option<String>,
}

#[derive(Clone, Debug, Deserialize)]
pub struct PublicCombinedSubscriptionPayload {
    #[serde(default)]
    pub channels: Vec<String>,
    #[serde(default)]
    pub email: Option<String>,
    #[serde(default, alias = "displayName")]
    pub display_name: Option<String>,
    #[serde(default)]
    pub subscription: Option<serde_json::Value>,
    #[serde(default)]
    pub source: Option<String>,
    #[serde(default, alias = "turnstileToken")]
    pub turnstile_token: Option<String>,
    #[serde(default, alias = "captchaToken")]
    pub captcha_token: Option<String>,
    #[serde(default, alias = "captchaAnswer")]
    pub captcha_answer: Option<String>,
}

#[derive(Clone, Debug, Deserialize)]
pub struct SubscriptionTokenPayload {
    pub token: String,
}

#[derive(Clone, Debug, Deserialize)]
pub struct SubscriptionManageQuery {
    pub token: String,
}

#[derive(Clone, Debug, Deserialize)]
pub struct SubscriptionManageUpdatePayload {
    pub token: String,
    #[serde(default, alias = "displayName")]
    pub display_name: Option<String>,
    #[serde(default)]
    pub status: Option<String>,
    #[serde(default)]
    pub filters: Option<serde_json::Value>,
}

#[derive(Clone, Debug, Serialize)]
pub struct PublicSubscriptionResponse {
    pub ok: bool,
    pub subscription_id: i32,
    pub status: String,
    pub requires_confirmation: bool,
    pub message: String,
}

#[derive(Clone, Debug, Serialize)]
pub struct PublicCombinedSubscriptionItemResponse {
    pub channel_type: String,
    pub subscription_id: i32,
    pub status: String,
    pub requires_confirmation: bool,
}

#[derive(Clone, Debug, Serialize)]
pub struct PublicCombinedSubscriptionResponse {
    pub ok: bool,
    pub channels: Vec<PublicCombinedSubscriptionItemResponse>,
    pub message: String,
}

#[derive(Clone, Debug, Serialize)]
pub struct SubscriptionManageResponse {
    pub ok: bool,
    pub subscription: subscriptions::PublicSubscriptionView,
}

fn public_subscription_metadata(source: Option<String>) -> serde_json::Value {
    serde_json::json!({
        "source": source,
        "kind": "public-form",
    })
}

fn public_browser_push_metadata(
    source: Option<String>,
    subscription: serde_json::Value,
    user_agent: Option<String>,
) -> serde_json::Value {
    serde_json::json!({
        "source": source,
        "kind": "browser-push",
        "subscription": subscription,
        "user_agent": user_agent,
    })
}

fn normalize_public_subscription_channels(channels: &[String]) -> Vec<String> {
    let mut normalized = Vec::new();

    for raw in channels {
        let Some(channel) = ({
            match raw.trim().to_ascii_lowercase().as_str() {
                "email" | "mail" => Some("email"),
                "browser" | "browser-push" | "browser_push" | "webpush" | "web-push" => {
                    Some("browser_push")
                }
                _ => None,
            }
        }) else {
            continue;
        };

        if !normalized.iter().any(|value| value == channel) {
            normalized.push(channel.to_string());
        }
    }

    normalized
}

async fn verify_subscription_human_check(
    settings: &crate::models::_entities::site_settings::Model,
    turnstile_token: Option<&str>,
    captcha_token: Option<&str>,
    captcha_answer: Option<&str>,
    client_ip: Option<&str>,
) -> Result<()> {
    match turnstile::effective_mode(settings, turnstile::TurnstileScope::Subscription) {
        turnstile::VerificationMode::Off => Ok(()),
        turnstile::VerificationMode::Captcha => {
            crate::services::comment_guard::verify_captcha_solution(
                captcha_token,
                captcha_answer,
                client_ip,
            )
        }
        turnstile::VerificationMode::Turnstile => {
            turnstile::verify_token(settings, turnstile_token, client_ip).await
        }
    }
}

#[debug_handler]
pub async fn subscribe(
    State(ctx): State<AppContext>,
    headers: axum::http::HeaderMap,
    Json(payload): Json<PublicSubscriptionPayload>,
) -> Result<Response> {
    let email = payload.email.trim().to_ascii_lowercase();
    let client_ip = abuse_guard::detect_client_ip(&headers);
    abuse_guard::enforce_public_scope("subscription", client_ip.as_deref(), Some(&email))?;
    let settings = crate::controllers::site_settings::load_current(&ctx).await?;
    verify_subscription_human_check(
        &settings,
        payload.turnstile_token.as_deref(),
        payload.captcha_token.as_deref(),
        payload.captcha_answer.as_deref(),
        client_ip.as_deref(),
    )
    .await?;

    let result = subscriptions::create_public_email_subscription(
        &ctx,
        &email,
        payload.display_name,
        Some(public_subscription_metadata(payload.source)),
    )
    .await?;

    admin_audit::log_event(
        &ctx,
        None,
        if result.requires_confirmation {
            "subscription.public.pending"
        } else {
            "subscription.public.active"
        },
        "subscription",
        Some(result.subscription.id.to_string()),
        Some(result.subscription.target.clone()),
        Some(serde_json::json!({
            "channel_type": result.subscription.channel_type,
            "status": result.subscription.status,
        })),
    )
    .await?;

    format::json(PublicSubscriptionResponse {
        ok: true,
        subscription_id: result.subscription.id,
        status: result.subscription.status,
        requires_confirmation: result.requires_confirmation,
        message: result.message,
    })
}

#[debug_handler]
pub async fn subscribe_browser_push(
    State(ctx): State<AppContext>,
    headers: axum::http::HeaderMap,
    Json(payload): Json<PublicBrowserPushSubscriptionPayload>,
) -> Result<Response> {
    let settings = crate::controllers::site_settings::load_current(&ctx).await?;
    if !crate::services::web_push::is_enabled(&settings) {
        return Err(Error::BadRequest("浏览器推送未启用".to_string()));
    }

    let endpoint = payload
        .subscription
        .get("endpoint")
        .and_then(serde_json::Value::as_str)
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .ok_or_else(|| {
            Error::BadRequest("browser push subscription.endpoint 不能为空".to_string())
        })?
        .to_string();
    let client_ip = abuse_guard::detect_client_ip(&headers);
    let user_agent = headers
        .get(header::USER_AGENT)
        .and_then(|value| value.to_str().ok())
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(ToString::to_string);

    abuse_guard::enforce_public_scope(
        "browser-push-subscription",
        client_ip.as_deref(),
        Some(&endpoint),
    )?;

    let result = subscriptions::create_public_web_push_subscription(
        &ctx,
        payload.subscription.clone(),
        Some(public_browser_push_metadata(
            payload.source,
            payload.subscription,
            user_agent,
        )),
    )
    .await?;

    admin_audit::log_event(
        &ctx,
        None,
        "subscription.public.web_push.active",
        "subscription",
        Some(result.subscription.id.to_string()),
        Some(result.subscription.target.clone()),
        Some(serde_json::json!({
            "channel_type": result.subscription.channel_type,
            "status": result.subscription.status,
        })),
    )
    .await?;

    format::json(PublicSubscriptionResponse {
        ok: true,
        subscription_id: result.subscription.id,
        status: result.subscription.status,
        requires_confirmation: false,
        message: result.message,
    })
}

#[debug_handler]
pub async fn subscribe_combined(
    State(ctx): State<AppContext>,
    headers: axum::http::HeaderMap,
    Json(payload): Json<PublicCombinedSubscriptionPayload>,
) -> Result<Response> {
    let selected_channels = normalize_public_subscription_channels(&payload.channels);
    if selected_channels.is_empty() {
        return Err(Error::BadRequest("请至少选择一种订阅方式".to_string()));
    }

    let wants_email = selected_channels.iter().any(|value| value == "email");
    let wants_browser_push = selected_channels
        .iter()
        .any(|value| value == "browser_push");

    let settings = crate::controllers::site_settings::load_current(&ctx).await?;
    let client_ip = abuse_guard::detect_client_ip(&headers);

    let normalized_email = payload
        .email
        .as_deref()
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(|value| value.to_ascii_lowercase());

    if wants_email {
        let email = normalized_email
            .as_deref()
            .ok_or_else(|| Error::BadRequest("请选择邮箱订阅后填写邮箱地址".to_string()))?;
        abuse_guard::enforce_public_scope("subscription", client_ip.as_deref(), Some(email))?;
    }

    let normalized_browser_subscription = if wants_browser_push {
        if !crate::services::web_push::is_enabled(&settings) {
            return Err(Error::BadRequest("浏览器推送未启用".to_string()));
        }

        let subscription = payload
            .subscription
            .clone()
            .ok_or_else(|| Error::BadRequest("缺少浏览器推送订阅信息".to_string()))?;
        let endpoint = subscription
            .get("endpoint")
            .and_then(serde_json::Value::as_str)
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .ok_or_else(|| {
                Error::BadRequest("browser push subscription.endpoint 不能为空".to_string())
            })?
            .to_string();

        abuse_guard::enforce_public_scope(
            "browser-push-subscription",
            client_ip.as_deref(),
            Some(&endpoint),
        )?;

        Some(subscription)
    } else {
        None
    };

    if wants_email {
        verify_subscription_human_check(
            &settings,
            payload.turnstile_token.as_deref(),
            payload.captcha_token.as_deref(),
            payload.captcha_answer.as_deref(),
            client_ip.as_deref(),
        )
        .await?;
    }

    let user_agent = headers
        .get(header::USER_AGENT)
        .and_then(|value| value.to_str().ok())
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(ToString::to_string);

    let mut items = Vec::new();
    let mut message_parts = Vec::new();

    if let Some(subscription) = normalized_browser_subscription {
        let browser_result = subscriptions::create_public_web_push_subscription(
            &ctx,
            subscription.clone(),
            Some(public_browser_push_metadata(
                payload.source.clone(),
                subscription,
                user_agent,
            )),
        )
        .await?;

        admin_audit::log_event(
            &ctx,
            None,
            "subscription.public.web_push.active",
            "subscription",
            Some(browser_result.subscription.id.to_string()),
            Some(browser_result.subscription.target.clone()),
            Some(serde_json::json!({
                "channel_type": browser_result.subscription.channel_type,
                "status": browser_result.subscription.status,
            })),
        )
        .await?;

        message_parts.push(browser_result.message.clone());
        items.push(PublicCombinedSubscriptionItemResponse {
            channel_type: browser_result.subscription.channel_type,
            subscription_id: browser_result.subscription.id,
            status: browser_result.subscription.status,
            requires_confirmation: false,
        });
    }

    if wants_email {
        let email_result = subscriptions::create_public_email_subscription(
            &ctx,
            normalized_email.as_deref().unwrap_or_default(),
            payload.display_name,
            Some(public_subscription_metadata(payload.source)),
        )
        .await?;

        admin_audit::log_event(
            &ctx,
            None,
            if email_result.requires_confirmation {
                "subscription.public.pending"
            } else {
                "subscription.public.active"
            },
            "subscription",
            Some(email_result.subscription.id.to_string()),
            Some(email_result.subscription.target.clone()),
            Some(serde_json::json!({
                "channel_type": email_result.subscription.channel_type,
                "status": email_result.subscription.status,
            })),
        )
        .await?;

        message_parts.push(email_result.message.clone());
        items.push(PublicCombinedSubscriptionItemResponse {
            channel_type: email_result.subscription.channel_type,
            subscription_id: email_result.subscription.id,
            status: email_result.subscription.status,
            requires_confirmation: email_result.requires_confirmation,
        });
    }

    let message = if message_parts.is_empty() {
        "订阅请求已处理。".to_string()
    } else {
        message_parts.join(" ")
    };

    format::json(PublicCombinedSubscriptionResponse {
        ok: true,
        channels: items,
        message,
    })
}

#[debug_handler]
pub async fn confirm(
    State(ctx): State<AppContext>,
    Json(payload): Json<SubscriptionTokenPayload>,
) -> Result<Response> {
    let item = subscriptions::confirm_subscription(&ctx, &payload.token).await?;
    admin_audit::log_event(
        &ctx,
        None,
        "subscription.public.confirm",
        "subscription",
        Some(item.id.to_string()),
        Some(item.target.clone()),
        Some(serde_json::json!({ "channel_type": item.channel_type })),
    )
    .await?;

    format::json(SubscriptionManageResponse {
        ok: true,
        subscription: subscriptions::to_public_subscription_view(&item),
    })
}

#[debug_handler]
pub async fn manage(
    State(ctx): State<AppContext>,
    Query(query): Query<SubscriptionManageQuery>,
) -> Result<Response> {
    let item = subscriptions::get_subscription_by_manage_token(&ctx, &query.token).await?;
    format::json(SubscriptionManageResponse {
        ok: true,
        subscription: subscriptions::to_public_subscription_view(&item),
    })
}

#[debug_handler]
pub async fn update_manage(
    State(ctx): State<AppContext>,
    Json(payload): Json<SubscriptionManageUpdatePayload>,
) -> Result<Response> {
    let item = subscriptions::update_subscription_preferences(
        &ctx,
        &payload.token,
        payload.display_name,
        payload.status,
        payload.filters,
    )
    .await?;

    admin_audit::log_event(
        &ctx,
        None,
        "subscription.public.update",
        "subscription",
        Some(item.id.to_string()),
        Some(item.target.clone()),
        None,
    )
    .await?;

    format::json(SubscriptionManageResponse {
        ok: true,
        subscription: subscriptions::to_public_subscription_view(&item),
    })
}

#[debug_handler]
pub async fn unsubscribe(
    State(ctx): State<AppContext>,
    Json(payload): Json<SubscriptionTokenPayload>,
) -> Result<Response> {
    let item = subscriptions::unsubscribe_subscription(&ctx, &payload.token).await?;
    admin_audit::log_event(
        &ctx,
        None,
        "subscription.public.unsubscribe",
        "subscription",
        Some(item.id.to_string()),
        Some(item.target.clone()),
        None,
    )
    .await?;

    format::json(SubscriptionManageResponse {
        ok: true,
        subscription: subscriptions::to_public_subscription_view(&item),
    })
}

pub fn routes() -> Routes {
    Routes::new()
        .prefix("/api/subscriptions")
        .add("/", post(subscribe))
        .add("/combined", post(subscribe_combined))
        .add("/browser-push", post(subscribe_browser_push))
        .add("/confirm", post(confirm))
        .add("/manage", get(manage).patch(update_manage))
        .add("/unsubscribe", post(unsubscribe))
}
