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

// ── Routers ───────────────────────────────────────────────────────────────────

pub fn onboarding_router() -> Router<AppState> {
    Router::new()
        .route("/state",         get(get_state))
        .route("/save-progress", post(save_progress))
        .route("/submit",        post(submit))
}

#[allow(dead_code)]
pub fn me_router() -> Router<AppState> {
    Router::new()
        .route("/profile-status", get(profile_status))
}

// ── DTOs ──────────────────────────────────────────────────────────────────────

#[derive(Deserialize)]
pub struct RoleKeyQuery {
    #[serde(rename = "roleKey")]
    pub role_key: Option<String>,
}

// Accept role_key (string) so the frontend never has to know the internal UUID.
#[derive(Deserialize)]
pub struct SaveProgressInput {
    #[serde(rename = "roleKey", alias = "role_key")]
    pub role_key: String,
    pub progress_json: Option<serde_json::Value>,
}

#[derive(Deserialize)]
pub struct SubmitInput {
    #[serde(rename = "roleKey", alias = "role_key")]
    pub role_key: String,
    pub progress_json: Option<serde_json::Value>,
}

#[derive(Serialize)]
#[allow(dead_code)]
pub struct ProfileStatusResponse {
    pub onboarding_complete: bool,
    pub active_role: Option<String>,
    pub roles: Vec<String>,
    pub email_verified: bool,
}

// ── Handlers ──────────────────────────────────────────────────────────────────

