use crate::AppState;
use axum::{
    extract::{Multipart, Query, State},
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post},
    Json, Router,
};
use contracts::auth_middleware::AuthUser;
use db::models::config::ConfigRepository;
use db::models::{role::RoleRepository, user::UserRepository, verification::VerificationRepository};
use serde::Deserialize;
use uuid::Uuid;

use super::role_meta::role_to_table;

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

pub fn router() -> Router<AppState> {
    Router::new()
        .route("/", get(get_profile).patch(save_profile))
        .route("/submit-for-verification", post(submit_for_verification))
        .route("/photo", post(upload_photo))
        .route("/documents/presign", post(presign_own_document))
}

pub fn me_router() -> Router<AppState> {
    Router::new()
        .route("/", get(get_me))
        .route("/verification-status", get(verification_status))
}

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

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

#[derive(Deserialize)]
pub struct SaveProfileInput {
    #[serde(rename = "roleKey", alias = "role_key")]
    pub role_key: String,
    pub profile_data: serde_json::Value,
}

#[derive(Deserialize)]
pub struct SubmitInput {
    #[serde(rename = "roleKey", alias = "role_key")]
    pub role_key: String,
    /// Optional: if provided, saves this data before submitting.
    /// If omitted, reads previously saved profile data from DB.
    pub profile_data: Option<serde_json::Value>,
}

// ── Helpers ───────────────────────────────────────────────────────────────────

fn is_dummy_account_email(email: &str) -> bool {
    email.ends_with("@demo.com")
        || email == "paymentgateway@demo.com"
        || email.contains("+dummy@")
        || email.starts_with("dummy+")
}

fn extract_documents(profile_data: &serde_json::Value) -> serde_json::Value {
    // Keep in sync with DOC_FIELDS in nxtgauge-frontend-solid/src/lib/profile-fields-config.ts —
    // each entry there is the source of truth for the document key a role's upload writes.
    let doc_keys = [
        "aadhar_doc",
        "registration_doc",
        "gst_doc",
        "address_proof",
        "portfolio_ownership_proof",
        "professional_certifications",
        "qualification_proof",
        "certification_doc",
        "fssai_license",
        "tax_document",
    ];
    let mut docs = vec![];
    for key in &doc_keys {
        if let Some(val) = profile_data.get(key) {
            if !val.is_null() {
                docs.push(serde_json::json!({
                    "type": key,
                    "value": val,
                    "status": "SUBMITTED"
                }));
            }
        }
    }
    serde_json::Value::Array(docs)
}

fn resolve_role_key(auth_role: &str, query_role: Option<String>) -> String {
    query_role
        .filter(|k| !k.is_empty())
        .unwrap_or_else(|| auth_role.to_string())
        .to_uppercase()
}

/// Field ids marked `lockAfterApproval: true` anywhere in a role's active
/// onboarding schema — these become permanently read-only once the profile
/// is APPROVED, per the "name/GST/Aadhar stay locked, phone/address stay
/// editable" verification design.
fn locked_field_ids(schema_json: &serde_json::Value) -> Vec<String> {
    schema_json
        .get("steps")
        .and_then(|s| s.as_array())
        .into_iter()
        .flatten()
        .filter_map(|step| step.get("fields")?.as_array())
        .flatten()
        .filter(|field| field.get("lockAfterApproval").and_then(|v| v.as_bool()).unwrap_or(false))
        .filter_map(|field| field.get("id")?.as_str().map(String::from))
        .collect()
}

