use axum::extract::{Query, State};
use axum::http::StatusCode;
use axum::response::{Html, IntoResponse, Redirect, Response};
use axum::routing::{get, post};
use axum::{Form, Router};
use chrono::Utc;
use forage_core::integrations::SlackUserLink;
use minijinja::context;
use serde::Deserialize;

use super::{error_page, internal_error};
use crate::auth::{self, MaybeSession, Session};
use crate::state::AppState;
use forage_core::auth::{validate_email, validate_password, validate_username, UserEmail};
use forage_core::session::{CachedOrg, CachedUser, SessionData, generate_csrf_token};

pub fn router() -> Router<AppState> {
    Router::new()
        .route("/signup", get(signup_page).post(signup_submit))
        .route("/login", get(login_page).post(login_submit))
        .route("/logout", post(logout_submit))
        .route(
            "/settings/tokens",
            get(tokens_page).post(create_token_submit),
        )
        .route("/settings/tokens/{id}/delete", post(delete_token_submit))
        .route("/settings/account", get(account_page))
        .route("/settings/account/username", post(update_username_submit))
        .route("/settings/account/password", post(change_password_submit))
        .route("/settings/account/emails", post(add_email_submit))
        .route(
            "/settings/account/emails/remove",
            post(remove_email_submit),
        )
        .route(
            "/settings/account/notifications",
            post(update_notification_preference),
        )
        .route(
            "/settings/account/slack/connect",
            get(slack_connect),
        )
        .route(
            "/settings/account/slack/callback",
            get(slack_user_callback),
        )
        .route(
            "/settings/account/slack/disconnect",
            post(slack_disconnect),
        )
}

// ─── Signup ─────────────────────────────────────────────────────────

async fn signup_page(
    State(state): State<AppState>,
    maybe: MaybeSession,
) -> Result<Response, axum::http::StatusCode> {
    if maybe.session.is_some() {
        return Ok(Redirect::to("/dashboard").into_response());
    }

    render_signup(&state, "", "", "", None)
}

#[derive(Deserialize)]
struct SignupForm {
    username: String,
    email: String,
    password: String,
    password_confirm: String,
}

async fn signup_submit(
    State(state): State<AppState>,
    maybe: MaybeSession,
    Form(form): Form<SignupForm>,
) -> Result<Response, axum::http::StatusCode> {
    if maybe.session.is_some() {
        return Ok(Redirect::to("/dashboard").into_response());
    }

    // Validate
    if let Err(e) = validate_username(&form.username) {
        return render_signup(&state, &form.username, &form.email, "", Some(e.0));
    }
    if let Err(e) = validate_email(&form.email) {
        return render_signup(&state, &form.username, &form.email, "", Some(e.0));
    }
    if let Err(e) = validate_password(&form.password) {
        return render_signup(&state, &form.username, &form.email, "", Some(e.0));
    }
    if form.password != form.password_confirm {
        return render_signup(
            &state,
            &form.username,
            &form.email,
            "",
            Some("Passwords do not match".into()),
        );
    }

    // Register via forest-server
    match state
        .forest_client
        .register(&form.username, &form.email, &form.password)
        .await
    {
        Ok(tokens) => {
            // Fetch user info for the session cache
            let user_cache = match state
                .forest_client
                .get_user(&tokens.access_token)
                .await
            {
                Ok(u) => {
                    let orgs = match state
                        .platform_client
                        .list_my_organisations(&tokens.access_token)
                        .await
                    {
                        Ok(orgs) => orgs
                            .into_iter()
                            .map(|o| CachedOrg {
                                organisation_id: o.organisation_id,
                                name: o.name,
                                role: o.role,
                            })
                            .collect(),
                        Err(e) => {
                            tracing::warn!(error = %e, "failed to fetch orgs during signup");
                            vec![]
                        }
                    };
                    Some(CachedUser {
                        user_id: u.user_id,
                        username: u.username,
                        emails: u.emails,
                        orgs,
                    })
                }
                Err(e) => {
                    tracing::warn!(error = %e, "failed to fetch user during signup");
                    None
                }
            };

            let now = Utc::now();
            let session_data = SessionData {
                access_token: tokens.access_token,
                refresh_token: tokens.refresh_token,
                access_expires_at: now + chrono::Duration::seconds(auth::cap_token_expiry(tokens.expires_in_seconds)),
                user: user_cache,
                csrf_token: generate_csrf_token(),
                created_at: now,
                last_seen_at: now,
            };

            match state.sessions.create(session_data).await {
                Ok(session_id) => {
                    let cookie = auth::session_cookie(&session_id, true);
                    Ok((cookie, Redirect::to("/dashboard")).into_response())
                }
                Err(_) => render_signup(
                    &state,
                    &form.username,
                    &form.email,
                    "",
                    Some("Internal error. Please try again.".into()),
                ),
            }
        }
        Err(forage_core::auth::AuthError::AlreadyExists(_)) => render_signup(
            &state,
            &form.username,
            &form.email,
            "",
            Some("Username or email already registered".into()),
        ),
        Err(forage_core::auth::AuthError::Unavailable(msg)) => {
            tracing::error!("forest-server unavailable: {msg}");
            render_signup(
                &state,
                &form.username,
                &form.email,
                "",
                Some("Service temporarily unavailable. Please try again.".into()),
            )
        }
        Err(e) => render_signup(
            &state,
            &form.username,
            &form.email,
            "",
            Some(e.to_string()),
        ),
    }
}

