mod byok_config;
mod fs_util;
mod pty;
mod settings;
mod updater;
mod vigthoria_cli;
mod workspace;

use byok_config::{byok_status, save_byok_config, ByokStatus};
use fs_util::{
    delete_workspace_file, is_valid_project_root, list_directory, path_exists,
    read_workspace_file, write_workspace_file,
};
use pty::{PtyManager, PtySpawnOptions, PtyWritePayload};
use settings::{AppSettings, SettingsStore};
use std::path::PathBuf;
use tauri::{AppHandle, Manager, State};
use updater::WorkbenchUpdateInfo;
use vigthoria_cli::{
    bundled_cli_status, detect_package_script, get_auth_status, get_auth_token,
    install_bundled_cli as install_bundled_cli_package, launch_login,
    login_with_credentials, run_community_repo_command as run_community_repo,
    run_logout, run_update, run_workspace_stabilize,
    run_workspace_stabilize_sandbox, vigthoria_http_request, BundledCliStatus,
    VigthoriaAuthStatus, VigthoriaHttpResponse,
};
use workspace::{WorkspaceDefinition, WorkspaceStore};

#[derive(Default)]
pub struct AppState {
    pub pty: PtyManager,
    pub workspaces: WorkspaceStore,
    pub settings: SettingsStore,
}

#[tauri::command]
fn pty_spawn(
    app: AppHandle,
    state: State<'_, AppState>,
    options: PtySpawnOptions,
) -> Result<String, String> {
    state.pty.spawn(app, options)
}

#[tauri::command]
fn pty_write(state: State<'_, AppState>, payload: PtyWritePayload) -> Result<(), String> {
    state.pty.write(&payload.id, &payload.data)
}

#[tauri::command]
fn pty_resize(state: State<'_, AppState>, id: String, cols: u16, rows: u16) -> Result<(), String> {
    state.pty.resize(&id, cols, rows)
}

#[tauri::command]
fn pty_kill(state: State<'_, AppState>, id: String) -> Result<(), String> {
    state.pty.kill(&id)
}

#[tauri::command]
fn workspace_list(state: State<'_, AppState>) -> Result<Vec<WorkspaceDefinition>, String> {
    Ok(state.workspaces.list())
}

#[tauri::command]
fn workspace_save(
    app: AppHandle,
    state: State<'_, AppState>,
    workspace: WorkspaceDefinition,
) -> Result<WorkspaceDefinition, String> {
    let saved = state.workspaces.save(workspace);
    state.workspaces.persist(&app)?;
    Ok(saved)
}

#[tauri::command]
fn workspace_delete(app: AppHandle, state: State<'_, AppState>, id: String) -> Result<(), String> {
    state.workspaces.delete(&id)?;
    state.workspaces.persist(&app)?;
    Ok(())
}

#[tauri::command]
fn settings_get(state: State<'_, AppState>) -> Result<AppSettings, String> {
    Ok(state.settings.get())
}

#[tauri::command]
fn settings_save(
    app: AppHandle,
    state: State<'_, AppState>,
    settings: AppSettings,
) -> Result<AppSettings, String> {
    state.settings.save(&app, settings)
}

#[tauri::command]
fn detect_vigthoria_cli() -> Result<Option<String>, String> {
    Ok(which::which("vigthoria")
        .ok()
        .map(|p| p.to_string_lossy().into_owned()))
}

fn bundled_cli_package_path(app: &AppHandle) -> Option<PathBuf> {
    const NAME: &str = "vigthoria-cli-1.13.13.tgz";
    let mut candidates = Vec::new();
    if let Ok(resource_dir) = app.path().resource_dir() {
        candidates.push(resource_dir.join("_up_").join("resources").join(NAME));
        candidates.push(resource_dir.join("resources").join(NAME));
        candidates.push(resource_dir.join(NAME));
    }
    if let Ok(executable) = std::env::current_exe() {
        if let Some(parent) = executable.parent() {
            candidates.push(parent.join("resources").join(NAME));
            candidates.push(parent.join(NAME));
        }
    }
    candidates.push(
        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .join("..")
            .join("resources")
            .join(NAME),
    );
    candidates.into_iter().find(|path| path.is_file())
}

