use crate::ai::{credits, litellm, model_router, orchestrator, plans, usage};
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::ai::{
    AiAutoApplyLogRepository, AiAutoApplySettingsRepository, AiAutoRequestLogRepository,
    AiAutoRequestSettingsRepository,
};
use serde::Deserialize;
use serde_json::Value;

#[derive(Debug, Deserialize)]
struct AutoApplySettingsBody {
    #[serde(default)]
    is_enabled: Option<bool>,
    #[serde(default)]
    preferred_titles: Option<Value>,
    #[serde(default)]
    preferred_locations: Option<Value>,
    #[serde(default)]
    preferred_job_types: Option<Value>,
    #[serde(default)]
    preferred_work_modes: Option<Value>,
    #[serde(default)]
    preferred_skills: Option<Value>,
    #[serde(default)]
    min_salary: Option<i32>,
    #[serde(default)]
    max_salary: Option<i32>,
    #[serde(default)]
    max_applications_per_day: Option<i32>,
    #[serde(default)]
    require_user_approval: Option<bool>,
}

#[derive(Debug, Deserialize)]
struct AutoRequestSettingsBody {
    #[serde(default)]
    professional_role_code: Option<String>,
    #[serde(default)]
    is_enabled: Option<bool>,
    #[serde(default)]
    preferred_categories: Option<Value>,
    #[serde(default)]
    preferred_locations: Option<Value>,
    #[serde(default)]
    preferred_requirement_types: Option<Value>,
    #[serde(default)]
    min_budget: Option<i32>,
    #[serde(default)]
    max_budget: Option<i32>,
    #[serde(default)]
    max_requests_per_day: Option<i32>,
    #[serde(default)]
    require_user_approval: Option<bool>,
}

#[derive(Debug, Deserialize)]
struct AutoSuggestBody {
    text: String,
    #[serde(default)]
    model: Option<String>,
}

#[derive(Debug, Deserialize)]
struct LogQueryParams {
    #[serde(default = "default_limit")]
    limit: i64,
    #[serde(default)]
    offset: i64,
}

fn default_limit() -> i64 {
    50
}

pub fn ai_auto_router() -> Router<AppState> {
    Router::new()
        .route("/job-seeker/auto-apply/settings", get(get_auto_apply_settings).post(update_auto_apply_settings))
        .route("/job-seeker/auto-apply/suggest", post(auto_apply_suggest))
        .route("/job-seeker/auto-apply/logs", get(get_auto_apply_logs))
        .route("/professional/auto-request/settings", get(get_auto_request_settings).post(update_auto_request_settings))
        .route("/professional/auto-request/suggest", post(auto_request_suggest))
        .route("/professional/auto-request/logs", get(get_auto_request_logs))
}

async fn get_auto_apply_settings(
    State(state): State<AppState>,
    auth: AuthUser,
) -> impl IntoResponse {
    match AiAutoApplySettingsRepository::get_by_user_id(&state.pool, auth.user_id).await {
        Ok(Some(s)) => (StatusCode::OK, Json(serde_json::json!({ "settings": s }))).into_response(),
        Ok(None) => (
            StatusCode::OK,
            Json(serde_json::json!({ "settings": Value::Null })),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to fetch auto-apply settings: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({ "error": "Database error" })),
            )
                .into_response()
        }
    }
}

async fn update_auto_apply_settings(
    State(state): State<AppState>,
    auth: AuthUser,
    Json(body): Json<AutoApplySettingsBody>,
) -> impl IntoResponse {
    let current = AiAutoApplySettingsRepository::get_by_user_id(&state.pool, auth.user_id).await.ok().flatten();

    let settings = AiAutoApplySettingsRepository::upsert(
        &state.pool,
        auth.user_id,
        body.is_enabled.unwrap_or_else(|| current.as_ref().map(|s| s.is_enabled).unwrap_or(false)),
        body.preferred_titles.or_else(|| current.as_ref().and_then(|s| s.preferred_titles.clone())),
        body.preferred_locations.or_else(|| current.as_ref().and_then(|s| s.preferred_locations.clone())),
        body.preferred_job_types.or_else(|| current.as_ref().and_then(|s| s.preferred_job_types.clone())),
        body.preferred_work_modes.or_else(|| current.as_ref().and_then(|s| s.preferred_work_modes.clone())),
        body.preferred_skills.or_else(|| current.as_ref().and_then(|s| s.preferred_skills.clone())),
        body.min_salary.or_else(|| current.as_ref().and_then(|s| s.min_salary)),
        body.max_salary.or_else(|| current.as_ref().and_then(|s| s.max_salary)),
        body.max_applications_per_day.unwrap_or_else(|| current.as_ref().map(|s| s.max_applications_per_day).unwrap_or(3)),
        body.require_user_approval.unwrap_or_else(|| current.as_ref().map(|s| s.require_user_approval).unwrap_or(true)),
    )
    .await;

    match settings {
        Ok(s) => (StatusCode::OK, Json(serde_json::json!({ "settings": s }))).into_response(),
        Err(e) => {
            tracing::error!("Failed to update auto-apply settings: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({ "error": "Database error" })),
            )
                .into_response()
        }
    }
}