fn render_signup(
    state: &AppState,
    username: &str,
    email: &str,
    _password: &str,
    error: Option<String>,
) -> Result<Response, axum::http::StatusCode> {
    let html = state
        .templates
        .render(
            "pages/signup.html.jinja",
            context! {
                title => "Sign Up - Forage",
                description => "Create your Forage account",
                username => username,
                email => email,
                error => error,
            },
        )
        .map_err(|e| {
            tracing::error!("template error: {e:#}");
            axum::http::StatusCode::INTERNAL_SERVER_ERROR
        })?;

    Ok(Html(html).into_response())
}

// ─── Login ──────────────────────────────────────────────────────────

async fn login_page(
    State(state): State<AppState>,
    maybe: MaybeSession,
) -> Result<Response, axum::http::StatusCode> {
    if maybe.session.is_some() {
        return Ok(Redirect::to("/dashboard").into_response());
    }

    render_login(&state, "", None)
}

#[derive(Deserialize)]
struct LoginForm {
    identifier: String,
    password: String,
    #[serde(default)]
    remember_me: Option<String>,
}

async fn login_submit(
    State(state): State<AppState>,
    maybe: MaybeSession,
    Form(form): Form<LoginForm>,
) -> Result<Response, axum::http::StatusCode> {
    if maybe.session.is_some() {
        return Ok(Redirect::to("/dashboard").into_response());
    }

    if form.identifier.is_empty() || form.password.is_empty() {
        return render_login(
            &state,
            &form.identifier,
            Some("Email/username and password are required".into()),
        );
    }

    match state
        .forest_client
        .login(&form.identifier, &form.password)
        .await
    {
        Ok(tokens) => {
            let user_cache = match state
                .forest_client
                .get_user(&tokens.access_token)
                .await
            {
                Ok(u) => {
                    let orgs = match state
                        .platform_client
                        .list_my_organisations(&tokens.access_token)
                        .await
                    {
                        Ok(orgs) => orgs
                            .into_iter()
                            .map(|o| CachedOrg {
                                organisation_id: o.organisation_id,
                                name: o.name,
                                role: o.role,
                            })
                            .collect(),
                        Err(e) => {
                            tracing::warn!(error = %e, "failed to fetch orgs during login");
                            vec![]
                        }
                    };
                    Some(CachedUser {
                        user_id: u.user_id,
                        username: u.username,
                        emails: u.emails,
                        orgs,
                    })
                }
                Err(e) => {
                    tracing::warn!(error = %e, "failed to fetch user during login");
                    None
                }
            };

            let now = Utc::now();
            let session_data = SessionData {
                access_token: tokens.access_token,
                refresh_token: tokens.refresh_token,
                access_expires_at: now + chrono::Duration::seconds(auth::cap_token_expiry(tokens.expires_in_seconds)),
                user: user_cache,
                csrf_token: generate_csrf_token(),
                created_at: now,
                last_seen_at: now,
            };

            let remember = form.remember_me.is_some();
            match state.sessions.create(session_data).await {
                Ok(session_id) => {
                    let cookie = auth::session_cookie(&session_id, remember);
                    Ok((cookie, Redirect::to("/dashboard")).into_response())
                }
                Err(_) => render_login(
                    &state,
                    &form.identifier,
                    Some("Internal error. Please try again.".into()),
                ),
            }
        }
        Err(forage_core::auth::AuthError::InvalidCredentials) => render_login(
            &state,
            &form.identifier,
            Some("Invalid email/username or password".into()),
        ),
        Err(forage_core::auth::AuthError::Unavailable(msg)) => {
            tracing::error!("forest-server unavailable: {msg}");
            render_login(
                &state,
                &form.identifier,
                Some("Service temporarily unavailable. Please try again.".into()),
            )
        }
        Err(e) => render_login(&state, &form.identifier, Some(e.to_string())),
    }
}

