Skip to content
use actix_web::{HttpRequest, HttpResponse, Responder, get, web};

use crate::session::SessionStore;
use crate::view::guard::guard_view;
use crate::view::icons;
use crate::view::{is_htmx, main_with_content};

pub fn notify_content() -> maud::Markup {
    maud::html! {
        div class="flex min-h-screen w-full flex-col" {
            nav class="navbar bg-base-100 shadow-sm border-b border-base-200 px-4" {
                div class="navbar-start" {
                    a
                        href="/"
                        class="btn btn-ghost text-xl"
                        hx-get="/notify"
                        hx-target="#main"
                        hx-swap="innerHTML"
                        hx-push-url="/" {
                        "Notify"
                    }
                }
                div class="navbar-end gap-2" {
                    a
                        href="/install"
                        class="btn btn-ghost btn-sm"
                        hx-get="/install"
                        hx-target="#main"
                        hx-swap="innerHTML"
                        hx-push-url="true" {
                        "Install"
                    }
                    a
                        href="/debug"
                        class="btn btn-ghost btn-sm"
                        hx-get="/debug"
                        hx-target="#main"
                        hx-swap="innerHTML"
                        hx-push-url="true" {
                        "Debug"
                    }
                }
            }

            div class="hero flex-1" {
                div class="hero-content w-full justify-center p-4" {
                    div class="card card-border w-full max-w-sm bg-base-200" {
                        div class="card-body p-6" {
                            h1 class="card-title text-2xl font-bold justify-center text-center mb-2" {
                                "Notify"
                            }

                            div
                                class="card-actions flex flex-col gap-3 w-full"
                                hx-on="htmx:after:request -> { const ctx = event.detail.ctx; if (ctx.response.status < 400) { const feedback = document.getElementById('notify-feedback'); feedback.textContent = 'Sent ✓'; feedback.classList.remove('send-feedback-visible'); void feedback.offsetWidth; feedback.classList.add('send-feedback-visible'); setTimeout(() => { feedback.textContent = ''; feedback.classList.remove('send-feedback-visible'); }, 1800); } }" {
                                // to tell me I should look on my phone
                                button
                                    type="button"
                                    class="btn btn-secondary btn-lg btn-block"
                                    hx-post="/notify/heart"
                                    hx-swap="none"
                                    hx-disabled-elt="this" {
                                    span class="loading loading-spinner loading-md htmx-indicator" {}
                                    span class="htmx-indicator font-semibold" { "Pending..." }
                                    span class="htmx-hide-on-request flex items-center justify-center gap-2" {
                                        (icons::heart())
                                        span { "Look at your phone" }
                                    }
                                }

                                //if my wife misses me
                                button
                                    type="button"
                                    class="btn btn-lg btn-block"
                                    hx-post="/notify/cat"
                                    hx-swap="none"
                                    hx-disabled-elt="this" {
                                    span class="loading loading-spinner loading-md htmx-indicator" {}
                                    span class="htmx-indicator font-semibold" { "Pending..." }
                                    span class="htmx-hide-on-request flex items-center justify-center gap-2" {
                                        (icons::cat())
                                        span { "Missing you" }
                                    }
                                }

                                //when I need to call back now
                                button
                                    type="button"
                                    class="btn btn-error btn-lg btn-block"
                                    hx-post="/notify/emergency"
                                    hx-swap="none"
                                    hx-disabled-elt="this" {
                                    span class="loading loading-spinner loading-md htmx-indicator" {}
                                    span class="htmx-indicator font-semibold" { "Pending..." }
                                    span class="htmx-hide-on-request flex items-center justify-center gap-2" {
                                        (icons::emergency())
                                        span { "Emergency" }
                                    }
                                }
                            }
                            p id="notify-feedback" class="send-feedback" role="status" aria-live="polite" {}
                        }
                    }
                }
            }
        }
    }
}

