use reqwest::Method;
use reqwest::StatusCode;
use serde::Serialize;
use serde_json::{Map, Value};
use std::time::Duration;

struct ApiState {
    client: reqwest::Client,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct BridgeResponse {
    ok: bool,
    status: u16,
    data: Value,
}

fn normalize_base_url(base_url: &str) -> String {
    let trimmed = base_url.trim();
    if trimmed.is_empty() {
        return String::new();
    }
    trimmed.trim_end_matches('/').to_string()
}

fn join_api_url(base_url: &str, path: &str) -> String {
    format!("{}{}", normalize_base_url(base_url), path)
}

fn fallback_json(status: StatusCode, text: &str) -> Value {
    let mut data = Map::new();
    data.insert("success".to_string(), Value::Bool(status.is_success()));
    data.insert(
        "message".to_string(),
        Value::String(if text.trim().is_empty() {
            format!("HTTP {}", status.as_u16())
        } else {
            text.to_string()
        }),
    );
    Value::Object(data)
}

async fn request_json(
    client: &reqwest::Client,
    method: Method,
    url: String,
    body: Option<Value>,
    csrf_token: Option<String>,
) -> Result<BridgeResponse, String> {
    if url.is_empty() {
        return Err("API 地址不能为空".to_string());
    }

    let mut request = client
        .request(method, &url)
        .header("Accept", "application/json")
        .header("Content-Type", "application/json");

    if let Some(csrf) = csrf_token {
        request = request.header("X-CSRF-Token", csrf);
    }

    if let Some(payload) = body {
        request = request.json(&payload);
    }

    let response = request
        .send()
        .await
        .map_err(|err| format!("请求失败: {}", err))?;

    let status = response.status();
    let text = response
        .text()
        .await
        .map_err(|err| format!("读取响应失败: {}", err))?;
    let data = match serde_json::from_str::<Value>(&text) {
        Ok(parsed) => parsed,
        Err(_) => fallback_json(status, &text),
    };

    Ok(BridgeResponse {
        ok: status.is_success(),
        status: status.as_u16(),
        data,
    })
}

async fn fetch_csrf_token(client: &reqwest::Client, base_url: &str) -> Result<Option<String>, String> {
    let response = request_json(
        client,
        Method::GET,
        join_api_url(base_url, "/api/csrf-token"),
        None,
        None,
    )
    .await?;

    let token = response
        .data
        .get("csrfToken")
        .and_then(Value::as_str)
        .map(|v| v.to_string());

    Ok(token)
}

async fn request_with_optional_csrf(
    client: &reqwest::Client,
    method: Method,
    base_url: &str,
    path: &str,
    body: Option<Value>,
    need_csrf: bool,
) -> Result<BridgeResponse, String> {
    let csrf_token = if need_csrf {
        fetch_csrf_token(client, base_url).await?
    } else {
        None
    };

    request_json(
        client,
        method,
        join_api_url(base_url, path),
        body,
        csrf_token,
    )
    .await
}

#[tauri::command]
async fn api_login(
    state: tauri::State<'_, ApiState>,
    base_url: String,
    username: String,
    password: String,
    captcha: Option<String>,
) -> Result<BridgeResponse, String> {
    let mut body = Map::new();
    body.insert("username".to_string(), Value::String(username));
    body.insert("password".to_string(), Value::String(password));
    if let Some(value) = captcha {
        if !value.trim().is_empty() {
            body.insert("captcha".to_string(), Value::String(value));
        }
    }

    request_with_optional_csrf(
        &state.client,
        Method::POST,
        &base_url,
        "/api/login",
        Some(Value::Object(body)),
        false,
    )
    .await
}

#[tauri::command]
async fn api_get_profile(
    state: tauri::State<'_, ApiState>,
    base_url: String,
) -> Result<BridgeResponse, String> {
    request_with_optional_csrf(
        &state.client,
        Method::GET,
        &base_url,
        "/api/user/profile",
        None,
        false,
    )
    .await
}

#[tauri::command]
async fn api_list_files(
    state: tauri::State<'_, ApiState>,
    base_url: String,
    path: String,
) -> Result<BridgeResponse, String> {
    let normalized = if path.trim().is_empty() {
        "/".to_string()
    } else {
        path
    };
    let encoded = urlencoding::encode(&normalized);
    let api_url = format!("{}?path={}", join_api_url(&base_url, "/api/files"), encoded);

    request_json(&state.client, Method::GET, api_url, None, None).await
}

#[tauri::command]
async fn api_logout(
    state: tauri::State<'_, ApiState>,
    base_url: String,
) -> Result<BridgeResponse, String> {
    request_with_optional_csrf(
        &state.client,
        Method::POST,
        &base_url,
        "/api/logout",
        Some(Value::Object(Map::new())),
        true,
    )
    .await
}

#[tauri::command]
async fn api_search_files(
    state: tauri::State<'_, ApiState>,
    base_url: String,
    path: String,
    keyword: String,
    search_type: Option<String>,
    limit: Option<u32>,
) -> Result<BridgeResponse, String> {
    let normalized_path = if path.trim().is_empty() {
        "/".to_string()
    } else {
        path
    };
    let kind = search_type
        .unwrap_or_else(|| "all".to_string())
        .trim()
        .to_string();
    let max_limit = limit.unwrap_or(100).clamp(1, 500);

    let api_url = format!(
        "{}?path={}&keyword={}&type={}&limit={}",
        join_api_url(&base_url, "/api/files/search"),
        urlencoding::encode(&normalized_path),
        urlencoding::encode(&keyword),
        urlencoding::encode(&kind),
        max_limit
    );

    request_json(&state.client, Method::GET, api_url, None, None).await
}

#[tauri::command]
async fn api_mkdir(
    state: tauri::State<'_, ApiState>,
    base_url: String,
    path: String,
    folder_name: String,
) -> Result<BridgeResponse, String> {
    let mut body = Map::new();
    body.insert("path".to_string(), Value::String(path));
    body.insert("folderName".to_string(), Value::String(folder_name));

    request_with_optional_csrf(
        &state.client,
        Method::POST,
        &base_url,
        "/api/files/mkdir",
        Some(Value::Object(body)),
        true,
    )
    .await
}

#[tauri::command]
async fn api_rename_file(
    state: tauri::State<'_, ApiState>,
    base_url: String,
    path: String,
    old_name: String,
    new_name: String,
) -> Result<BridgeResponse, String> {
    let mut body = Map::new();
    body.insert("path".to_string(), Value::String(path));
    body.insert("oldName".to_string(), Value::String(old_name));
    body.insert("newName".to_string(), Value::String(new_name));

    request_with_optional_csrf(
        &state.client,
        Method::POST,
        &base_url,
        "/api/files/rename",
        Some(Value::Object(body)),
        true,
    )
    .await
}

#[tauri::command]
async fn api_delete_file(
    state: tauri::State<'_, ApiState>,
    base_url: String,
    path: String,
    file_name: String,
) -> Result<BridgeResponse, String> {
    let mut body = Map::new();
    body.insert("path".to_string(), Value::String(path));
    body.insert("fileName".to_string(), Value::String(file_name));

    request_with_optional_csrf(
        &state.client,
        Method::POST,
        &base_url,
        "/api/files/delete",
        Some(Value::Object(body)),
        true,
    )
    .await
}

#[tauri::command]
async fn api_get_download_url(
    state: tauri::State<'_, ApiState>,
    base_url: String,
    path: String,
    mode: Option<String>,
) -> Result<BridgeResponse, String> {
    let normalized_mode = mode.unwrap_or_else(|| "download".to_string());
    let api_url = format!(
        "{}?path={}&mode={}",
        join_api_url(&base_url, "/api/files/download-url"),
        urlencoding::encode(&path),
        urlencoding::encode(&normalized_mode)
    );
    request_json(&state.client, Method::GET, api_url, None, None).await
}

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

    tauri::Builder::default()
        .manage(ApiState { client })
        .plugin(tauri_plugin_opener::init())
        .invoke_handler(tauri::generate_handler![
            api_login,
            api_get_profile,
            api_list_files,
            api_logout,
            api_search_files,
            api_mkdir,
            api_rename_file,
            api_delete_file,
            api_get_download_url
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