fn render_login(
    state: &AppState,
    identifier: &str,
    error: Option<String>,
) -> Result<Response, axum::http::StatusCode> {
    let html = state
        .templates
        .render(
            "pages/login.html.jinja",
            context! {
                title => "Sign In - Forage",
                description => "Sign in to your Forage account",
                identifier => identifier,
                error => error,
            },
        )
        .map_err(|e| {
            tracing::error!("template error: {e:#}");
            axum::http::StatusCode::INTERNAL_SERVER_ERROR
        })?;

    Ok(Html(html).into_response())
}

// ─── Logout ─────────────────────────────────────────────────────────

async fn logout_submit(
    State(state): State<AppState>,
    session: Session,
    Form(form): Form<CsrfForm>,
) -> Result<impl IntoResponse, Response> {
    if !auth::validate_csrf(&session, &form._csrf) {
        return Err(error_page(&state, StatusCode::FORBIDDEN, "Invalid request", "CSRF validation failed. Please try again."));
    }
    // Best-effort logout on forest-server
    if let Ok(Some(data)) = state.sessions.get(&session.session_id).await {
        let _ = state.forest_client.logout(&data.refresh_token).await;
    }
    let _ = state.sessions.delete(&session.session_id).await;
    Ok((auth::clear_session_cookie(), Redirect::to("/")))
}

// ─── Tokens ─────────────────────────────────────────────────────────

async fn tokens_page(
    State(state): State<AppState>,
    session: Session,
) -> Result<Response, Response> {
    let tokens = state
        .forest_client
        .list_tokens(&session.access_token, &session.user.user_id)
        .await
        .unwrap_or_default();

    let html = state
        .templates
        .render(
            "pages/tokens.html.jinja",
            context! {
                title => "API Tokens - Forage",
                description => "Manage your personal access tokens",
                user => context! { username => session.user.username },
                current_org => session.user.orgs.first().map(|o| &o.name),
                orgs => session.user.orgs.iter().map(|o| context! { name => o.name, role => o.role }).collect::<Vec<_>>(),
                tokens => tokens.iter().map(|t| context! {
                    token_id => t.token_id,
                    name => t.name,
                    created_at => t.created_at,
                    last_used => t.last_used,
                    expires_at => t.expires_at,
                }).collect::<Vec<_>>(),
                csrf_token => &session.csrf_token,
                created_token => None::<String>,
                active_tab => "tokens",
            },
        )
        .map_err(|e| {
            internal_error(&state, "template error", &e)
        })?;

    Ok(Html(html).into_response())
}

#[derive(Deserialize)]
struct CsrfForm {
    _csrf: String,
}

#[derive(Deserialize)]
struct CreateTokenForm {
    name: String,
    _csrf: String,
}

