use std::collections::HashMap;
use std::time::Instant;
use serde::{Serialize, Deserialize};
use tauri::State;
use tokio::sync::{Mutex, oneshot};

#[derive(Serialize, Deserialize)]
struct HttpResponse {
    status: u16,
    headers: HashMap<String, String>,
    body: String,
    time_elapsed: u128, // milliseconds
    headers_size: usize,
    body_size: usize,
    total_size: usize,
}

#[derive(Serialize, Deserialize)]
struct AppError {
    code: String,
    message: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct BasicAuth {
    username: String,
    password: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct BearerAuth {
    token: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct ApiKeyAuth {
    key: String,
    value: String,
    #[serde(rename = "addTo")]
    add_to: String, // "header" or "query"
}

#[derive(Serialize, Deserialize, Debug)]
struct AuthConfig {
    #[serde(rename = "type")]
    auth_type: String, // "none", "basic", "bearer", "api_key"
    basic: BasicAuth,
    bearer: BearerAuth,
    #[serde(rename = "apiKey")]
    api_key: ApiKeyAuth,
}

struct AppState {
    client: reqwest::Client,
    pending_requests: Mutex<HashMap<String, oneshot::Sender<()>>>,
}

impl AppError {
    fn new(code: &str, message: impl Into<String>) -> Self {
        Self {
            code: code.to_string(),
            message: message.into(),
        }
    }
}

fn map_reqwest_error(error: reqwest::Error) -> AppError {
    if error.is_timeout() {
        return AppError::new("timeout", "The request timed out after 30 seconds.");
    }

    if error.is_connect() {
        return AppError::new("connect", format!("Failed to connect: {error}"));
    }

    if error.is_decode() {
        return AppError::new("decode", format!("Failed to decode response body: {error}"));
    }

    if error.is_request() {
        return AppError::new("request", format!("Failed to build or send the request: {error}"));
    }

    AppError::new("network", format!("Request failed: {error}"))
}

async fn perform_request(
    request_builder: reqwest::RequestBuilder,
) -> Result<HttpResponse, AppError> {
    let start_time = Instant::now();
    let response = request_builder.send().await.map_err(map_reqwest_error)?;
    let time_elapsed = start_time.elapsed().as_millis();

    let status = response.status().as_u16();

    let mut response_headers = HashMap::new();
    let mut headers_size = 2usize;
    for (key, value) in response.headers() {
        let val_bytes = value.as_bytes();
        let val_str = value.to_str().unwrap_or("").to_string();
        headers_size += key.as_str().len() + 2 + val_bytes.len() + 2;
        response_headers.insert(key.to_string(), val_str);
    }

    let body_text = response.text().await.map_err(map_reqwest_error)?;
    let body_size = body_text.as_bytes().len();
    let total_size = headers_size + body_size;

    Ok(HttpResponse {
        status,
        headers: response_headers,
        body: body_text,
        time_elapsed,
        headers_size,
        body_size,
        total_size,
    })
}

#[tauri::command]
async fn execute_request(
    state: State<'_, AppState>,
    request_id: String,
    method: String,
    url: String,
    headers: HashMap<String, String>,
    body: Option<String>,
    query_params: Option<HashMap<String, String>>,
    auth: Option<AuthConfig>,
) -> Result<HttpResponse, AppError> {
    let req_method = method.parse::<reqwest::Method>()
        .map_err(|e| AppError::new("invalid_method", format!("Invalid HTTP method: {e}")))?;
    let mut request_builder = state.client.request(req_method, &url);

    // Add Query Params
    if let Some(params) = query_params {
        request_builder = request_builder.query(&params);
    }

    // Add Headers
    for (key, value) in headers {
        request_builder = request_builder.header(key, value);
    }

    // Add Auth
    if let Some(auth_config) = auth {
        match auth_config.auth_type.as_str() {
            "basic" => {
                request_builder = request_builder.basic_auth(
                    auth_config.basic.username,
                    Some(auth_config.basic.password),
                );
            }
            "bearer" => {
                request_builder = request_builder.bearer_auth(auth_config.bearer.token);
            }
            "api_key" => {
                let key = auth_config.api_key.key;
                let value = auth_config.api_key.value;
                if auth_config.api_key.add_to == "header" {
                    request_builder = request_builder.header(key, value);
                } else if auth_config.api_key.add_to == "query" {
                    request_builder = request_builder.query(&[(key, value)]);
                }
            }
            _ => {} // "none" or unknown
        }
    }

    // Add Body
    if let Some(b) = body {
        if !b.is_empty() {
             request_builder = request_builder.body(b);
        }
    }

    let (cancel_tx, mut cancel_rx) = oneshot::channel::<()>();
    {
        let mut pending_requests = state.pending_requests.lock().await;
        pending_requests.insert(request_id.clone(), cancel_tx);
    }

    let result = tokio::select! {
        _ = &mut cancel_rx => Err(AppError::new("canceled", "The request was canceled.")),
        result = perform_request(request_builder) => result,
    };

    let mut pending_requests = state.pending_requests.lock().await;
    pending_requests.remove(&request_id);

    result
}

#[tauri::command]
async fn cancel_request(
    state: State<'_, AppState>,
    request_id: String,
) -> Result<bool, AppError> {
    let sender = {
        let mut pending_requests = state.pending_requests.lock().await;
        pending_requests.remove(&request_id)
    };

    match sender {
        Some(cancel_tx) => {
            let _ = cancel_tx.send(());
            Ok(true)
        }
        None => Ok(false),
    }
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    let client = reqwest::Client::builder()
        .timeout(std::time::Duration::from_secs(30))
        .build()
        .expect("failed to create HTTP client");

    tauri::Builder::default()
        .manage(AppState {
            client,
            pending_requests: Mutex::new(HashMap::new()),
        })
        .plugin(tauri_plugin_opener::init())
        .invoke_handler(tauri::generate_handler![execute_request, cancel_request])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