async fn auto_apply_suggest(
    State(state): State<AppState>,
    auth: AuthUser,
    Json(body): Json<AutoSuggestBody>,
) -> impl IntoResponse {
    ai_suggest(
        &state,
        &auth,
        "auto_apply_suggest",
        &body.text,
        body.model.as_deref(),
        "You are a job matching assistant. Given the candidate preferences below, suggest which jobs they should apply to and why. Keep it concise.",
    )
    .await
}

async fn get_auto_request_settings(
    State(state): State<AppState>,
    auth: AuthUser,
) -> impl IntoResponse {
    match AiAutoRequestSettingsRepository::get_by_user_id(&state.pool, auth.user_id).await {
        Ok(Some(s)) => (
            StatusCode::OK,
            Json(serde_json::json!({ "settings": s })),
        )
            .into_response(),
        Ok(None) => (
            StatusCode::OK,
            Json(serde_json::json!({ "settings": Value::Null })),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to fetch auto-request settings: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({ "error": "Database error" })),
            )
                .into_response()
        }
    }
}

async fn update_auto_request_settings(
    State(state): State<AppState>,
    auth: AuthUser,
    Json(body): Json<AutoRequestSettingsBody>,
) -> impl IntoResponse {
    let current = AiAutoRequestSettingsRepository::get_by_user_id(&state.pool, auth.user_id)
        .await
        .ok()
        .flatten();

    let professional_role_code = body
        .professional_role_code
        .or_else(|| current.as_ref().map(|s| s.professional_role_code.clone()))
        .unwrap_or_default();

    let settings = AiAutoRequestSettingsRepository::upsert(
        &state.pool,
        auth.user_id,
        &professional_role_code,
        body.is_enabled.unwrap_or_else(|| current.as_ref().map(|s| s.is_enabled).unwrap_or(false)),
        body.preferred_categories.or_else(|| current.as_ref().and_then(|s| s.preferred_categories.clone())),
        body.preferred_locations.or_else(|| current.as_ref().and_then(|s| s.preferred_locations.clone())),
        body.preferred_requirement_types.or_else(|| current.as_ref().and_then(|s| s.preferred_requirement_types.clone())),
        body.min_budget.or_else(|| current.as_ref().and_then(|s| s.min_budget)),
        body.max_budget.or_else(|| current.as_ref().and_then(|s| s.max_budget)),
        body.max_requests_per_day.unwrap_or_else(|| current.as_ref().map(|s| s.max_requests_per_day).unwrap_or(3)),
        body.require_user_approval.unwrap_or_else(|| current.as_ref().map(|s| s.require_user_approval).unwrap_or(true)),
    )
    .await;

    match settings {
        Ok(s) => (StatusCode::OK, Json(serde_json::json!({ "settings": s }))).into_response(),
        Err(e) => {
            tracing::error!("Failed to update auto-request settings: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({ "error": "Database error" })),
            )
                .into_response()
        }
    }
}

async fn auto_request_suggest(
    State(state): State<AppState>,
    auth: AuthUser,
    Json(body): Json<AutoSuggestBody>,
) -> impl IntoResponse {
    ai_suggest(
        &state,
        &auth,
        "auto_request_suggest",
        &body.text,
        body.model.as_deref(),
        "You are a lead/requirement matching assistant. Given the professional's preferences below, suggest which requirements they should respond to and why. Keep it concise.",
    )
    .await
}

