mod common;

use axum::body::Body;
use axum::http::{Request, StatusCode, header};
use tower::util::ServiceExt;

use common::{
    TestConfig, body_string, extract_session_cookie, get_with_cookie, login_request, paths,
    test_user_with_password,
};

#[tokio::test]
async fn login_success_sets_session_cookie_and_redirects() {
    let cfg = TestConfig::new()
        .with_user(test_user_with_password("dr_a", "secret"))
        .build();
    let app = doctate_server::create_router(cfg);

    let resp = app.oneshot(login_request("dr_a", "secret")).await.unwrap();
    assert_eq!(
        resp.status(),
        StatusCode::SEE_OTHER.as_u16(),
        "got {:?}",
        resp.status()
    );

    let loc = resp
        .headers()
        .get(header::LOCATION)
        .unwrap()
        .to_str()
        .unwrap();
    assert_eq!(loc, "/web/cases");

    let cookie = extract_session_cookie(&resp).expect("no session cookie");
    assert!(cookie.starts_with("session="));
    assert!(
        cookie.len() > "session=".len() + 20,
        "token looks too short: {cookie}"
    );
}

#[tokio::test]
async fn login_wrong_password_shows_error() {
    let cfg = TestConfig::new()
        .with_user(test_user_with_password("dr_a", "secret"))
        .build();
    let app = doctate_server::create_router(cfg);

    let resp = app.oneshot(login_request("dr_a", "wrong")).await.unwrap();
    assert_eq!(resp.status(), StatusCode::OK);
    assert!(extract_session_cookie(&resp).is_none());
    let body = body_string(resp).await;
    assert!(body.contains("Login fehlgeschlagen"), "got: {body}");
}

#[tokio::test]
async fn login_unknown_user_shows_error() {
    let cfg = TestConfig::new()
        .with_user(test_user_with_password("dr_a", "secret"))
        .build();
    let app = doctate_server::create_router(cfg);

    let resp = app
        .oneshot(login_request("ghost", "anything"))
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::OK);
    assert!(extract_session_cookie(&resp).is_none());
}

#[tokio::test]
async fn cases_without_cookie_redirects_to_login() {
    let cfg = TestConfig::new()
        .with_user(test_user_with_password("dr_a", "secret"))
        .build();
    let app = doctate_server::create_router(cfg);

    let resp = app
        .oneshot(
            Request::builder()
                .uri(paths::CASES)
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::FOUND);
    assert_eq!(
        resp.headers()
            .get(header::LOCATION)
            .unwrap()
            .to_str()
            .unwrap(),
        paths::LOGIN
    );
}

#[tokio::test]
async fn cases_with_valid_cookie_shows_only_own_cases() {
    let cfg = TestConfig::new()
        .with_user(test_user_with_password("dr_a", "s"))
        .with_user(test_user_with_password("dr_b", "s"))
        .build();
    // Seed fixtures: dr_a has an open case, dr_b has one too.
    let case_a = cfg
        .data_path
        .join("dr_a/11111111-1111-1111-1111-111111111111");
    let case_b = cfg
        .data_path
        .join("dr_b/22222222-2222-2222-2222-222222222222");
    std::fs::create_dir_all(&case_a).unwrap();
    std::fs::create_dir_all(&case_b).unwrap();
    std::fs::write(case_a.join("2026-04-14T10-00-00Z.m4a"), b"x").unwrap();
    std::fs::write(case_b.join("2026-04-14T11-00-00Z.m4a"), b"x").unwrap();

    let app = doctate_server::create_router(cfg);
    let cookie = common::login(&app, "dr_a", "s").await;

    let resp = app
        .oneshot(get_with_cookie(paths::CASES, &cookie))
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::OK);
    let body = body_string(resp).await;
    assert!(body.contains("11111111"), "own case missing: {body}");
    assert!(!body.contains("22222222"), "foreign case leaked: {body}");
}

#[tokio::test]
async fn case_detail_of_foreign_case_returns_404() {
    let cfg = TestConfig::new()
        .with_user(test_user_with_password("dr_a", "s"))
        .with_user(test_user_with_password("dr_b", "s"))
        .build();
    let case_b = cfg
        .data_path
        .join("dr_b/22222222-2222-2222-2222-222222222222");
    std::fs::create_dir_all(&case_b).unwrap();
    std::fs::write(case_b.join("2026-04-14T11-00-00Z.m4a"), b"x").unwrap();

    let app = doctate_server::create_router(cfg);
    let cookie = common::login(&app, "dr_a", "s").await;

    let resp = app
        .oneshot(get_with_cookie(
            paths::case_detail("22222222-2222-2222-2222-222222222222"),
            &cookie,
        ))
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}

#[tokio::test]
async fn logout_clears_session() {
    let cfg = TestConfig::new()
        .with_user(test_user_with_password("dr_a", "s"))
        .build();
    let (app, store) = doctate_server::create_router_and_session_store(cfg);

    let (cookie, csrf) = common::login_with_csrf(&app, &store, "dr_a", "s").await;

    let logout = app
        .clone()
        .oneshot(common::csrf_form_post(paths::LOGOUT, &cookie, &csrf, ""))
        .await
        .unwrap();
    assert_eq!(logout.status(), StatusCode::SEE_OTHER.as_u16());

    // Same cookie must no longer be accepted.
    let resp = app
        .oneshot(get_with_cookie(paths::CASES, &cookie))
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::FOUND);
}