async fn create_token_submit(
    State(state): State<AppState>,
    session: Session,
    Form(form): Form<CreateTokenForm>,
) -> Result<Response, Response> {
    if !auth::validate_csrf(&session, &form._csrf) {
        return Err(error_page(&state, StatusCode::FORBIDDEN, "Invalid request", "CSRF validation failed. Please try again."));
    }

    let created = state
        .forest_client
        .create_token(&session.access_token, &session.user.user_id, &form.name)
        .await
        .map_err(|e| {
            internal_error(&state, "failed to create token", &e)
        })?;

    let tokens = state
        .forest_client
        .list_tokens(&session.access_token, &session.user.user_id)
        .await
        .unwrap_or_default();

    let html = state
        .templates
        .render(
            "pages/tokens.html.jinja",
            context! {
                title => "API Tokens - Forage",
                description => "Manage your personal access tokens",
                user => context! { username => session.user.username },
                current_org => session.user.orgs.first().map(|o| &o.name),
                orgs => session.user.orgs.iter().map(|o| context! { name => o.name, role => o.role }).collect::<Vec<_>>(),
                tokens => tokens.iter().map(|t| context! {
                    token_id => t.token_id,
                    name => t.name,
                    created_at => t.created_at,
                    last_used => t.last_used,
                    expires_at => t.expires_at,
                }).collect::<Vec<_>>(),
                csrf_token => &session.csrf_token,
                created_token => Some(created.raw_token),
                active_tab => "tokens",
            },
        )
        .map_err(|e| {
            internal_error(&state, "template error", &e)
        })?;

    Ok(Html(html).into_response())
}

async fn delete_token_submit(
    State(state): State<AppState>,
    session: Session,
    axum::extract::Path(token_id): axum::extract::Path<String>,
    Form(form): Form<CsrfForm>,
) -> Result<Response, Response> {
    if !auth::validate_csrf(&session, &form._csrf) {
        return Err(error_page(&state, StatusCode::FORBIDDEN, "Invalid request", "CSRF validation failed. Please try again."));
    }

    state
        .forest_client
        .delete_token(&session.access_token, &token_id)
        .await
        .map_err(|e| {
            internal_error(&state, "failed to delete token", &e)
        })?;

    Ok(Redirect::to("/settings/tokens").into_response())
}

// ─── Account settings ────────────────────────────────────────────────

async fn account_page(
    State(state): State<AppState>,
    session: Session,
) -> Result<Response, Response> {
    let prefs = state
        .platform_client
        .get_notification_preferences(&session.access_token)
        .await
        .unwrap_or_default();

    let slack_links = if let Some(store) = state.integration_store.as_ref() {
        store
            .list_slack_user_links(&session.user.user_id)
            .await
            .unwrap_or_default()
    } else {
        vec![]
    };

    render_account(&state, &session, None, &prefs, &slack_links)
}

#[allow(clippy::result_large_err)]
fn render_account(
    state: &AppState,
    session: &Session,
    error: Option<&str>,
    notification_prefs: &[forage_core::platform::NotificationPreference],
    slack_links: &[SlackUserLink],
) -> Result<Response, Response> {
    let html = state
        .templates
        .render(
            "pages/account.html.jinja",
            context! {
                title => "Account Settings - Forage",
                description => "Manage your account settings",
                user => context! {
                    username => &session.user.username,
                    user_id => &session.user.user_id,
                    emails => session.user.emails.iter().map(|e| context! {
                        email => &e.email,
                        verified => e.verified,
                    }).collect::<Vec<_>>(),
                },
                current_org => session.user.orgs.first().map(|o| &o.name),
                orgs => session.user.orgs.iter().map(|o| context! { name => o.name, role => o.role }).collect::<Vec<_>>(),
                csrf_token => &session.csrf_token,
                error => error,
                active_tab => "account",
                enabled_prefs => notification_prefs.iter()
                    .filter(|p| p.enabled)
                    .map(|p| format!("{}|{}", p.notification_type, p.channel))
                    .collect::<Vec<_>>(),
                has_slack_oauth => state.slack_config.is_some(),
                slack_links => slack_links.iter().map(|l| context! {
                    id => &l.id,
                    team_id => &l.team_id,
                    team_name => &l.team_name,
                    slack_user_id => &l.slack_user_id,
                    slack_username => &l.slack_username,
                }).collect::<Vec<_>>(),
            },
        )
        .map_err(|e| {
            internal_error(state, "template error", &e)
        })?;

    Ok(Html(html).into_response())
}

#[derive(Deserialize)]
struct UpdateUsernameForm {
    username: String,
    _csrf: String,
}

