//! Session cookie extraction + login flows.
//!
//! These were previously copy-pasted 4–6 times across the integration
//! tests. Centralising them here means: a change to the cookie format
//! or the login form field names affects one file, not six.

use axum::Router;
use axum::body::Body;
use axum::http::{Request, header};
use axum::response::Response;
use tower::util::ServiceExt;

use doctate_server::web_session::SessionStore;

use crate::common::paths;

/// Extract the freshly-minted `session=<value>` cookie from a login
/// response's `Set-Cookie` headers. Returns the whole `name=value` pair
/// (no attributes like `; Path=/`) so it can be sent back verbatim in a
/// `Cookie` header.
pub fn extract_session_cookie(resp: &Response) -> Option<String> {
    for v in resp.headers().get_all(header::SET_COOKIE).iter() {
        let s = v.to_str().ok()?;
        if let Some(pair) = s.split(';').next()
            && pair.starts_with("session=")
        {
            return Some(pair.to_string());
        }
    }
    None
}

/// Build the `POST /web/login` request with a URL-encoded form body.
/// Pulled out so tests that need to *inspect* the login response
/// (e.g. session-fixation tests) can reuse the exact wire shape.
pub fn login_request(slug: &str, password: &str) -> Request<Body> {
    let body = format!("slug={slug}&password={password}");
    Request::builder()
        .method("POST")
        .uri(paths::LOGIN)
        .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
        .body(Body::from(body))
        .unwrap()
}

/// Log in, return the resulting `session=...` cookie string.
///
/// Panics if the server didn't set a session cookie — meaning the
/// credentials were wrong or the route shape has changed. Tests that
/// deliberately exercise the failure path should use [`login_request`]
/// directly.
pub async fn login(app: &Router, slug: &str, password: &str) -> String {
    let resp = app
        .clone()
        .oneshot(login_request(slug, password))
        .await
        .unwrap();
    extract_session_cookie(&resp).expect("login did not set a session cookie")
}

/// Log in and additionally fetch the session's CSRF token directly from
/// the session store. Pairs with
/// `doctate_server::create_router_and_session_store` — the returned
/// token is what the `CsrfForm<T>` extractor validates on every
/// state-changing POST under `/web/`.
///
/// Returns `(cookie_header_value, csrf_token)`.
pub async fn login_with_csrf(
    app: &Router,
    store: &SessionStore,
    slug: &str,
    password: &str,
) -> (String, String) {
    let cookie = login(app, slug, password).await;
    let token = cookie.trim_start_matches("session=");
    let csrf = store
        .read()
        .await
        .get(token)
        .expect("session must be in the store right after login")
        .csrf_token
        .clone();
    (cookie, csrf)
}
