use std::sync::OnceLock;

use loco_rs::prelude::*;
use reqwest::Client;
use serde::Deserialize;

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

const DEFAULT_TURNSTILE_VERIFY_URL: &str =
    "https://challenges.cloudflare.com/turnstile/v0/siteverify";

const ENV_TURNSTILE_SECRET_KEY: &str = "TERMI_TURNSTILE_SECRET_KEY";
const ENV_LEGACY_TURNSTILE_SECRET_KEY: &str = "TERMI_COMMENT_TURNSTILE_SECRET_KEY";
const ENV_TURNSTILE_SITE_KEY: &str = "PUBLIC_COMMENT_TURNSTILE_SITE_KEY";
const ENV_TURNSTILE_VERIFY_URL: &str = "TERMI_TURNSTILE_VERIFY_URL";

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TurnstileScope {
    Comment,
    Subscription,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum VerificationMode {
    Off,
    Captcha,
    Turnstile,
}

pub const VERIFICATION_MODE_OFF: &str = "off";
pub const VERIFICATION_MODE_CAPTCHA: &str = "captcha";
pub const VERIFICATION_MODE_TURNSTILE: &str = "turnstile";

impl VerificationMode {
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Off => VERIFICATION_MODE_OFF,
            Self::Captcha => VERIFICATION_MODE_CAPTCHA,
            Self::Turnstile => VERIFICATION_MODE_TURNSTILE,
        }
    }
}

#[derive(Clone, Debug, Deserialize)]
struct TurnstileVerifyResponse {
    success: bool,
    #[serde(default, rename = "error-codes")]
    error_codes: Vec<String>,
}

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

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 normalize_verification_mode(value: Option<&str>) -> Option<VerificationMode> {
    match value?.trim().to_ascii_lowercase().as_str() {
        VERIFICATION_MODE_OFF => Some(VerificationMode::Off),
        VERIFICATION_MODE_CAPTCHA | "normal" | "simple" => Some(VerificationMode::Captcha),
        VERIFICATION_MODE_TURNSTILE => Some(VerificationMode::Turnstile),
        _ => None,
    }
}

fn normalize_ip(value: Option<&str>) -> Option<String> {
    trim_to_option(value).map(|item| item.chars().take(96).collect::<String>())
}

fn verify_url() -> String {
    env_value(ENV_TURNSTILE_VERIFY_URL).unwrap_or_else(|| DEFAULT_TURNSTILE_VERIFY_URL.to_string())
}

fn client() -> &'static Client {
    static CLIENT: OnceLock<Client> = OnceLock::new();
    CLIENT.get_or_init(Client::new)
}

pub fn secret_key(settings: &site_settings::Model) -> Option<String> {
    configured_value(settings.turnstile_secret_key.as_ref())
        .or_else(|| env_value(ENV_TURNSTILE_SECRET_KEY))
        .or_else(|| env_value(ENV_LEGACY_TURNSTILE_SECRET_KEY))
}

pub fn site_key(settings: &site_settings::Model) -> Option<String> {
    configured_value(settings.turnstile_site_key.as_ref())
        .or_else(|| env_value(ENV_TURNSTILE_SITE_KEY))
}

fn site_key_configured(settings: &site_settings::Model) -> bool {
    site_key(settings).is_some()
}

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

fn legacy_mode(settings: &site_settings::Model, scope: TurnstileScope) -> VerificationMode {
    match scope {
        TurnstileScope::Comment => {
            if settings.comment_turnstile_enabled.unwrap_or(false) {
                VerificationMode::Turnstile
            } else {
                VerificationMode::Captcha
            }
        }
        TurnstileScope::Subscription => {
            if settings.subscription_turnstile_enabled.unwrap_or(false) {
                VerificationMode::Turnstile
            } else {
                VerificationMode::Off
            }
        }
    }
}

pub fn selected_mode(settings: &site_settings::Model, scope: TurnstileScope) -> VerificationMode {
    let configured = match scope {
        TurnstileScope::Comment => settings.comment_verification_mode.as_deref(),
        TurnstileScope::Subscription => settings.subscription_verification_mode.as_deref(),
    };

    normalize_verification_mode(configured).unwrap_or_else(|| legacy_mode(settings, scope))
}

pub fn effective_mode(settings: &site_settings::Model, scope: TurnstileScope) -> VerificationMode {
    match selected_mode(settings, scope) {
        VerificationMode::Turnstile
            if site_key_configured(settings) && secret_key_configured(settings) =>
        {
            VerificationMode::Turnstile
        }
        VerificationMode::Turnstile => VerificationMode::Captcha,
        mode => mode,
    }
}

pub fn is_enabled(settings: &site_settings::Model, scope: TurnstileScope) -> bool {
    effective_mode(settings, scope) == VerificationMode::Turnstile
}

pub async fn is_enabled_for_ctx(ctx: &AppContext, scope: TurnstileScope) -> Result<bool> {
    let settings = crate::controllers::site_settings::load_current(ctx).await?;
    Ok(is_enabled(&settings, scope))
}

pub async fn verify_token(
    settings: &site_settings::Model,
    token: Option<&str>,
    client_ip: Option<&str>,
) -> Result<()> {
    let secret = secret_key(settings)
        .ok_or_else(|| Error::BadRequest("人机验证尚未配置完成，请稍后重试".to_string()))?;
    let response_token =
        trim_to_option(token).ok_or_else(|| Error::BadRequest("请先完成人机验证".to_string()))?;

    let mut form_data = vec![
        ("secret".to_string(), secret),
        ("response".to_string(), response_token),
    ];

    if let Some(remote_ip) = normalize_ip(client_ip) {
        form_data.push(("remoteip".to_string(), remote_ip));
    }

    let response = client()
        .post(verify_url())
        .form(&form_data)
        .send()
        .await
        .map_err(|error| {
            tracing::warn!("turnstile verify request failed: {error}");
            Error::BadRequest("人机验证服务暂时不可用，请稍后重试".to_string())
        })?;

    if !response.status().is_success() {
        tracing::warn!(
            "turnstile verify returned unexpected status: {}",
            response.status()
        );
        return Err(Error::BadRequest(
            "人机验证服务暂时不可用，请稍后重试".to_string(),
        ));
    }

    let payload = response
        .json::<TurnstileVerifyResponse>()
        .await
        .map_err(|error| {
            tracing::warn!("turnstile verify decode failed: {error}");
            Error::BadRequest("人机验证服务暂时不可用，请稍后重试".to_string())
        })?;

    if !payload.success {
        tracing::warn!(
            error_codes = ?payload.error_codes,
            "turnstile verify rejected request"
        );
        return Err(Error::BadRequest("人机验证未通过，请重试".to_string()));
    }

    Ok(())
}

pub async fn verify_if_enabled(
    ctx: &AppContext,
    scope: TurnstileScope,
    token: Option<&str>,
    client_ip: Option<&str>,
) -> Result<bool> {
    let settings = crate::controllers::site_settings::load_current(ctx).await?;
    if effective_mode(&settings, scope) != VerificationMode::Turnstile {
        return Ok(false);
    }

    verify_token(&settings, token, client_ip).await?;
    Ok(true)
}