async fn update_username_submit(
    State(state): State<AppState>,
    session: Session,
    Form(form): Form<UpdateUsernameForm>,
) -> Result<Response, Response> {
    if !auth::validate_csrf(&session, &form._csrf) {
        return Err(error_page(
            &state,
            StatusCode::FORBIDDEN,
            "Invalid request",
            "CSRF validation failed.",
        ));
    }

    if let Err(e) = validate_username(&form.username) {
        return render_account(&state, &session, Some(&e.0), &[], &[]);
    }

    match state
        .forest_client
        .update_username(&session.access_token, &session.user.user_id, &form.username)
        .await
    {
        Ok(updated_user) => {
            // Update cached username in session
            if let Ok(Some(mut session_data)) = state.sessions.get(&session.session_id).await {
                if let Some(ref mut user) = session_data.user {
                    user.username = updated_user.username;
                }
                let _ = state
                    .sessions
                    .update(&session.session_id, session_data)
                    .await;
            }
            Ok(Redirect::to("/settings/account").into_response())
        }
        Err(forage_core::auth::AuthError::AlreadyExists(_)) => {
            render_account(&state, &session, Some("Username is already taken."), &[], &[])
        }
        Err(e) => {
            tracing::error!("failed to update username: {e}");
            render_account(&state, &session, Some("Could not update username. Please try again."), &[], &[])
        }
    }
}

#[derive(Deserialize)]
struct ChangePasswordForm {
    current_password: String,
    new_password: String,
    new_password_confirm: String,
    _csrf: String,
}

async fn change_password_submit(
    State(state): State<AppState>,
    session: Session,
    Form(form): Form<ChangePasswordForm>,
) -> Result<Response, Response> {
    if !auth::validate_csrf(&session, &form._csrf) {
        return Err(error_page(
            &state,
            StatusCode::FORBIDDEN,
            "Invalid request",
            "CSRF validation failed.",
        ));
    }

    if form.new_password != form.new_password_confirm {
        return render_account(&state, &session, Some("New passwords do not match."), &[], &[]);
    }

    if let Err(e) = validate_password(&form.new_password) {
        return render_account(&state, &session, Some(&e.0), &[], &[]);
    }

    match state
        .forest_client
        .change_password(
            &session.access_token,
            &session.user.user_id,
            &form.current_password,
            &form.new_password,
        )
        .await
    {
        Ok(()) => Ok(Redirect::to("/settings/account").into_response()),
        Err(forage_core::auth::AuthError::InvalidCredentials) => {
            render_account(&state, &session, Some("Current password is incorrect."), &[], &[])
        }
        Err(e) => {
            tracing::error!("failed to change password: {e}");
            render_account(&state, &session, Some("Could not change password. Please try again."), &[], &[])
        }
    }
}

#[derive(Deserialize)]
struct AddEmailForm {
    email: String,
    _csrf: String,
}

async fn add_email_submit(
    State(state): State<AppState>,
    session: Session,
    Form(form): Form<AddEmailForm>,
) -> Result<Response, Response> {
    if !auth::validate_csrf(&session, &form._csrf) {
        return Err(error_page(
            &state,
            StatusCode::FORBIDDEN,
            "Invalid request",
            "CSRF validation failed.",
        ));
    }

    if let Err(e) = validate_email(&form.email) {
        return render_account(&state, &session, Some(&e.0), &[], &[]);
    }

    match state
        .forest_client
        .add_email(&session.access_token, &session.user.user_id, &form.email)
        .await
    {
        Ok(new_email) => {
            // Update cached emails in session
            if let Ok(Some(mut session_data)) = state.sessions.get(&session.session_id).await {
                if let Some(ref mut user) = session_data.user {
                    user.emails.push(UserEmail {
                        email: new_email.email,
                        verified: new_email.verified,
                    });
                }
                let _ = state
                    .sessions
                    .update(&session.session_id, session_data)
                    .await;
            }
            Ok(Redirect::to("/settings/account").into_response())
        }
        Err(forage_core::auth::AuthError::AlreadyExists(_)) => {
            render_account(&state, &session, Some("Email is already registered."), &[], &[])
        }
        Err(e) => {
            tracing::error!("failed to add email: {e}");
            render_account(&state, &session, Some("Could not add email. Please try again."), &[], &[])
        }
    }
}

#[derive(Deserialize)]
struct RemoveEmailForm {
    email: String,
    _csrf: String,
}

