use crate::AppState;
use axum::{
    extract::State,
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post},
    Json, Router,
};
use contracts::auth_middleware::AuthUser;
use db::models::role::RoleRepository;
use db::models::user_role_profile::UserRoleProfileRepository;
use serde::{Deserialize, Serialize};

pub fn router() -> Router<AppState> {
    Router::new()
        .route("/", get(list_my_roles))
        .route("/register", post(register_role))
}

#[derive(Deserialize)]
pub struct RegisterRolePayload {
    pub role_key: String,
}

#[derive(Serialize)]
pub struct UserRoleResponse {
    pub role_key: String,
    pub role_name: String,
    pub status: String,
    pub approved_at: Option<chrono::DateTime<chrono::Utc>>,
}

#[allow(dead_code)]
fn is_professional_role(role_key: &str) -> bool {
    matches!(
        role_key,
        "PHOTOGRAPHER"
            | "MAKEUP_ARTIST"
            | "TUTOR"
            | "DEVELOPER"
            | "VIDEO_EDITOR"
            | "GRAPHIC_DESIGNER"
            | "SOCIAL_MEDIA_MANAGER"
            | "FITNESS_TRAINER"
            | "CATERING_SERVICES"
            | "UGC_CONTENT_CREATOR"
    )
}

#[derive(sqlx::FromRow)]
struct UserRoleRow {
    key: String,
    name: String,
    status: String,
    approved_at: Option<chrono::DateTime<chrono::Utc>>,
}

async fn list_my_roles(
    auth: AuthUser,
    State(state): State<AppState>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    let rows = sqlx::query_as::<_, UserRoleRow>(
        r#"
        SELECT r.key, r.name, ur.status, ur.approved_at
        FROM user_role_assignments ur
        INNER JOIN roles r ON r.id = ur.role_id
        WHERE ur.user_id = $1
        ORDER BY ur.created_at ASC
        "#,
    )
    .bind(auth.user_id)
    .fetch_all(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    let mapped = rows
        .into_iter()
        .map(|r| UserRoleResponse {
            role_key: r.key,
            role_name: r.name,
            status: r.status,
            approved_at: r.approved_at,
        })
        .collect::<Vec<_>>();

    Ok((StatusCode::OK, Json(mapped)))
}

async fn register_role(
    auth: AuthUser,
    State(state): State<AppState>,
    Json(payload): Json<RegisterRolePayload>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    let role_key = payload.role_key.trim().to_uppercase();
    if role_key.is_empty() {
        return Err((StatusCode::BAD_REQUEST, "role_key is required".to_string()));
    }

    let role = RoleRepository::get_by_key(&state.pool, &role_key)
        .await
        .map_err(|_| (StatusCode::NOT_FOUND, format!("Role '{}' not found", role_key)))?;

    sqlx::query(
        r#"
        INSERT INTO user_role_assignments (user_id, role_id, status, approved_at)
        VALUES ($1, $2, 'APPROVED', NOW())
        ON CONFLICT (user_id, role_id)
        DO UPDATE SET status = 'APPROVED', approved_at = NOW()
        "#,
    )
    .bind(auth.user_id)
    .bind(role.id)
    .execute(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    // For professional/external roles, also create the user_role_profiles entry so
    // downstream services (e.g. leads) can find the professional's profile.
    if is_professional_role(&role_key) {
        if let Err(e) = UserRoleProfileRepository::create(&state.pool, auth.user_id, &role_key).await {
            tracing::warn!("Failed to create user_role_profiles entry for {}: {}", role_key, e);
            // Non-fatal — the assignment is created; the profile row can be backfilled later.
        }
    }

    Ok((
        StatusCode::OK,
        Json(serde_json::json!({
            "message": "Role registered successfully",
            "role_key": role_key,
            "role_id": role.id,
            "user_id": auth.user_id.to_string(),
            "status": "APPROVED"
        })),
    ))
}