#[tauri::command]
fn get_bundled_cli_status(app: AppHandle) -> BundledCliStatus {
    let package = bundled_cli_package_path(&app);
    bundled_cli_status(package.as_deref())
}

#[tauri::command]
async fn install_bundled_cli(app: AppHandle) -> Result<String, String> {
    let package = bundled_cli_package_path(&app)
        .ok_or_else(|| "Bundled Vigthoria CLI package was not found.".to_string())?;
    tauri::async_runtime::spawn_blocking(move || install_bundled_cli_package(&package))
        .await
        .map_err(|e| format!("Bundled CLI install task failed: {e}"))?
}

#[tauri::command]
async fn run_vigthoria_update(cli_path: Option<String>) -> Result<String, String> {
    // Runs on a dedicated blocking thread so a slow/stalled `vigthoria update`
    // (npm registry issues, network problems, etc.) can never freeze the
    // Workbench UI. `run_update` itself also enforces a hard timeout.
    tauri::async_runtime::spawn_blocking(move || run_update(cli_path.as_deref()))
        .await
        .map_err(|e| format!("Update task failed to complete: {e}"))?
}

#[tauri::command]
async fn run_community_repo_command(
    cli_path: Option<String>,
    action: String,
    root_path: String,
    repository_name: Option<String>,
    visibility: Option<String>,
) -> Result<String, String> {
    tauri::async_runtime::spawn_blocking(move || {
        run_community_repo(
            cli_path.as_deref(),
            &action,
            &root_path,
            repository_name.as_deref(),
            visibility.as_deref(),
        )
    })
    .await
    .map_err(|e| format!("Community repository task failed: {e}"))?
}

#[tauri::command]
fn launch_vigthoria_login(cli_path: Option<String>) -> Result<String, String> {
    launch_login(cli_path.as_deref())
}

#[tauri::command]
async fn vigthoria_login(
    cli_path: Option<String>,
    email: String,
    password: String,
) -> Result<String, String> {
    // Runs on a dedicated blocking thread; login_with_credentials enforces
    // its own hard timeout so a stalled login process can't freeze the UI.
    tauri::async_runtime::spawn_blocking(move || {
        login_with_credentials(cli_path.as_deref(), &email, &password)
    })
    .await
    .map_err(|e| format!("Login task failed to complete: {e}"))?
}

#[tauri::command]
async fn vigthoria_logout(cli_path: Option<String>) -> Result<String, String> {
    // Runs on a dedicated blocking thread; run_logout enforces its own hard
    // timeout so a stalled CLI process can't freeze the UI.
    tauri::async_runtime::spawn_blocking(move || run_logout(cli_path.as_deref()))
        .await
        .map_err(|e| format!("Logout task failed to complete: {e}"))?
}

#[tauri::command]
fn default_shell() -> String {
    if cfg!(windows) {
        if let Ok(comspec) = std::env::var("COMSPEC") {
            let trimmed = comspec.trim();
            if !trimmed.is_empty() {
                return trimmed.to_string();
            }
        }
        r"C:\Windows\System32\cmd.exe".into()
    } else if cfg!(target_os = "macos") {
        std::env::var("SHELL").unwrap_or_else(|_| "/bin/zsh".into())
    } else {
        std::env::var("SHELL").unwrap_or_else(|_| "/bin/bash".into())
    }
}

#[tauri::command]
fn home_directory() -> Result<String, String> {
    dirs::home_dir()
        .map(|p| p.to_string_lossy().into_owned())
        .ok_or_else(|| "Could not resolve home directory".into())
}