async fn remove_email_submit(
    State(state): State<AppState>,
    session: Session,
    Form(form): Form<RemoveEmailForm>,
) -> Result<Response, Response> {
    if !auth::validate_csrf(&session, &form._csrf) {
        return Err(error_page(
            &state,
            StatusCode::FORBIDDEN,
            "Invalid request",
            "CSRF validation failed.",
        ));
    }

    match state
        .forest_client
        .remove_email(&session.access_token, &session.user.user_id, &form.email)
        .await
    {
        Ok(()) => {
            // Update cached emails in session
            if let Ok(Some(mut session_data)) = state.sessions.get(&session.session_id).await {
                if let Some(ref mut user) = session_data.user {
                    user.emails.retain(|e| e.email != form.email);
                }
                let _ = state
                    .sessions
                    .update(&session.session_id, session_data)
                    .await;
            }
            Ok(Redirect::to("/settings/account").into_response())
        }
        Err(e) => {
            tracing::error!("failed to remove email: {e}");
            render_account(&state, &session, Some("Could not remove email. Please try again."), &[], &[])
        }
    }
}

// ─── Notification preferences ────────────────────────────────────────

#[derive(Deserialize)]
struct UpdateNotificationPreferenceForm {
    _csrf: String,
    notification_type: String,
    channel: String,
    enabled: String,
}

async fn update_notification_preference(
    State(state): State<AppState>,
    session: Session,
    Form(form): Form<UpdateNotificationPreferenceForm>,
) -> Result<Response, Response> {
    if !auth::validate_csrf(&session, &form._csrf) {
        return Err(error_page(
            &state,
            StatusCode::FORBIDDEN,
            "Forbidden",
            "Invalid CSRF token.",
        ));
    }

    let enabled = form.enabled == "true";

    state
        .platform_client
        .set_notification_preference(
            &session.access_token,
            &form.notification_type,
            &form.channel,
            enabled,
        )
        .await
        .map_err(|e| internal_error(&state, "set notification preference", &e))?;

    Ok(Redirect::to("/settings/account").into_response())
}

// ─── Slack user enrollment ────────────────────────────────────────────

async fn slack_connect(
    State(state): State<AppState>,
    session: Session,
) -> Result<impl IntoResponse, Response> {
    let slack_config = state.slack_config.as_ref().ok_or_else(|| {
        error_page(
            &state,
            StatusCode::SERVICE_UNAVAILABLE,
            "Slack not configured",
            "Slack OAuth is not configured on this server.",
        )
    })?;

    let redirect_uri = format!(
        "{}/settings/account/slack/callback",
        slack_config.redirect_host
    );
    let url = format!(
        "https://slack.com/oauth/v2/authorize?client_id={}&user_scope=identity.basic&redirect_uri={}&state={}",
        urlencoding::encode(&slack_config.client_id),
        urlencoding::encode(&redirect_uri),
        urlencoding::encode(&session.user.user_id),
    );

    Ok(Redirect::to(&url))
}

#[derive(Deserialize)]
struct SlackUserCallbackQuery {
    code: Option<String>,
    state: Option<String>,
    error: Option<String>,
}