/// Current verification status + a flat view of currently-stored profile
/// fields for this user/role, used only to check locked-field values before
/// a save is persisted. Best-effort: any lookup failure yields an
/// "unapproved" default, since there is nothing to protect if no profile row
/// exists yet.
async fn fetch_current_profile_state(
    pool: &sqlx::PgPool,
    user_id: Uuid,
    role_key: &str,
) -> (String, serde_json::Value) {
    if role_key == "COMPANY" {
        let row = sqlx::query(
            r#"SELECT company_name, gst_number, status FROM company_profiles WHERE user_id = $1"#,
        )
        .bind(user_id)
        .fetch_optional(pool)
        .await;
        return match row {
            Ok(Some(r)) => {
                use sqlx::Row;
                let company_name: Option<String> = r.try_get("company_name").ok();
                let gst_number: Option<String> = r.try_get("gst_number").ok();
                let status: String = r.try_get("status").unwrap_or_default();
                (status, serde_json::json!({ "company_name": company_name, "gst_number": gst_number }))
            }
            _ => ("NOT_STARTED".to_string(), serde_json::Value::Null),
        };
    }

    if role_key == "JOB_SEEKER" || role_key == "CUSTOMER" {
        let table = if role_key == "JOB_SEEKER" { "job_seeker_profiles" } else { "customer_profiles" };
        let query = format!(r#"SELECT custom_data, status FROM {} WHERE user_id = $1"#, table);
        let row = sqlx::query(&query).bind(user_id).fetch_optional(pool).await;
        return match row {
            Ok(Some(r)) => {
                use sqlx::Row;
                let custom_data: serde_json::Value =
                    r.try_get("custom_data").unwrap_or(serde_json::Value::Null);
                let status: String = r.try_get("status").unwrap_or_default();
                let profile_data = custom_data.get("basic_info").cloned().unwrap_or(serde_json::Value::Null);
                (status, profile_data)
            }
            _ => ("NOT_STARTED".to_string(), serde_json::Value::Null),
        };
    }

    let Some(table) = role_to_table(role_key) else {
        return ("NOT_STARTED".to_string(), serde_json::Value::Null);
    };
    let Ok(Some(user_role_profile_id)) = get_user_role_profile_id(pool, user_id, role_key).await else {
        return ("NOT_STARTED".to_string(), serde_json::Value::Null);
    };
    let query = format!(r#"SELECT custom_data, status FROM {} WHERE user_role_profile_id = $1"#, table);
    match sqlx::query(&query).bind(user_role_profile_id).fetch_optional(pool).await {
        Ok(Some(row)) => {
            use sqlx::Row;
            let profile_data: serde_json::Value =
                row.try_get("custom_data").unwrap_or(serde_json::Value::Null);
            let status: String = row.try_get("status").unwrap_or_default();
            (status, profile_data)
        }
        _ => ("NOT_STARTED".to_string(), serde_json::Value::Null),
    }
}

/// Rejects a save that changes any field marked `lockAfterApproval` once the
/// profile has already been APPROVED — the server-side backstop for the
/// "verified fields stay locked" guarantee (the UI already disables these
/// inputs, but this stops a direct API call from bypassing that).
async fn reject_locked_field_changes(
    pool: &sqlx::PgPool,
    user_id: Uuid,
    role_key: &str,
    incoming: &serde_json::Value,
) -> Result<(), (StatusCode, Json<serde_json::Value>)> {
    let schema = match ConfigRepository::get_active_onboarding_by_role_key(pool, role_key).await {
        Ok(config) => config.schema_json,
        Err(_) => return Ok(()), // no schema configured for this role - nothing to enforce
    };
    let locked = locked_field_ids(&schema);
    if locked.is_empty() {
        return Ok(());
    }

    let (status, current) = fetch_current_profile_state(pool, user_id, role_key).await;
    if status != "APPROVED" {
        return Ok(());
    }

    let mut violations = vec![];
    for field_id in &locked {
        if let Some(new_val) = incoming.get(field_id) {
            let current_val = current.get(field_id).cloned().unwrap_or(serde_json::Value::Null);
            if !current_val.is_null() && new_val != &current_val {
                violations.push(field_id.clone());
            }
        }
    }

    if violations.is_empty() {
        Ok(())
    } else {
        Err((
            StatusCode::CONFLICT,
            Json(serde_json::json!({
                "error": format!("These verified fields can no longer be changed: {}", violations.join(", ")),
                "code": "FIELD_LOCKED",
                "locked_fields": violations,
            })),
        ))
    }
}

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

/// GET /api/profile?roleKey=PHOTOGRAPHER
async fn get_profile(
    auth: AuthUser,
    State(state): State<AppState>,
    Query(q): Query<RoleKeyQuery>,
) -> impl IntoResponse {
    let role_key = resolve_role_key(&auth.claims.active_role, q.role_key);

    if role_key == "COMPANY" {
        let row = sqlx::query(
            r#"SELECT company_name, contact_email, contact_phone, website_url,
                      address_line1, city, state, postal_code, gst_number,
                      status, updated_at
               FROM company_profiles WHERE user_id = $1"#,
        )
        .bind(auth.user_id)
        .fetch_optional(&state.pool)
        .await;

        return match row {
            Ok(Some(r)) => {
                use sqlx::Row;
                let name: Option<String> = r.try_get("company_name").ok();
                let email: Option<String> = r.try_get("contact_email").ok();
                let phone: Option<String> = r.try_get("contact_phone").ok();
                let website: Option<String> = r.try_get("website_url").ok();
                let address: Option<String> = r.try_get("address_line1").ok();
                let city: Option<String> = r.try_get("city").ok();
                let state_val: Option<String> = r.try_get("state").ok();
                let postal: Option<String> = r.try_get("postal_code").ok();
                let gst: Option<String> = r.try_get("gst_number").ok();
                let status: String = r.try_get("status").unwrap_or_default();
                (
                    StatusCode::OK,
                    Json(serde_json::json!({
                        "role_key": role_key,
                        "profile_data": {
                            "company_name": name,
                            "company_email": email,
                            "company_phone": phone,
                            "website": website,
                            "address": address,
                            "location": city,
                            "state": state_val,
                            "pin_code": postal,
                            "gst_number": gst,
                        },
                        "verification_status": status,
                    })),
                )
                    .into_response()
            }
            Ok(None) => (
                StatusCode::OK,
                Json(serde_json::json!({
                    "role_key": role_key,
                    "profile_data": null,
                    "verification_status": "NOT_STARTED",
                })),
            )
                .into_response(),
            Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
        };
    }

    if role_key == "JOB_SEEKER" || role_key == "CUSTOMER" {
        let table = if role_key == "JOB_SEEKER" { "job_seeker_profiles" } else { "customer_profiles" };
        let query = format!(r#"SELECT custom_data, status FROM {} WHERE user_id = $1"#, table);
        return match sqlx::query(&query)
        .bind(auth.user_id)
        .fetch_optional(&state.pool)
        .await
        {
            Ok(Some(r)) => {
                use sqlx::Row;
                let custom_data: serde_json::Value =
                    r.try_get("custom_data").unwrap_or(serde_json::Value::Null);
                let status: String = r.try_get("status").unwrap_or_default();
                let profile_data = custom_data
                    .get("basic_info")
                    .cloned()
                    .unwrap_or(serde_json::Value::Null);
                (
                    StatusCode::OK,
                    Json(serde_json::json!({
                        "role_key": role_key,
                        "profile_data": profile_data,
                        "verification_status": status,
                    })),
                )
                    .into_response()
            }
            Ok(None) => (
                StatusCode::OK,
                Json(serde_json::json!({
                    "role_key": role_key,
                    "profile_data": null,
                    "verification_status": "NOT_STARTED",
                })),
            )
                .into_response(),
            Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
        };
    }

    let table = match role_to_table(&role_key) {
        Some(t) => t,
        None => {
            return (
                StatusCode::BAD_REQUEST,
                Json(serde_json::json!({ "error": format!("Unknown role: {}", role_key) })),
            )
                .into_response()
        }
    };

    let query = format!(
        r#"SELECT custom_data, status FROM {} WHERE user_role_profile_id = $1"#,
        table
    );

    let user_role_profile_id = match get_user_role_profile_id(&state.pool, auth.user_id, &role_key).await {
        Ok(Some(id)) => id,
        Ok(None) => {
            return (
                StatusCode::OK,
                Json(serde_json::json!({
                    "role_key": role_key,
                    "profile_data": null,
                    "verification_status": "NOT_STARTED",
                })),
            )
                .into_response();
        }
        Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    };

    match sqlx::query(&query)
        .bind(user_role_profile_id)
        .fetch_optional(&state.pool)
        .await
    {
        Ok(Some(row)) => {
            use sqlx::Row;
            let profile_data: serde_json::Value = row
                .try_get("custom_data")
                .unwrap_or(serde_json::Value::Null);
            let verification_status: String =
                row.try_get("status").unwrap_or_default();
            (
                StatusCode::OK,
                Json(serde_json::json!({
                    "role_key": role_key,
                    "profile_data": profile_data,
                    "verification_status": verification_status,
                })),
            )
                .into_response()
        }
        Ok(None) => (
            StatusCode::OK,
            Json(serde_json::json!({
                "role_key": role_key,
                "profile_data": null,
                "verification_status": "NOT_STARTED",
            })),
        )
            .into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

/// PATCH /api/profile
async fn save_profile(
    auth: AuthUser,
    State(state): State<AppState>,
    Json(input): Json<SaveProfileInput>,
) -> impl IntoResponse {
    let role_key = input.role_key.to_uppercase();

    if let Err((status, body)) =
        reject_locked_field_changes(&state.pool, auth.user_id, &role_key, &input.profile_data).await
    {
        return (status, body).into_response();
    }

    if role_key == "COMPANY" {
        let name = input
            .profile_data
            .get("company_name")
            .and_then(|v| v.as_str())
            .unwrap_or_default()
            .to_string();
        let email = input
            .profile_data
            .get("company_email")
            .and_then(|v| v.as_str())
            .unwrap_or_default()
            .to_string();
        let phone = input
            .profile_data
            .get("company_phone")
            .and_then(|v| v.as_str())
            .unwrap_or_default()
            .to_string();
        let website = input
            .profile_data
            .get("website")
            .and_then(|v| v.as_str())
            .unwrap_or_default()
            .to_string();
        let city = input
            .profile_data
            .get("location")
            .and_then(|v| v.as_str())
            .unwrap_or_default()
            .to_string();
        let state_val = input
            .profile_data
            .get("state")
            .and_then(|v| v.as_str())
            .unwrap_or_default()
            .to_string();
        let postal = input
            .profile_data
            .get("pin_code")
            .and_then(|v| v.as_str())
            .unwrap_or_default()
            .to_string();
        let address = input
            .profile_data
            .get("address")
            .and_then(|v| v.as_str())
            .unwrap_or_default()
            .to_string();
        let gst = input
            .profile_data
            .get("gst_number")
            .and_then(|v| v.as_str())
            .unwrap_or_default()
            .to_string();

        return match sqlx::query(
            r#"
            INSERT INTO company_profiles (
                user_id, company_name, contact_email, contact_phone, website_url,
                address_line1, city, state, postal_code, gst_number, status, updated_at
            )
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'DRAFT', NOW())
            ON CONFLICT (user_id) DO UPDATE SET
                company_name = EXCLUDED.company_name,
                contact_email = EXCLUDED.contact_email,
                contact_phone = EXCLUDED.contact_phone,
                website_url = EXCLUDED.website_url,
                address_line1 = EXCLUDED.address_line1,
                city = EXCLUDED.city,
                state = EXCLUDED.state,
                postal_code = EXCLUDED.postal_code,
                gst_number = EXCLUDED.gst_number,
                updated_at = NOW()
            "#,
        )
        .bind(auth.user_id)
        .bind(&name)
        .bind(&email)
        .bind(&phone)
        .bind(&website)
        .bind(&address)
        .bind(&city)
        .bind(&state_val)
        .bind(&postal)
        .bind(&gst)
        .execute(&state.pool)
        .await
        {
            Ok(_) => (
                StatusCode::OK,
                Json(serde_json::json!({ "saved": true, "role_key": role_key })),
            )
                .into_response(),
            Err(e) => {
                tracing::error!("save_profile(COMPANY) failed for user {}: {}", auth.user_id, e);
                (StatusCode::INTERNAL_SERVER_ERROR, format!("Database error: {}", e)).into_response()
            }
        };
    }

    if role_key == "JOB_SEEKER" || role_key == "CUSTOMER" {
        let table = if role_key == "JOB_SEEKER" { "job_seeker_profiles" } else { "customer_profiles" };

        // Both tables also store other blobs inside custom_data (job_seeker_portfolio
        // for JOB_SEEKER, written by the dedicated /api/jobseeker/profile/me endpoint),
        // so basic-tab fields are merged in under a nested "basic_info" key instead of
        // overwriting custom_data wholesale.
        let existing_custom_data: serde_json::Value = sqlx::query_scalar(
            &format!(r#"SELECT custom_data FROM {} WHERE user_id = $1"#, table),
        )
        .bind(auth.user_id)
        .fetch_optional(&state.pool)
        .await
        .ok()
        .flatten()
        .unwrap_or(serde_json::Value::Object(Default::default()));

        let mut merged = match existing_custom_data {
            serde_json::Value::Object(map) => map,
            _ => Default::default(),
        };
        merged.insert("basic_info".to_string(), input.profile_data.clone());
        let merged_custom_data = serde_json::Value::Object(merged);

        let query = format!(
            r#"
            INSERT INTO {table} (user_id, custom_data, status, updated_at)
            VALUES ($1, $2, 'DRAFT', NOW())
            ON CONFLICT (user_id) DO UPDATE SET
                custom_data = EXCLUDED.custom_data,
                updated_at = NOW()
            "#
        );
        return match sqlx::query(&query)
        .bind(auth.user_id)
        .bind(&merged_custom_data)
        .execute(&state.pool)
        .await
        {
            Ok(_) => (
                StatusCode::OK,
                Json(serde_json::json!({ "saved": true, "role_key": role_key })),
            )
                .into_response(),
            Err(e) => {
                tracing::error!("save_profile({}) failed for user {}: {}", role_key, auth.user_id, e);
                (StatusCode::INTERNAL_SERVER_ERROR, format!("Database error: {}", e)).into_response()
            }
        };
    }

    let table = match role_to_table(&role_key) {
        Some(t) => t,
        None => {
            return (
                StatusCode::BAD_REQUEST,
                Json(serde_json::json!({ "error": format!("Unknown role: {}", role_key) })),
            )
                .into_response()
        }
    };

    let user_role_profile_id = match get_or_create_user_role_profile_id(&state.pool, auth.user_id, &role_key).await {
        Ok(id) => id,
        Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    };

    // Shallow-merge onto existing custom_data instead of overwriting: RoleWizard
    // issues separate PATCH calls for the portfolio step and the basic/documents
    // steps, so a wholesale overwrite here would let the second call silently
    // wipe out whatever the first one just saved.
    let existing_custom_data: serde_json::Value = sqlx::query_scalar(
        &format!(r#"SELECT custom_data FROM {} WHERE user_role_profile_id = $1"#, table),
    )
    .bind(user_role_profile_id)
    .fetch_optional(&state.pool)
    .await
    .ok()
    .flatten()
    .unwrap_or(serde_json::Value::Null);

    let mut merged = match existing_custom_data {
        serde_json::Value::Object(map) => map,
        _ => Default::default(),
    };
    if let serde_json::Value::Object(incoming) = &input.profile_data {
        for (k, v) in incoming {
            merged.insert(k.clone(), v.clone());
        }
    }
    let merged_custom_data = serde_json::Value::Object(merged);

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

    match sqlx::query(&query)
        .bind(auth.user_id)
        .bind(user_role_profile_id)
        .bind(&merged_custom_data)
        .execute(&state.pool)
        .await
    {
        Ok(_) => (
            StatusCode::OK,
            Json(serde_json::json!({ "saved": true, "role_key": role_key })),
        )
            .into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

/// POST /api/profile/submit-for-verification
async fn submit_for_verification(
    auth: AuthUser,
    State(state): State<AppState>,
    Json(input): Json<SubmitInput>,
) -> impl IntoResponse {
    let role_key = input.role_key.to_uppercase();

    // Check if user is a demo account
    let is_demo = sqlx::query_scalar::<_, String>("SELECT email FROM users WHERE id = $1")
        .bind(auth.user_id)
        .fetch_one(&state.pool)
        .await
        .map(|email| is_dummy_account_email(&email))
        .unwrap_or(false);

    // For demo accounts: auto-approve verification
    if is_demo {
        tracing::info!(user_id = %auth.user_id, role_key = %role_key, "Demo account auto-approved for verification");
        
        // Update role assignment to APPROVED
        if let Ok(role) = RoleRepository::get_by_key(&state.pool, &role_key).await {
            sqlx::query(
                "UPDATE user_role_assignments SET status = 'APPROVED' WHERE user_id = $1 AND role_id = $2",
            )
            .bind(auth.user_id)
            .bind(role.id)
            .execute(&state.pool)
            .await
            .ok();
        }

        // Mark profile as VERIFIED
        set_profile_status(&state, auth.user_id, &role_key, "VERIFIED").await;

        // Create a verification record with APPROVED status
        let profile_data = input.profile_data.unwrap_or_else(|| {
            serde_json::json!({
                "company_name": "Payment Gateway Demo Company",
                "company_description": "Demo account for reviewing packages",
                "industry": "Technology",
                "location": "India"
            })
        });
        let documents = extract_documents(&profile_data);
        
        match VerificationRepository::create_approved(
            &state.pool,
            auth.user_id,
            &role_key,
            "PROFILE_VERIFICATION",
            profile_data,
            documents,
        )
        .await
        {
            Ok(v) => (
                StatusCode::CREATED,
                Json(serde_json::json!({
                    "verification_id": v.id,
                    "reference_number": v.reference_number,
                    "status": "APPROVED",
                    "message": "Your profile has been auto-approved for demo access."
                })),
            )
                .into_response(),
            Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
        }
    } else {
        // Regular verification flow for non-demo accounts
        // Guard: reject if an active verification already exists
        let existing: Result<Option<Uuid>, sqlx::Error> = sqlx::query_scalar(
            r#"
            SELECT id FROM verifications
            WHERE user_id = $1 AND role_key = $2
              AND status IN ('PENDING', 'UNDER_REVIEW', 'DOCUMENTS_REQUESTED', 'REVISION_REQUESTED')
            LIMIT 1
            "#,
        )
        .bind(auth.user_id)
        .bind(&role_key)
        .fetch_optional(&state.pool)
        .await;

        if existing.unwrap_or(None).is_some() {
            return (
                StatusCode::CONFLICT,
                Json(serde_json::json!({
                    "error": "A verification is already in progress for this role. Please wait for it to be reviewed."
                })),
            )
                .into_response();
        }

        // Fetch saved profile data or use submitted data
        let profile_data = match input.profile_data {
            Some(data) => data,
            None => fetch_saved_profile(&state, auth.user_id, &role_key).await,
        };

        let documents = extract_documents(&profile_data);

        // Mark profile as PENDING in role-specific table
        set_profile_status(&state, auth.user_id, &role_key, "PENDING").await;

        // NOTE: we intentionally do NOT change user_role_assignments.status here.
        // Verification status is tracked in the verifications table. Downgrading
        // the role assignment to PENDING would block the user from logging in
        // while their verification is under review, which is broken UX.

        // Create verification record — appears in admin Verification Management
        match VerificationRepository::create(
            &state.pool,
            auth.user_id,
            &role_key,
            "PROFILE_VERIFICATION",
            "MEDIUM",
            profile_data,
            documents,
        )
        .await
        {
            Ok(v) => (
                StatusCode::CREATED,
                Json(serde_json::json!({
                    "verification_id": v.id,
                    "reference_number": v.reference_number,
                    "status": v.status,
                    "message": "Your profile has been submitted for verification. We will notify you once it has been reviewed."
                })),
            )
                .into_response(),
            Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
        }
    }
}

#[derive(Deserialize)]
pub struct PresignOwnDocumentPayload {
    pub url: String,
}

/// POST /api/profile/documents/presign
/// Mints a short-lived signed URL for a document the caller themselves submitted
/// (found via any of their own verification submissions), so their own dashboard
/// never has to render or link to the permanent storage URL directly.
async fn presign_own_document(
    auth: AuthUser,
    State(state): State<AppState>,
    Json(payload): Json<PresignOwnDocumentPayload>,
) -> impl IntoResponse {
    let owns: Option<Uuid> = sqlx::query_scalar(
        r#"
        SELECT id FROM verifications
        WHERE user_id = $1
          AND (payload::text LIKE '%' || $2 || '%' OR documents::text LIKE '%' || $2 || '%')
        LIMIT 1
        "#,
    )
    .bind(auth.user_id)
    .bind(&payload.url)
    .fetch_optional(&state.pool)
    .await
    .unwrap_or(None);

    if owns.is_none() {
        return (StatusCode::FORBIDDEN, Json(serde_json::json!({ "error": "Document not found for this account" }))).into_response();
    }

    match state.storage.presign(&payload.url, std::time::Duration::from_secs(300)).await {
        Ok(url) => (StatusCode::OK, Json(serde_json::json!({ "url": url, "expires_in": 300 }))).into_response(),
        Err(e) => {
            tracing::error!("Failed to presign own document URL: {:?}", e);
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to generate view URL" }))).into_response()
        }
    }
}

/// GET /api/me/verification-status?roleKey=PHOTOGRAPHER
pub async fn verification_status(
    auth: AuthUser,
    State(state): State<AppState>,
    Query(q): Query<RoleKeyQuery>,
) -> impl IntoResponse {
    let role_key = resolve_role_key(&auth.claims.active_role, q.role_key);

    let row = sqlx::query(
        r#"
        SELECT id, reference_number, status, notes, rejection_reason, updated_at, documents
        FROM verifications
        WHERE user_id = $1 AND role_key = $2
        ORDER BY created_at DESC
        LIMIT 1
        "#,
    )
    .bind(auth.user_id)
    .bind(&role_key)
    .fetch_optional(&state.pool)
    .await;

    match row {
        Ok(Some(r)) => {
            use sqlx::Row;
            let id: Uuid = r.try_get("id").unwrap_or(Uuid::nil());
            let reference_number: String = r.try_get("reference_number").unwrap_or_default();
            let raw_status: String = r.try_get("status").unwrap_or_default();
            let notes: Option<String> = r.try_get("notes").ok().flatten();
            let rejection_reason: Option<String> = r.try_get("rejection_reason").ok().flatten();
            let updated_at: Option<chrono::DateTime<chrono::Utc>> =
                r.try_get("updated_at").ok();
            let documents: serde_json::Value = r
                .try_get("documents")
                .unwrap_or(serde_json::Value::Array(vec![]));

            let profile_status = fetch_current_profile_state(&state.pool, auth.user_id, &role_key).await.0;
            // Use the verification record's status as the source of truth.
            // role_assignment_approved is NOT used here — keeping the role APPROVED
            // so users can log in while under review means it cannot drive this check.
            let status = if raw_status.eq_ignore_ascii_case("COMPLETED")
                || profile_status.eq_ignore_ascii_case("APPROVED")
            {
                "APPROVED".to_string()
            } else if raw_status.eq_ignore_ascii_case("FINAL_REJECTED") {
                "REJECTED".to_string()
            } else {
                raw_status
            };

            (
                StatusCode::OK,
                Json(serde_json::json!({
                    "has_verification": true,
                    "verification_id": id,
                    "reference_number": reference_number,
                    "status": status,
                    "document_request": notes,
                    "rejection_reason": rejection_reason,
                    "updated_at": updated_at,
                    "documents": documents,
                })),
            )
                .into_response()
        }
        Ok(None) => (
            StatusCode::OK,
            Json(serde_json::json!({
                "has_verification": false,
                "status": "NOT_SUBMITTED",
            })),
        )
            .into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

// ── Internal helpers ──────────────────────────────────────────────────────────

async fn fetch_saved_profile(
    state: &AppState,
    user_id: Uuid,
    role_key: &str,
) -> serde_json::Value {
    if role_key == "COMPANY" {
        return match sqlx::query(
            r#"SELECT company_name, contact_email, contact_phone, website_url,
                      address_line1, city, state, postal_code, gst_number
               FROM company_profiles WHERE user_id = $1"#,
        )
        .bind(user_id)
        .fetch_optional(&state.pool)
        .await
        {
            Ok(Some(r)) => {
                use sqlx::Row;
                let name: Option<String> = r.try_get("company_name").ok();
                let email: Option<String> = r.try_get("contact_email").ok();
                let phone: Option<String> = r.try_get("contact_phone").ok();
                let website: Option<String> = r.try_get("website_url").ok();
                let address: Option<String> = r.try_get("address_line1").ok();
                let city: Option<String> = r.try_get("city").ok();
                let state_val: Option<String> = r.try_get("state").ok();
                let postal: Option<String> = r.try_get("postal_code").ok();
                let gst: Option<String> = r.try_get("gst_number").ok();
                serde_json::json!({
                    "company_name": name,
                    "company_email": email,
                    "company_phone": phone,
                    "website": website,
                    "address": address,
                    "location": city,
                    "state": state_val,
                    "pin_code": postal,
                    "gst_number": gst,
                })
            }
            _ => serde_json::Value::Object(Default::default()),
        };
    }

    if role_key == "JOB_SEEKER" || role_key == "CUSTOMER" {
        let table = if role_key == "JOB_SEEKER" { "job_seeker_profiles" } else { "customer_profiles" };
        let custom_data: serde_json::Value = sqlx::query_scalar(
            &format!(r#"SELECT custom_data FROM {} WHERE user_id = $1"#, table),
        )
        .bind(user_id)
        .fetch_optional(&state.pool)
        .await
        .ok()
        .flatten()
        .unwrap_or(serde_json::Value::Object(Default::default()));

        return custom_data
            .get("basic_info")
            .cloned()
            .unwrap_or(serde_json::Value::Object(Default::default()));
    }

    if let Some(urp_id) = get_user_role_profile_id(&state.pool, user_id, role_key).await.ok().flatten() {
        return fetch_saved_profile_by_urp_id(state, urp_id, role_key).await;
    }

    serde_json::Value::Object(Default::default())
}

async fn set_profile_status(state: &AppState, user_id: Uuid, role_key: &str, status: &str) {
    if role_key == "COMPANY" {
        sqlx::query(
            r#"UPDATE company_profiles SET status = $1, updated_at = NOW() WHERE user_id = $2"#,
        )
        .bind(status)
        .bind(user_id)
        .execute(&state.pool)
        .await
        .ok();
        return;
    }

    if role_key == "JOB_SEEKER" || role_key == "CUSTOMER" {
        let table = if role_key == "JOB_SEEKER" { "job_seeker_profiles" } else { "customer_profiles" };
        sqlx::query(
            &format!(r#"UPDATE {} SET status = $1, updated_at = NOW() WHERE user_id = $2"#, table),
        )
        .bind(status)
        .bind(user_id)
        .execute(&state.pool)
        .await
        .ok();
        return;
    }

    let user_role_profile_id = match get_user_role_profile_id(&state.pool, user_id, role_key).await {
        Ok(Some(id)) => id,
        Ok(None) => return,
        Err(_) => return,
    };

    if let Some(table) = role_to_table(role_key) {
        let q = format!(
            "UPDATE {} SET status = $1, updated_at = NOW() WHERE user_role_profile_id = $2",
            table
        );
        sqlx::query(&q)
            .bind(status)
            .bind(user_role_profile_id)
            .execute(&state.pool)
            .await
            .ok();
    }
}

async fn get_user_role_profile_id(
    pool: &sqlx::PgPool,
    user_id: Uuid,
    role_key: &str,
) -> Result<Option<Uuid>, sqlx::Error> {
    sqlx::query_scalar::<_, 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
}

async fn get_or_create_user_role_profile_id(
    pool: &sqlx::PgPool,
    user_id: Uuid,
    role_key: &str,
) -> Result<Uuid, sqlx::Error> {
    if let Some(id) = get_user_role_profile_id(pool, user_id, role_key).await? {
        return Ok(id);
    }

    let _role = RoleRepository::get_by_key(pool, role_key).await?;

    sqlx::query_scalar::<_, 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
}

async fn fetch_saved_profile_by_urp_id(
    state: &AppState,
    user_role_profile_id: Uuid,
    role_key: &str,
) -> serde_json::Value {
    if let Some(table) = role_to_table(role_key) {
        let q = format!(r#"SELECT custom_data FROM {} WHERE user_role_profile_id = $1"#, table);
        if let Ok(Some(row)) = sqlx::query(&q)
            .bind(user_role_profile_id)
            .fetch_optional(&state.pool)
            .await
        {
            use sqlx::Row;
            return row
                .try_get::<serde_json::Value, _>("custom_data")
                .unwrap_or(serde_json::Value::Object(Default::default()));
        }
    }
    serde_json::Value::Object(Default::default())
}

/// POST /api/profile/photo — multipart upload, updates users.avatar_url
async fn upload_photo(
    auth: AuthUser,
    State(state): State<AppState>,
    mut multipart: Multipart,
) -> impl IntoResponse {
    use bytes::Bytes;

    let mut file_bytes: Option<Bytes> = None;
    let mut content_type = String::from("image/jpeg");
    let mut extension = String::from("jpg");

    while let Ok(Some(field)) = multipart.next_field().await {
        let ct = field
            .content_type()
            .map(|s| s.to_string())
            .unwrap_or_else(|| "image/jpeg".to_string());

        let ext = match ct.as_str() {
            "image/png" => "png",
            "image/gif" => "gif",
            "image/webp" => "webp",
            _ => "jpg",
        };

        if let Ok(data) = field.bytes().await {
            if !data.is_empty() {
                content_type = ct;
                extension = ext.to_string();
                file_bytes = Some(data);
                break;
            }
        }
    }

    let bytes = match file_bytes {
        Some(b) => b,
        None => {
            return (
                StatusCode::BAD_REQUEST,
                Json(serde_json::json!({ "error": "No file received" })),
            )
                .into_response()
        }
    };

    let url = match state
        .storage
        .upload("photos", &extension, bytes, &content_type)
        .await
    {
        Ok(u) => u,
        Err(e) => {
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({ "error": format!("Upload failed: {e}") })),
            )
                .into_response()
        }
    };

    let res = sqlx::query(
        "UPDATE users SET avatar_url = $1, updated_at = NOW() WHERE id = $2",
    )
    .bind(&url)
    .bind(auth.user_id)
    .execute(&state.pool)
    .await;

    match res {
        Ok(_) => (
            StatusCode::OK,
            Json(serde_json::json!({ "avatar_url": url })),
        )
            .into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

/// GET /api/me — returns the authenticated user's basic info
pub async fn get_me(
    auth: AuthUser,
    State(state): State<AppState>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    let user = UserRepository::get_by_id(&state.pool, auth.user_id)
        .await
        .map_err(|_| (StatusCode::UNAUTHORIZED, "User not found".to_string()))?;

    let avatar_url: Option<String> = sqlx::query_scalar(
        "SELECT avatar_url FROM users WHERE id = $1",
    )
    .bind(auth.user_id)
    .fetch_optional(&state.pool)
    .await
    .ok()
    .flatten();

    Ok(Json(serde_json::json!({
        "id": user.id,
        "email": user.email,
        "firstName": user.first_name,
        "lastName": user.last_name,
        "activeRole": auth.claims.active_role,
        "emailVerified": user.email_verified,
        "avatar_url": avatar_url,
    })))
}