async fn ai_suggest(
    state: &AppState,
    auth: &AuthUser,
    feature_code: &str,
    text: &str,
    requested_model: Option<&str>,
    system_prompt: &str,
) -> impl IntoResponse {
    let role_code = auth.claims.active_role.clone();

    let (sub, plan) = match plans::ensure_free_subscription(
        &state.pool,
        auth.user_id,
        Some(&role_code),
    )
    .await
    {
        Ok(sp) => sp,
        Err(e) => return (e.status_code(), Json(e.error_body())).into_response(),
    };

    if !plans::is_feature_allowed(&plan, feature_code) {
        let e = plans::PlanError::FeatureNotAllowed {
            feature: feature_code.to_string(),
        };
        return (e.status_code(), Json(e.error_body())).into_response();
    }

    let feature_cost = match credits::get_feature_cost(&state.pool, feature_code).await {
        Ok(c) => c,
        Err(e) => return (e.status_code(), Json(e.error_body())).into_response(),
    };

    let model_alias = match model_router::resolve_model(&feature_cost, &plan, requested_model) {
        Ok(m) => m,
        Err(e) => return (e.status_code(), Json(e.error_body())).into_response(),
    };

    if credits::remaining_credits(&sub) < feature_cost.credit_cost {
        let e = credits::CreditError::InsufficientCredits;
        return (e.status_code(), Json(e.error_body())).into_response();
    }

    let client = match litellm::LiteLlmClient::new() {
        Ok(c) => c,
        Err(e) => {
            tracing::error!("LiteLLM client creation failed: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({ "error": "AI service unavailable" })),
            )
                .into_response();
        }
    };

    let full_prompt = format!("{}\n\nPreferences/Context: {}\n\nSuggestions:", system_prompt, text);
    let (response_text, usage, request_id) = match client
        .chat_completion_text(&model_alias, Some(&orchestrator::effective_system_prompt(None)), &full_prompt, None)
        .await
    {
        Ok(r) => r,
        Err(e) => {
            tracing::error!("LiteLLM call failed: {}", e);
            return (
                StatusCode::BAD_GATEWAY,
                Json(serde_json::json!({ "error": "AI model call failed" })),
            )
                .into_response();
        }
    };

    if let Err(e) = credits::charge_feature(
        &state.pool,
        auth.user_id,
        Some(&role_code),
        feature_code,
        Some(&model_alias),
    )
    .await
    {
        tracing::error!(
            "Failed to charge AI feature {} for user {}: {}",
            feature_code,
            auth.user_id,
            e
        );
    }

    usage::log_usage(
        &state.pool,
        auth.user_id,
        Some(&role_code),
        feature_code,
        &model_alias,
        feature_cost.credit_cost,
        usage.prompt_tokens,
        usage.completion_tokens,
        usage.total_tokens,
        "success",
        request_id.as_deref(),
        None,
    )
    .await;

    let remaining_credits = credits::remaining_credits(&sub) - feature_cost.credit_cost;
    let remaining_daily = credits::remaining_daily_actions(&sub, &plan) - 1;

    (
        StatusCode::OK,
        Json(serde_json::json!({
            "suggestions": response_text,
            "model": model_alias,
            "credits_charged": feature_cost.credit_cost,
            "remaining_credits": remaining_credits,
            "remaining_daily_actions": remaining_daily.max(0),
            "request_id": request_id,
        })),
    )
        .into_response()
}

async fn get_auto_apply_logs(
    State(state): State<AppState>,
    auth: AuthUser,
    Query(params): Query<LogQueryParams>,
) -> impl IntoResponse {
    match AiAutoApplyLogRepository::list_by_user(&state.pool, auth.user_id, params.limit, params.offset).await {
        Ok(logs) => (StatusCode::OK, Json(serde_json::json!({ "logs": logs }))).into_response(),
        Err(e) => {
            tracing::error!("Failed to fetch auto-apply logs: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({ "error": "Database error" })),
            )
                .into_response()
        }
    }
}

async fn get_auto_request_logs(
    State(state): State<AppState>,
    auth: AuthUser,
    Query(params): Query<LogQueryParams>,
) -> impl IntoResponse {
    match AiAutoRequestLogRepository::list_by_user(&state.pool, auth.user_id, params.limit, params.offset).await {
        Ok(logs) => (StatusCode::OK, Json(serde_json::json!({ "logs": logs }))).into_response(),
        Err(e) => {
            tracing::error!("Failed to fetch auto-request logs: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({ "error": "Database error" })),
            )
                .into_response()
        }
    }
}