async fn slack_user_callback(
    State(state): State<AppState>,
    session: Session,
    Query(query): Query<SlackUserCallbackQuery>,
) -> Result<Response, Response> {
    // Handle user-denied case
    if let Some(err) = query.error {
        tracing::warn!("Slack user OAuth denied: {err}");
        return Ok(Redirect::to("/settings/account").into_response());
    }

    let code = query.code.ok_or_else(|| {
        error_page(
            &state,
            StatusCode::BAD_REQUEST,
            "Invalid request",
            "Missing authorization code from Slack.",
        )
    })?;

    // Verify state matches our user_id to prevent CSRF
    let state_param = query.state.unwrap_or_default();
    if state_param != session.user.user_id {
        return Err(error_page(
            &state,
            StatusCode::FORBIDDEN,
            "Invalid request",
            "State parameter mismatch. Please try connecting again.",
        ));
    }

    let slack_config = state.slack_config.as_ref().ok_or_else(|| {
        error_page(
            &state,
            StatusCode::SERVICE_UNAVAILABLE,
            "Not configured",
            "Slack OAuth is not configured.",
        )
    })?;

    let integration_store = state.integration_store.as_ref().ok_or_else(|| {
        error_page(
            &state,
            StatusCode::SERVICE_UNAVAILABLE,
            "Not available",
            "Slack account linking requires a database. Set DATABASE_URL to enable.",
        )
    })?;

    let redirect_uri = format!(
        "{}/settings/account/slack/callback",
        slack_config.redirect_host
    );

    // Exchange the authorization code for a user access token
    let http = reqwest::Client::new();
    let token_resp = http
        .post("https://slack.com/api/oauth.v2.access")
        .form(&[
            ("client_id", slack_config.client_id.as_str()),
            ("client_secret", slack_config.client_secret.as_str()),
            ("code", &code),
            ("redirect_uri", &redirect_uri),
        ])
        .send()
        .await
        .map_err(|e| {
            internal_error(
                &state,
                "slack user oauth",
                &format!("Failed to contact Slack: {e}"),
            )
        })?;

    let resp_body: serde_json::Value = token_resp.json().await.map_err(|e| {
        internal_error(
            &state,
            "slack user oauth",
            &format!("Failed to parse Slack response: {e}"),
        )
    })?;

    if resp_body.get("ok").and_then(|v| v.as_bool()) != Some(true) {
        let err_msg = resp_body
            .get("error")
            .and_then(|v| v.as_str())
            .unwrap_or("unknown error");
        tracing::error!("Slack user OAuth error: {err_msg}");
        return Err(error_page(
            &state,
            StatusCode::BAD_GATEWAY,
            "Slack error",
            &format!("Slack returned an error: {err_msg}"),
        ));
    }

    // For user-scoped OAuth, the user token is nested under authed_user
    let authed_user = resp_body.get("authed_user").ok_or_else(|| {
        internal_error(
            &state,
            "slack user oauth",
            &"Missing authed_user in Slack response",
        )
    })?;

    let slack_user_id = authed_user["id"].as_str().unwrap_or("").to_string();
    let user_access_token = authed_user["access_token"].as_str().unwrap_or("").to_string();
    let team_id = resp_body["team"]["id"].as_str().unwrap_or("").to_string();
    let team_name = resp_body["team"]["name"].as_str().unwrap_or("").to_string();

    // Fetch display name via users.identity (requires identity.basic user scope)
    let slack_username = if user_access_token.is_empty() {
        slack_user_id.clone()
    } else {
        let identity_name: Option<String> = async {
            let r = http
                .get("https://slack.com/api/users.identity")
                .bearer_auth(&user_access_token)
                .send()
                .await
                .ok()?;
            let body: serde_json::Value = r.json().await.ok()?;
            let name = body["user"]["name"].as_str()?.to_string();
            if name.is_empty() { None } else { Some(name) }
        }
        .await;

        identity_name.unwrap_or_else(|| slack_user_id.clone())
    };

    let now = chrono::Utc::now().to_rfc3339();
    let link = SlackUserLink {
        id: uuid::Uuid::new_v4().to_string(),
        user_id: session.user.user_id.clone(),
        team_id,
        team_name,
        slack_user_id,
        slack_username,
        created_at: now,
    };

    integration_store
        .upsert_slack_user_link(&link)
        .await
        .map_err(|e| internal_error(&state, "upsert slack user link", &e))?;

    Ok(Redirect::to("/settings/account").into_response())
}

#[derive(Deserialize)]
struct SlackDisconnectForm {
    team_id: String,
    _csrf: String,
}

async fn slack_disconnect(
    State(state): State<AppState>,
    session: Session,
    Form(form): Form<SlackDisconnectForm>,
) -> Result<Response, Response> {
    if !auth::validate_csrf(&session, &form._csrf) {
        return Err(error_page(
            &state,
            StatusCode::FORBIDDEN,
            "Invalid request",
            "CSRF validation failed.",
        ));
    }

    let integration_store = state.integration_store.as_ref().ok_or_else(|| {
        error_page(
            &state,
            StatusCode::SERVICE_UNAVAILABLE,
            "Not available",
            "Slack account linking requires a database.",
        )
    })?;

    integration_store
        .delete_slack_user_link(&session.user.user_id, &form.team_id)
        .await
        .map_err(|e| internal_error(&state, "delete slack user link", &e))?;

    Ok(Redirect::to("/settings/account").into_response())
}