#[tauri::command]
async fn vigthoria_auth_status(verify: Option<bool>) -> VigthoriaAuthStatus {
    let verify = verify.unwrap_or(true);
    // get_auth_status can make a blocking network call (up to ~8s); run it
    // off the main thread so it never stalls the UI message loop.
    tauri::async_runtime::spawn_blocking(move || get_auth_status(verify))
        .await
        .unwrap_or_else(|e| VigthoriaAuthStatus {
            cli_installed: false,
            has_token: false,
            authenticated: false,
            email: None,
            user_id: None,
            plan: None,
            is_admin: false,
            is_master_admin: false,
            api_url: "https://coder.vigthoria.io".into(),
            models_api_url: "https://api.vigthoria.io".into(),
            message: format!("Auth check task failed to complete: {e}"),
        })
}

#[tauri::command]
fn vigthoria_auth_token() -> Option<String> {
    get_auth_token()
}

#[tauri::command]
async fn vigthoria_http_proxy(
    method: String,
    url: String,
    token: Option<String>,
    body: Option<String>,
) -> Result<VigthoriaHttpResponse, String> {
    // Hyper-Loop/account calls can legitimately take up to ~120s; run off the
    // main thread so a long-running request never freezes the Workbench UI.
    tauri::async_runtime::spawn_blocking(move || {
        vigthoria_http_request(&method, &url, token.as_deref(), body.as_deref())
    })
    .await
    .map_err(|e| format!("HTTP proxy task failed to complete: {e}"))?
}

#[tauri::command]
fn run_workspace_stabilize_sandbox_cmd(root_path: String) -> Result<serde_json::Value, String> {
    let (success, output) = run_workspace_stabilize_sandbox(&root_path)?;
    Ok(serde_json::json!({ "success": success, "output": output }))
}

#[tauri::command]
fn run_workspace_stabilize_cmd(root_path: String) -> Result<serde_json::Value, String> {
    let (success, output) = run_workspace_stabilize(&root_path)?;
    Ok(serde_json::json!({ "success": success, "output": output }))
}

#[tauri::command]
fn package_start_command(root_path: String) -> Result<Option<String>, String> {
    detect_package_script(&root_path)
}

#[tauri::command]
fn platform() -> String {
    if cfg!(windows) {
        "windows".into()
    } else if cfg!(target_os = "macos") {
        "macos".into()
    } else {
        "linux".into()
    }
}

#[tauri::command]
fn app_version() -> String {
    updater::current_version()
}

#[tauri::command]
fn check_workbench_update() -> Result<WorkbenchUpdateInfo, String> {
    updater::check_for_update()
}

#[tauri::command]
fn download_workbench_update(url: String, sha256: String) -> Result<String, String> {
    updater::download_update(&url, &sha256)
}

#[tauri::command]
async fn get_byok_status() -> Result<ByokStatus, String> {
    // Local disk read only, but keep it off the main thread defensively so
    // the UI never stalls, consistent with the other Tauri commands here.
    tauri::async_runtime::spawn_blocking(byok_status)
        .await
        .map_err(|e| format!("BYOK status task failed to complete: {e}"))?
}

#[tauri::command]
async fn set_byok_config(
    api_key: Option<String>,
    base_url: String,
    model: String,
    clear_api_key: Option<bool>,
) -> Result<ByokStatus, String> {
    let clear_api_key = clear_api_key.unwrap_or(false);
    tauri::async_runtime::spawn_blocking(move || {
        save_byok_config(api_key, base_url, model, clear_api_key)
    })
    .await
    .map_err(|e| format!("BYOK save task failed to complete: {e}"))?
}

#[tauri::command]
fn install_workbench_update(path: String) -> Result<(), String> {
    updater::launch_installer_and_exit(&path)
}