#[get("/notify")]
pub async fn notify(req: HttpRequest, session_store: web::Data<SessionStore>) -> impl Responder {
    if session_store.is_authenticated(&req) {
        let content = notify_content();
        if is_htmx(&req) {
            HttpResponse::Ok()
                .content_type("text/html; charset=utf-8")
                .body(content.into_string())
        } else {
            HttpResponse::Ok()
                .content_type("text/html; charset=utf-8")
                .body(main_with_content(content).into_string())
        }
    } else {
        let guard = guard_view(None);
        if is_htmx(&req) {
            HttpResponse::Ok()
                .content_type("text/html; charset=utf-8")
                .body(guard.into_string())
        } else {
            HttpResponse::Ok()
                .content_type("text/html; charset=utf-8")
                .body(main_with_content(guard).into_string())
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use actix_web::{App, cookie::Cookie, test as aw_test};

    #[actix_web::test]
    async fn test_notify_unauthenticated_returns_guard() {
        let session_store = web::Data::new(SessionStore::in_memory());
        let app =
            aw_test::init_service(App::new().app_data(session_store.clone()).service(notify)).await;

        let req = aw_test::TestRequest::get().uri("/notify").to_request();
        let resp = aw_test::call_service(&app, req).await;
        assert!(resp.status().is_success());

        let body = aw_test::read_body(resp).await;
        let body_str = String::from_utf8(body.to_vec()).unwrap();
        assert!(body_str.contains("class=\"otp\""));
        assert!(body_str.contains("Unlock"));
        assert!(body_str.contains("<!DOCTYPE html>"));

        // HTMX request
        let req_htmx = aw_test::TestRequest::get()
            .uri("/notify")
            .insert_header(("HX-Request", "true"))
            .to_request();
        let resp_htmx = aw_test::call_service(&app, req_htmx).await;
        assert!(resp_htmx.status().is_success());
        let body_htmx = aw_test::read_body(resp_htmx).await;
        let body_htmx_str = String::from_utf8(body_htmx.to_vec()).unwrap();
        assert!(!body_htmx_str.contains("<!DOCTYPE html>"));
        assert!(body_htmx_str.contains("class=\"otp\""));
    }

    #[actix_web::test]
    async fn test_notify_authenticated_returns_content() {
        let session_store = web::Data::new(SessionStore::in_memory());
        let token = session_store.create_session().await;
        let cookie = Cookie::build(crate::session::SESSION_COOKIE_NAME, token).finish();

        let app =
            aw_test::init_service(App::new().app_data(session_store.clone()).service(notify)).await;

        let req = aw_test::TestRequest::get()
            .uri("/notify")
            .cookie(cookie.clone())
            .to_request();
        let resp = aw_test::call_service(&app, req).await;
        assert!(resp.status().is_success());

        let body = aw_test::read_body(resp).await;
        let body_str = String::from_utf8(body.to_vec()).unwrap();
        assert!(body_str.contains("<!DOCTYPE html>"));
        assert!(body_str.contains("Look at your phone"));
        assert!(body_str.contains("Missing you"));
        assert!(body_str.contains("Emergency"));

        // Check HTMX pending state elements
        assert!(body_str.contains("loading loading-spinner"));
        assert!(body_str.contains("htmx-indicator"));
        assert!(body_str.contains("Pending..."));
        assert!(body_str.contains("id=\"notify-feedback\""));
        assert!(body_str.contains("htmx:after:request"));
        assert!(body_str.contains("hx-disabled-elt=\"this\""));
        assert!(body_str.contains("hx-swap=\"none\""));

        // Check navigation and debug button placement
        assert!(body_str.contains("class=\"navbar"));
        assert!(body_str.contains("navbar-end"));
        assert!(body_str.contains("href=\"/install\""));
        assert!(body_str.contains("Install"));
        assert!(body_str.contains("href=\"/debug\""));
        assert!(body_str.contains("Debug"));
        assert!(!body_str.contains("download=\"notify.json\""));
        assert!(!body_str.contains("Export config"));
        assert!(!body_str.contains("absolute top-4 right-4"));

        // HTMX request returns partial content
        let req_htmx = aw_test::TestRequest::get()
            .uri("/notify")
            .cookie(cookie)
            .insert_header(("HX-Request", "true"))
            .to_request();
        let resp_htmx = aw_test::call_service(&app, req_htmx).await;
        assert!(resp_htmx.status().is_success());
        let body_htmx = aw_test::read_body(resp_htmx).await;
        let body_htmx_str = String::from_utf8(body_htmx.to_vec()).unwrap();
        assert!(!body_htmx_str.contains("<!DOCTYPE html>"));
        assert!(body_htmx_str.contains("Look at your phone"));
    }
}