use crate::{
    endpoints::user::post_login::LoginForm,
    models::{Function, Role, User},
};
use actix_http::Request;
use actix_web::{
    body::MessageBody,
    cookie::Cookie,
    dev::{Service, ServiceResponse},
    error::Error,
    test,
};
use serde::Serialize;
use sqlx::{Pool, Postgres};

pub struct RequestConfig {
    pub uri: String,
    pub role: Role,
    pub function: Function,
    pub user_area: i32,
}

impl RequestConfig {
    pub fn new(uri: &str) -> Self {
        Self {
            uri: uri.to_string(),
            role: Role::Staff,
            function: Function::Posten,
            user_area: 1,
        }
    }
}

async fn create_user_and_get_login_cookie<'a, T, R>(
    pool: &Pool<Postgres>,
    app: &T,
    config: &RequestConfig,
) -> Cookie<'a>
where
    T: Service<Request, Response = ServiceResponse<R>, Error = Error>,
    R: MessageBody + 'a,
{
    const HASH: &str = "$argon2id$v=19$m=19456,t=2,p=1$IPiLaPCFZOK69MA1a6GUzw$ZZinpbkP7pXhN7g7dGkh87kGTeuFd/2er1U+y+4IKWo";
    const SALT: &str = "IPiLaPCFZOK69MA1a6GUzw";

    User::create_with_password(
        pool,
        "abc",
        "abc",
        &HASH,
        &SALT,
        config.role,
        config.function,
        config.user_area,
    )
    .await
    .unwrap();

    let login_form = LoginForm {
        email: "abc".to_string(),
        password: "abc".to_string(),
    };

    let login_req = test::TestRequest::post()
        .uri("/login")
        .set_form(login_form)
        .to_request();

    let login_resp = test::call_service(&app, login_req).await;
    login_resp.response().cookies().next().unwrap().into_owned()
}

pub async fn read_body<M>(response: ServiceResponse<M>) -> String
where
    M: MessageBody,
{
    String::from_utf8(test::read_body(response).await.to_vec()).unwrap()
}

pub async fn test_get<T, R>(
    pool: &Pool<Postgres>,
    app: T,
    config: &RequestConfig,
) -> ServiceResponse<R>
where
    T: Service<Request, Response = ServiceResponse<R>, Error = Error>,
    R: MessageBody,
{
    let cookie = create_user_and_get_login_cookie(&pool, &app, &config).await;

    let get_request = test::TestRequest::get()
        .uri(&config.uri)
        .cookie(cookie)
        .to_request();

    test::call_service(&app, get_request).await
}

pub async fn test_post<T, R, F>(
    pool: &Pool<Postgres>,
    app: T,
    config: &RequestConfig,
    form: F,
) -> ServiceResponse<R>
where
    T: Service<Request, Response = ServiceResponse<R>, Error = Error>,
    R: MessageBody,
    F: Serialize,
{
    let cookie = create_user_and_get_login_cookie(&pool, &app, config).await;

    let post_request = test::TestRequest::post()
        .uri(&config.uri)
        .cookie(cookie)
        .set_form(form)
        .to_request();

    test::call_service(&app, post_request).await
}

pub async fn test_delete<T, R>(
    pool: &Pool<Postgres>,
    app: T,
    config: &RequestConfig,
) -> ServiceResponse<R>
where
    T: Service<Request, Response = ServiceResponse<R>, Error = Error>,
    R: MessageBody,
{
    let cookie = create_user_and_get_login_cookie(&pool, &app, config).await;

    let delete_request = test::TestRequest::delete()
        .uri(&config.uri)
        .cookie(cookie)
        .to_request();

    test::call_service(&app, delete_request).await
}