#[tauri::command]
fn export_diagnostics(app: AppHandle, state: State<'_, AppState>) -> Result<String, String> {
    use std::time::{SystemTime, UNIX_EPOCH};

    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|e| format!("System clock error: {e}"))?
        .as_secs();
    let correlation_id = format!("wb-{timestamp}-{}", uuid::Uuid::new_v4().simple());
    let log_path = std::env::temp_dir().join("vigthoria-workbench-diag.log");
    let diagnostic_log = std::fs::read(&log_path)
        .ok()
        .map(|bytes| {
            let start = bytes.len().saturating_sub(1_048_576);
            String::from_utf8_lossy(&bytes[start..]).into_owned()
        })
        .unwrap_or_else(|| "No diagnostic log was found.".into());
    let cli_path = which::which("vigthoria")
        .ok()
        .map(|path| path.to_string_lossy().into_owned());
    let bundle = serde_json::json!({
        "schemaVersion": 1,
        "correlationId": correlation_id,
        "createdAtUnix": timestamp,
        "workbenchVersion": updater::current_version(),
        "platform": platform(),
        "cli": {
            "detected": cli_path.is_some(),
            "path": cli_path,
        },
        "workspaceCount": state.workspaces.list().len(),
        "settings": state.settings.get(),
        "diagnosticLog": diagnostic_log,
        "privacy": "No Vigthoria auth token, password, or BYOK API key is included.",
    });
    let output_dir = app
        .path()
        .download_dir()
        .or_else(|_| app.path().app_data_dir())
        .map_err(|e| format!("Could not resolve diagnostics output directory: {e}"))?;
    std::fs::create_dir_all(&output_dir)
        .map_err(|e| format!("Could not create diagnostics output directory: {e}"))?;
    let output = output_dir.join(format!("vigthoria-workbench-support-{timestamp}.json"));
    std::fs::write(
        &output,
        serde_json::to_vec_pretty(&bundle)
            .map_err(|e| format!("Could not encode diagnostics: {e}"))?,
    )
    .map_err(|e| format!("Could not write diagnostics: {e}"))?;
    diag_log(&format!(
        "diagnostics exported correlation_id={} path={}",
        correlation_id,
        output.display()
    ));
    Ok(output.to_string_lossy().into_owned())
}

fn diag_log(msg: &str) {
    use std::io::Write;
    let path = std::env::temp_dir().join("vigthoria-workbench-diag.log");
    if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&path) {
        let _ = writeln!(f, "[{:?}] {}", std::time::SystemTime::now(), msg);
    }
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    let default_hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |info| {
        diag_log(&format!("PANIC: {}", info));
        default_hook(info);
    }));
    diag_log("run() start");
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_store::Builder::default().build())
        .manage(AppState::default())
        .setup(|app| {
            diag_log("setup() start");
            let handle = app.handle().clone();
            let state = app.state::<AppState>();
            state.workspaces.load_from_disk(&handle);
            diag_log("workspaces loaded");
            state.settings.load_from_disk(&handle);
            diag_log("settings loaded");
            if let Some(win) = app.get_webview_window("main") {
                diag_log(&format!("main window found, visible={:?}", win.is_visible()));
                let _ = win.show();
                let _ = win.set_focus();
                diag_log(&format!("after show(), visible={:?}", win.is_visible()));
            } else {
                diag_log("main window NOT FOUND in setup()");
            }
            diag_log("setup() end");
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![
            pty_spawn,
            pty_write,
            pty_resize,
            pty_kill,
            workspace_list,
            workspace_save,
            workspace_delete,
            settings_get,
            settings_save,
            detect_vigthoria_cli,
            get_bundled_cli_status,
            install_bundled_cli,
            run_vigthoria_update,
            run_community_repo_command,
            launch_vigthoria_login,
            vigthoria_login,
            vigthoria_logout,
            default_shell,
            home_directory,
            platform,
            list_directory,
            path_exists,
            is_valid_project_root,
            vigthoria_auth_status,
            vigthoria_auth_token,
            vigthoria_http_proxy,
            package_start_command,
            run_workspace_stabilize_cmd,
            run_workspace_stabilize_sandbox_cmd,
            write_workspace_file,
            read_workspace_file,
            delete_workspace_file,
            app_version,
            check_workbench_update,
            download_workbench_update,
            install_workbench_update,
            export_diagnostics,
            get_byok_status,
            set_byok_config,
        ])
        .run(tauri::generate_context!())
        .expect("error while running Vigthoria Workbench");
    diag_log("run() returned (app exiting)");
}
