use crate::error::AppError;
use crate::models::graylog::{
    GraylogCredentials, GraylogCredentialsSafe, GraylogDetection, GraylogSubject,
};
use crate::services::crypto;
use crate::services::graylog_client::GraylogClient;
use crate::services::graylog_poller;
use crate::AppState;
use tauri::State;

fn validate_input(
    base_url: &str,
    analyst_agent_id: &str,
    developer_agent_id: &str,
    polling_interval_minutes: i32,
    lookback_minutes: i32,
    score_threshold: i32,
) -> Result<(String, String, String), AppError> {
    let base_url = base_url.trim().to_string();
    let analyst_agent_id = analyst_agent_id.trim().to_string();
    let developer_agent_id = developer_agent_id.trim().to_string();

    if base_url.is_empty() {
        return Err(AppError::from("Graylog URL is required".to_string()));
    }
    if analyst_agent_id.is_empty() {
        return Err(AppError::from("Analyst agent is required".to_string()));
    }
    if developer_agent_id.is_empty() {
        return Err(AppError::from("Developer agent is required".to_string()));
    }
    if polling_interval_minutes <= 0 {
        return Err(AppError::from(
            "Polling interval must be strictly positive".to_string(),
        ));
    }
    if lookback_minutes <= 0 {
        return Err(AppError::from(
            "Lookback window must be strictly positive".to_string(),
        ));
    }
    if !(1..=100).contains(&score_threshold) {
        return Err(AppError::from(
            "Score threshold must be between 1 and 100".to_string(),
        ));
    }

    Ok((base_url, analyst_agent_id, developer_agent_id))
}

#[tauri::command]
#[allow(clippy::too_many_arguments)]
pub fn set_graylog_credentials(
    state: State<'_, AppState>,
    project_id: String,
    base_url: String,
    api_token: String,
    analyst_agent_id: String,
    developer_agent_id: String,
    stream_id: Option<String>,
    query_filter: String,
    polling_interval_minutes: i32,
    lookback_minutes: i32,
    score_threshold: i32,
) -> Result<GraylogCredentialsSafe, AppError> {
    let (base_url, analyst_agent_id, developer_agent_id) = validate_input(
        &base_url,
        &analyst_agent_id,
        &developer_agent_id,
        polling_interval_minutes,
        lookback_minutes,
        score_threshold,
    )?;

    let db = state
        .db
        .lock()
        .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;

    let token_encrypted = if api_token.trim().is_empty() {
        match GraylogCredentials::get_by_project(&db, &project_id)? {
            Some(existing) => existing.api_token_encrypted,
            None => {
                return Err(AppError::from(
                    "API token is required for initial setup".to_string(),
                ));
            }
        }
    } else {
        crypto::encrypt(&state.encryption_key, api_token.trim()).map_err(AppError::from)?
    };

    let creds = GraylogCredentials::upsert_for_project(
        &db,
        &project_id,
        &base_url,
        &token_encrypted,
        &analyst_agent_id,
        &developer_agent_id,
        stream_id.as_deref().map(str::trim).filter(|v| !v.is_empty()),
        query_filter.trim(),
        polling_interval_minutes,
        lookback_minutes,
        score_threshold,
    )?;

    Ok(creds.to_safe())
}

#[tauri::command]
pub fn get_graylog_credentials(
    state: State<'_, AppState>,
    project_id: String,
) -> Result<Option<GraylogCredentialsSafe>, AppError> {
    let db = state
        .db
        .lock()
        .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;

    Ok(GraylogCredentials::get_by_project(&db, &project_id)?.map(|creds| creds.to_safe()))
}

#[tauri::command]
pub fn delete_graylog_credentials(
    state: State<'_, AppState>,
    project_id: String,
) -> Result<(), AppError> {
    let db = state
        .db
        .lock()
        .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;

    GraylogCredentials::delete_for_project(&db, &project_id)?;
    Ok(())
}

#[tauri::command]
pub async fn test_graylog_connection(
    state: State<'_, AppState>,
    project_id: String,
) -> Result<String, AppError> {
    let (base_url, token) = {
        let db = state
            .db
            .lock()
            .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;

        let creds = GraylogCredentials::get_by_project(&db, &project_id)?
            .ok_or_else(|| AppError::from("No Graylog credentials configured".to_string()))?;

        let token = crypto::decrypt(&state.encryption_key, &creds.api_token_encrypted)
            .map_err(AppError::from)?;

        (creds.base_url, token)
    };

    let client = GraylogClient::new(&state.http_client, &base_url, &token);
    client.test_connection().await.map_err(AppError::from)?;

    Ok("Connection successful".to_string())
}

#[tauri::command]
pub async fn manual_graylog_poll(
    state: State<'_, AppState>,
    app_handle: tauri::AppHandle,
    project_id: String,
) -> Result<i32, AppError> {
    graylog_poller::poll_project_once(
        &state.db,
        &state.encryption_key,
        &state.http_client,
        &app_handle,
        &project_id,
        &state.activity_state,
    )
    .await
    .map_err(AppError::from)
}

#[tauri::command]
pub fn list_graylog_subjects(
    state: State<'_, AppState>,
    project_id: String,
) -> Result<Vec<GraylogSubject>, AppError> {
    let db = state
        .db
        .lock()
        .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;

    Ok(GraylogSubject::list_by_project(&db, &project_id)?)
}

#[tauri::command]
pub fn list_graylog_detections(
    state: State<'_, AppState>,
    project_id: String,
    subject_id: Option<String>,
) -> Result<Vec<GraylogDetection>, AppError> {
    let db = state
        .db
        .lock()
        .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;

    Ok(GraylogDetection::list_by_project(
        &db,
        &project_id,
        subject_id.as_deref(),
    )?)
}
