use axum::{Extension, Json, extract::State, http::StatusCode, response::IntoResponse};
use axum_extra::extract::cookie::CookieJar;
use serde_json::json;

use crate::{
    auth::{AuthenticatedSession, cleared_session_cookie, extract_session_token, session_cookie},
    error::ApiError,
    service::{
        ChangePasswordPayload, LoginPayload, UpdateCurrentWorkspacePayload, UpdateProfilePayload,
    },
    state::AppState,
};

pub async fn login(
    State(state): State<AppState>,
    jar: CookieJar,
    Json(payload): Json<LoginPayload>,
) -> Result<impl IntoResponse, ApiError> {
    let (session_data, session) = state.service.login(payload).await?;
    let cookie_value = format!(
        "{}.{}",
        session_data.session_id.as_str(),
        session_data.value
    );
    let jar = jar.add(session_cookie(state.service.auth_settings(), &cookie_value));

    Ok((jar, Json(serde_json::json!(session))))
}

pub async fn logout(
    State(state): State<AppState>,
    jar: CookieJar,
) -> Result<impl IntoResponse, ApiError> {
    if let Some((session_id, session_value)) = extract_session_token(&jar) {
        state.service.logout(&session_id, &session_value).await?;
    }

    let jar = jar.remove(cleared_session_cookie(state.service.auth_settings()));
    Ok((jar, StatusCode::NO_CONTENT))
}

pub async fn get_session(
    State(state): State<AppState>,
    jar: CookieJar,
) -> Result<Json<serde_json::Value>, ApiError> {
    let (session_id, session_value) = extract_session_token(&jar)
        .ok_or_else(|| ApiError::unauthorized("authentication required"))?;
    let session = state
        .service
        .session_response(&session_id, &session_value)
        .await?
        .ok_or_else(|| ApiError::unauthorized("session is invalid or expired"))?;

    Ok(Json(serde_json::json!(session)))
}

pub async fn get_profile(
    Extension(session): Extension<AuthenticatedSession>,
) -> Result<Json<serde_json::Value>, ApiError> {
    Ok(Json(json!({
        "user": session.user,
        "memberships": session.memberships,
        "current_workspace_id": session.current_workspace_id
    })))
}

pub async fn update_profile(
    State(state): State<AppState>,
    Extension(session): Extension<AuthenticatedSession>,
    Json(payload): Json<UpdateProfilePayload>,
) -> Result<Json<serde_json::Value>, ApiError> {
    let updated = state
        .service
        .update_profile(
            &session.user.id,
            session.current_workspace_id.as_ref(),
            payload,
        )
        .await?;
    Ok(Json(json!(updated)))
}

pub async fn update_current_workspace(
    State(state): State<AppState>,
    Extension(session): Extension<AuthenticatedSession>,
    Json(payload): Json<UpdateCurrentWorkspacePayload>,
) -> Result<Json<serde_json::Value>, ApiError> {
    let updated = state
        .service
        .set_current_workspace(
            &session.session_id,
            &session.user.id,
            &payload.workspace_id.as_str().into(),
        )
        .await?;
    Ok(Json(json!(updated)))
}

pub async fn change_password(
    State(state): State<AppState>,
    Extension(session): Extension<AuthenticatedSession>,
    Json(payload): Json<ChangePasswordPayload>,
) -> Result<StatusCode, ApiError> {
    state
        .service
        .change_password(&session.user.id, payload)
        .await?;
    Ok(StatusCode::NO_CONTENT)
}