/// GET /api/onboarding/state?roleKey=COMPANY
async fn get_state(
    auth: AuthUser,
    State(state): State<AppState>,
    Query(query): Query<RoleKeyQuery>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    let role_key = query
        .role_key
        .filter(|k| !k.is_empty())
        .unwrap_or_else(|| auth.claims.active_role.clone());

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

    let onboarding_state =
        OnboardingStateRepository::get(&state.pool, auth.user_id, role.id)
            .await
            .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    let response = match onboarding_state {
        Some(s) => serde_json::json!({
            "status": s.status,
            "currentStep": s.progress_json
                .get("step")
                .and_then(|v| v.as_i64())
                .unwrap_or(0),
            "progress": s.progress_json,
            "completed_at": s.completed_at,
            "role_key": role_key,
        }),
        None => serde_json::json!({
            "status": "NOT_STARTED",
            "currentStep": 0,
            "progress": null,
            "completed_at": null,
            "role_key": role_key,
        }),
    };

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

/// POST /api/onboarding/save-progress
/// Body: { roleKey: "PHOTOGRAPHER", progress_json: { step: 2, total: 6, data: {...} } }
async fn save_progress(
    auth: AuthUser,
    State(state): State<AppState>,
    Json(input): Json<SaveProgressInput>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    let role = RoleRepository::get_by_key(&state.pool, &input.role_key)
        .await
        .map_err(|_| (StatusCode::NOT_FOUND, format!("Role '{}' not found", input.role_key)))?;

    let progress = input.progress_json.unwrap_or(serde_json::Value::Object(Default::default()));

    let saved = OnboardingStateRepository::save_progress(
        &state.pool,
        auth.user_id,
        role.id,
        &progress,
    )
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

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

/// POST /api/onboarding/submit
/// Body: { roleKey: "PHOTOGRAPHER", progress_json: { ...all form values... } }
async fn submit(
    auth: AuthUser,
    State(state): State<AppState>,
    Json(input): Json<SubmitInput>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    let role = RoleRepository::get_by_key(&state.pool, &input.role_key)
        .await
        .map_err(|_| (StatusCode::NOT_FOUND, format!("Role '{}' not found", input.role_key)))?;

    let progress = input.progress_json.unwrap_or(serde_json::Value::Object(Default::default()));

    // 1. Complete onboarding state
    let completed = OnboardingStateRepository::complete(
        &state.pool,
        auth.user_id,
        role.id,
        &progress,
    )
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    // 2. Create/Update specialized profile based on role
    let role_key = input.role_key.to_uppercase();
    let table_name = match role_key.as_str() {
        "PHOTOGRAPHER" => Some("photographer_profiles"),
        "MAKEUP_ARTIST" => Some("makeup_artist_profiles"),
        "TUTOR" => Some("tutor_profiles"),
        "JOB_SEEKER" | "JOBSEEKER" => Some("job_seeker_profiles"),
        _ => None,
    };

    if let Some(tbl) = table_name {
        let user_role_profile_id = get_or_create_user_role_profile_id(
            &state.pool,
            auth.user_id,
            &role_key,
            role.id,
        )
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to create user role profile: {}", e)))?;

        let query = format!(
            r#"
            INSERT INTO {} (id, user_id, custom_data, status, updated_at)
            VALUES ($1, $2, $3, 'PENDING', NOW())
            ON CONFLICT (id) DO UPDATE SET
                custom_data = EXCLUDED.custom_data,
                status = 'PENDING',
                updated_at = NOW()
            "#,
            tbl
        );

        sqlx::query(&query)
            .bind(user_role_profile_id)
            .bind(auth.user_id)
            .bind(&progress)
            .execute(&state.pool)
            .await
            .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Profile creation error: {}", e)))?;
    } else if role_key == "COMPANY" {
        // Simple companies upsert (using basic fields if possible)
        sqlx::query(
            r#"
            INSERT INTO company_profiles (user_id, status, updated_at)
            VALUES ($1, 'PENDING', NOW())
            ON CONFLICT (user_id) DO UPDATE SET
                status = 'PENDING',
                updated_at = NOW()
            "#,
        )
        .bind(auth.user_id)
        .execute(&state.pool)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Company profile error: {}", e)))?;
    }

    // 3. Mark the user_role as PENDING (awaiting admin review of onboarding)
    sqlx::query(
        r#"
        UPDATE user_role_assignments
        SET status = 'PENDING'
        WHERE user_id = $1 AND role_id = $2
        "#,
    )
    .bind(auth.user_id)
    .bind(role.id)
    .execute(&state.pool)
    .await
    .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

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

/// GET /api/me/profile-status
#[allow(dead_code)]
async fn profile_status(
    auth: AuthUser,
    State(state): State<AppState>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    use db::models::user::UserRepository;

    let user = UserRepository::get_by_id(&state.pool, auth.user_id)
        .await
        .map_err(|_| (StatusCode::UNAUTHORIZED, "User not found".to_string()))?;

    let roles = UserRepository::get_user_role_keys(&state.pool, auth.user_id)
        .await
        .unwrap_or_default();

    let active_role = auth.claims.active_role.clone();
    let active_role_opt = if active_role.is_empty() { None } else { Some(active_role.clone()) };

    let onboarding_complete = if let Some(ref role_key) = active_role_opt {
        match RoleRepository::get_by_key(&state.pool, role_key).await {
            Ok(role) => OnboardingStateRepository::is_complete(&state.pool, auth.user_id, role.id)
                .await
                .unwrap_or(false),
            Err(_) => false,
        }
    } else {
        false
    };

    Ok((
        StatusCode::OK,
        Json(ProfileStatusResponse {
            onboarding_complete,
            active_role: active_role_opt,
            roles,
            email_verified: user.email_verified,
        }),
    ))
}

async fn get_or_create_user_role_profile_id(
    pool: &sqlx::PgPool,
    user_id: uuid::Uuid,
    role_key: &str,
    _role_id: uuid::Uuid,
) -> Result<uuid::Uuid, sqlx::Error> {
    if let Some(id) = sqlx::query_scalar::<_, uuid::Uuid>(
        r#"SELECT id FROM user_role_profiles WHERE user_id = $1 AND role_key = $2"#,
    )
    .bind(user_id)
    .bind(role_key)
    .fetch_optional(pool)
    .await?
    {
        return Ok(id);
    }

    sqlx::query_scalar::<_, uuid::Uuid>(
        r#"
        INSERT INTO user_role_profiles (user_id, role_key, status)
        VALUES ($1, $2, 'DRAFT')
        ON CONFLICT (user_id, role_key) DO UPDATE SET updated_at = NOW()
        RETURNING id
        "#,
    )
    .bind(user_id)
    .bind(role_key)
    .fetch_one(pool)
    .await
}
