//! HTMX contact-form endpoint.
//!
//! Mounted at `POST /api/htmx/contact`. Accepts the form-encoded contact form
//! (`name`, `email`, `subject`, `message`, honeypot `website`) and returns an
//! HTML result fragment. Owner delivery reuses [`crate::email::EmailService`]
//! and is best-effort — with email disabled the send is a no-op, so the form
//! still confirms to the visitor.
//!
//! This replaces the previously dead `POST /api/contact` target in the contact
//! template (there was no handler at that path; the only contact route,
//! `/api/email/contact`, expects JSON and a wired `ConnectInfo`).

#[cfg(feature = "email")]
mod imp {
    use axum::extract::Form;
    use axum::http::StatusCode;
    use axum::response::{Html, IntoResponse, Response};
    use axum::routing::post;
    use axum::{Extension, Router};
    use serde::Deserialize;

    use crate::app_state::AppState;
    use crate::security::rate_limit::RateLimitResult;

    pub fn htmx_contact_routes() -> Router {
        Router::new().route("/", post(contact))
    }

    #[derive(Debug, Deserialize)]
    struct ContactForm {
        name: String,
        email: String,
        #[serde(default)]
        subject: Option<String>,
        message: String,
        #[serde(default)]
        lang: Option<String>,
        /// Honeypot — must stay empty. A non-empty value marks a bot.
        #[serde(default)]
        website: Option<String>,
    }

    async fn contact(
        Extension(state): Extension<AppState>,
        Form(form): Form<ContactForm>,
    ) -> Response {
        let lang = normalize_lang(form.lang.as_deref());

        // Honeypot: a filled `website` field is a bot. Confirm without sending so
        // the bot gets no signal.
        if !form.website.as_deref().unwrap_or("").trim().is_empty() {
            return (StatusCode::OK, Html(thanks_fragment(&lang))).into_response();
        }

        // Rate limit keyed on the submitter email. Bots that vary the address can
        // still bypass this; the global limiter caps total volume.
        let ident = format!("contact:{}", form.email.trim().to_lowercase());
        if matches!(
            state.rate_limiter.is_allowed(&ident),
            RateLimitResult::RateLimited { .. }
        ) {
            return (
                StatusCode::TOO_MANY_REQUESTS,
                Html(error_fragment(t(
                    &lang,
                    "Too many requests — please try again later.",
                    "Demasiadas peticiones — inténtalo más tarde.",
                ))),
            )
                .into_response();
        }

        if form.name.trim().is_empty()
            || form.email.trim().is_empty()
            || form.message.trim().is_empty()
        {
            return (
                StatusCode::BAD_REQUEST,
                Html(error_fragment(t(
                    &lang,
                    "Name, email and message are required.",
                    "Nombre, email y mensaje son obligatorios.",
                ))),
            )
                .into_response();
        }

        let site = std::env::var("SITE_NAME").unwrap_or_else(|_| "site".to_string());
        let subject = match form
            .subject
            .as_deref()
            .map(str::trim)
            .filter(|s| !s.is_empty())
        {
            Some(s) => format!("[{site}] {s}"),
            None => format!("[{site}] Contact from {}", form.name.trim()),
        };
        let body = format!(
            "Contact form submission.\nFrom: {name} <{email}>\n\n{message}",
            name = form.name.trim(),
            email = form.email.trim(),
            message = form.message.trim(),
        );
        let recipient = std::env::var("CONTACT_RECIPIENT")
            .or_else(|_| std::env::var("POST_MESSAGE_RECIPIENT"))
            .or_else(|_| std::env::var("EMAIL_FROM"))
            .ok()
            .filter(|s| !s.trim().is_empty());
        match recipient {
            Some(to) => {
                if let Err(e) = state
                    .email_service
                    .send_simple_email_with_language(&to, &subject, &body, &lang)
                    .await
                {
                    tracing::warn!("contact email failed: {e}");
                }
            }
            None => {
                tracing::info!("contact form submitted; set CONTACT_RECIPIENT to deliver it");
            }
        }

        (StatusCode::OK, Html(thanks_fragment(&lang))).into_response()
    }

    // ── Fragments ──────────────────────────────────────────────────────────

    fn thanks_fragment(lang: &str) -> String {
        let msg = esc(t(
            lang,
            "Thanks — your message was sent.",
            "Gracias — tu mensaje se ha enviado.",
        ));
        format!(r#"<div class="ds-alert-success mt-2" role="status">{msg}</div>"#)
    }

    fn error_fragment(message: &str) -> String {
        format!(
            r#"<div class="ds-alert-error mt-2" role="alert">{}</div>"#,
            esc(message)
        )
    }

    fn normalize_lang(lang: Option<&str>) -> String {
        match lang {
            Some(l) if l.len() <= 8 && l.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') => {
                l.to_string()
            }
            _ => "en".to_string(),
        }
    }

    fn t<'a>(lang: &str, en: &'a str, es: &'a str) -> &'a str {
        if lang == "es" {
            es
        } else {
            en
        }
    }

    fn esc(input: &str) -> String {
        let mut out = String::with_capacity(input.len());
        for ch in input.chars() {
            match ch {
                '&' => out.push_str("&amp;"),
                '<' => out.push_str("&lt;"),
                '>' => out.push_str("&gt;"),
                '"' => out.push_str("&quot;"),
                '\'' => out.push_str("&#39;"),
                _ => out.push(ch),
            }
        }
        out
    }

    #[cfg(test)]
    mod tests {
        use super::*;

        #[test]
        fn thanks_and_error_escape_and_localize() {
            assert!(thanks_fragment("es").contains("Gracias"));
            assert!(error_fragment("<x>").contains("&lt;x&gt;"));
        }

        #[test]
        fn normalize_lang_rejects_junk() {
            assert_eq!(normalize_lang(Some("es")), "es");
            assert_eq!(normalize_lang(Some("../etc")), "en");
        }
    }
}

#[cfg(feature = "email")]
pub use imp::htmx_contact_routes;
