#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod badge;
mod external_tools;
mod git;
mod integrations;
mod telemetry;

use badge::set_sync_badge;
use external_tools::{
    detect_external_tools, launch_external_diff, launch_external_merge, launch_external_tool,
};
use git::{
    SearchCancellationState, add_remote, add_to_gitignore, add_worktree, amend_commit,
    apply_file_patch, cancel_code_search, cancel_file_history, checkout_branch, cherry_pick_abort,
    cherry_pick_commit, cherry_pick_continue, clone_repository, commit, commit_ai_generate,
    commit_ai_review, commit_ai_split, compare_commits, compare_file_to_head,
    compare_file_to_parent, create_branch, create_tag, cred_delete, cred_load, cred_save,
    delete_branch, delete_commit_note, delete_remote_branch, delete_remote_branches, delete_tag,
    diff_file_against_working_tree, fetch, fetch_commit_notes, get_commit_note, get_file_blame,
    get_file_patch, get_remote_url, get_status, git_lfs_install, git_lfs_prune, git_lfs_pull,
    git_lfs_status, git_lfs_track, git_lfs_untrack, init_repository, last_commit_message,
    list_branches, list_commits, list_file_history, list_interactive_rebase_commits, list_reflog,
    list_remotes, list_repository_files, list_stashes, list_tags, list_worktrees, lock_worktree,
    merge_abort, merge_branch, merge_continue, move_worktree, open_repo_in_explorer,
    open_repository, open_repository_bundle, open_repository_file, prune_worktrees, pull, push,
    push_commit_notes, push_tag, read_conflict, rebase_abort, rebase_branch, rebase_continue,
    remove_remote, remove_worktree, rename_branch, rename_remote_branch, repair_worktree,
    resolve_conflict, resolve_conflict_side, restore_file_from_commit, restore_files,
    restore_reflog_entry, restore_to_commit, revert_commit, run_sequence_editor_if_requested,
    search_code_introductions, set_branch_upstream, set_commit_note, stage_files,
    start_interactive_rebase, stash_apply, stash_drop, stash_pop, stash_push, undo_last_commit,
    unlock_worktree, unstage_files, untrack_paths, update_remote,
};
use integrations::list_integration_repositories;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use tauri::{Emitter, Manager};
use telemetry::{emit_frontend_log, emit_frontend_span, set_telemetry_enabled};

struct StartupRepository(Mutex<Option<String>>);

#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct StartupCloneRequest {
    remote_url: String,
    parent_path: String,
    directory_name: String,
}

struct StartupClone(Mutex<Option<StartupCloneRequest>>);

fn resolve_startup_path(path: &str, cwd: &Path) -> Option<PathBuf> {
    let path = path.trim();
    if path.is_empty() {
        return None;
    }
    let path = PathBuf::from(path);
    Some(if path.is_absolute() {
        path
    } else {
        cwd.join(path)
    })
}

fn clone_request_from_args(
    args: impl IntoIterator<Item = String>,
    cwd: &Path,
) -> Option<StartupCloneRequest> {
    let mut args = args.into_iter();

    while let Some(arg) = args.next() {
        let remote_url = if arg == "clone" || arg == "--clone" {
            args.next()
        } else {
            arg.strip_prefix("--clone=").map(ToString::to_string)
        };
        let Some(remote_url) = remote_url.filter(|value| !value.trim().is_empty()) else {
            continue;
        };
        let target = resolve_startup_path(&args.next()?, cwd)?;
        let directory_name = target.file_name()?.to_string_lossy().trim().to_string();
        let parent_path = target.parent()?.to_string_lossy().into_owned();
        if directory_name.is_empty() || parent_path.trim().is_empty() {
            return None;
        }
        return Some(StartupCloneRequest {
            remote_url,
            parent_path,
            directory_name,
        });
    }

    None
}

fn repository_path_from_args(args: impl IntoIterator<Item = String>, cwd: &Path) -> Option<String> {
    let mut args = args.into_iter();
    let mut repository = None;

    while let Some(arg) = args.next() {
        if arg == "clone" || arg == "--clone" || arg.starts_with("--clone=") {
            return None;
        }
        if arg == "--repo" {
            repository = args.next();
            break;
        }
        if let Some(path) = arg.strip_prefix("--repo=") {
            repository = Some(path.to_string());
            break;
        }
        if !arg.starts_with('-') {
            repository = Some(arg);
            break;
        }
    }

    repository
        .and_then(|path| resolve_startup_path(&path, cwd))
        .map(|path| path.to_string_lossy().into_owned())
}

#[cfg(test)]
mod startup_repository_tests {
    use super::{clone_request_from_args, repository_path_from_args};
    use std::path::Path;

    #[test]
    fn accepts_direct_relative_repository_path() {
        let path =
            repository_path_from_args(["projects/repo".to_string()], Path::new("/home/user"));
        assert_eq!(
            path.map(|value| value.replace('\\', "/")),
            Some("/home/user/projects/repo".to_string())
        );
    }

    #[test]
    fn accepts_repo_option() {
        let path = repository_path_from_args(
            ["--repo".to_string(), "/projects/repo".to_string()],
            Path::new("/home/user"),
        );
        assert_eq!(path.as_deref(), Some("/projects/repo"));
    }

    #[test]
    fn accepts_repo_equals_option() {
        let path = repository_path_from_args(
            ["--repo=projects/repo".to_string()],
            Path::new("/home/user"),
        );
        assert_eq!(
            path.map(|value| value.replace('\\', "/")),
            Some("/home/user/projects/repo".to_string())
        );
    }

    #[test]
    fn accepts_clone_command_with_exact_relative_target() {
        let request = clone_request_from_args(
            [
                "clone".to_string(),
                "https://example.com/team/project.git".to_string(),
                "clones/local-copy".to_string(),
            ],
            Path::new("/home/user"),
        )
        .expect("clone request should be parsed");

        assert_eq!(request.remote_url, "https://example.com/team/project.git");
        assert_eq!(request.directory_name, "local-copy");
        assert_eq!(request.parent_path.replace('\\', "/"), "/home/user/clones");
    }

    #[test]
    fn accepts_clone_option_and_clone_equals_option() {
        for args in [
            vec![
                "--clone".to_string(),
                "git@example.com:team/project.git".to_string(),
                "/projects/project".to_string(),
            ],
            vec![
                "--clone=git@example.com:team/project.git".to_string(),
                "/projects/project".to_string(),
            ],
        ] {
            let request = clone_request_from_args(args, Path::new("/home/user"))
                .expect("clone option should be parsed");
            assert_eq!(request.directory_name, "project");
            assert_eq!(request.parent_path.replace('\\', "/"), "/projects");
        }
    }

    #[test]
    fn repository_parser_does_not_treat_clone_values_as_repository_paths() {
        let path = repository_path_from_args(
            [
                "--clone".to_string(),
                "https://example.com/team/project.git".to_string(),
                "clones/project".to_string(),
            ],
            Path::new("/home/user"),
        );
        assert!(path.is_none());
    }
}

#[tauri::command]
fn take_startup_repository(state: tauri::State<'_, StartupRepository>) -> Option<String> {
    state.0.lock().ok()?.take()
}

#[tauri::command]
fn take_startup_clone(state: tauri::State<'_, StartupClone>) -> Option<StartupCloneRequest> {
    state.0.lock().ok()?.take()
}

struct ConsoleLogger;

impl log::Log for ConsoleLogger {
    fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
        metadata.level() <= log::Level::Info
    }

    fn log(&self, record: &log::Record<'_>) {
        if self.enabled(record.metadata()) {
            eprintln!(
                "[{}] [{}] {}",
                record.level(),
                record.target(),
                record.args()
            );
        }
    }

    fn flush(&self) {}
}

static CONSOLE_LOGGER: ConsoleLogger = ConsoleLogger;

fn init_console_logging() {
    if log::set_logger(&CONSOLE_LOGGER).is_ok() {
        log::set_max_level(log::LevelFilter::Info);
        log::info!(target: "gitty", "Rust console logging initialized");
    }
}

#[tauri::command]
fn close_splashscreen(app: tauri::AppHandle) -> Result<(), String> {
    if let Some(window) = app.get_webview_window("splashscreen") {
        let _ = window.hide();
        window
            .destroy()
            .map_err(|error| format!("failed to destroy splashscreen: {error}"))?;
    }

    if let Some(window) = app.get_webview_window("main") {
        let _ = window.maximize();
        window
            .show()
            .map_err(|error| format!("failed to show main window: {error}"))?;
        let _ = window.set_focus();
    }

    Ok(())
}

#[tokio::main]
async fn main() {
    init_console_logging();
    telemetry::init();
    if let Some(result) = run_sequence_editor_if_requested() {
        if let Err(error) = result {
            eprintln!("{error}");
            std::process::exit(1);
        }
        return;
    }

    let startup_args: Vec<String> = std::env::args().skip(1).collect();
    let startup_cwd = std::env::current_dir().unwrap_or_default();
    let startup_clone = clone_request_from_args(startup_args.clone(), &startup_cwd);
    let startup_repository = repository_path_from_args(startup_args, &startup_cwd);

    let builder = tauri::Builder::default()
        .plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
            let args: Vec<String> = args.into_iter().skip(1).collect();
            if let Some(request) = clone_request_from_args(args.clone(), Path::new(&cwd)) {
                if let Ok(mut pending) = app.state::<StartupClone>().0.lock() {
                    *pending = Some(request);
                }
                let _ = app.emit("open-startup-repository", ());
            } else if let Some(path) = repository_path_from_args(args, Path::new(&cwd)) {
                if let Ok(mut pending) = app.state::<StartupRepository>().0.lock() {
                    *pending = Some(path);
                }
                let _ = app.emit("open-startup-repository", ());
            }
            #[cfg(desktop)]
            let _ = app
                .get_webview_window("main")
                .expect("no main window")
                .set_focus();
        }))
        .plugin(
            tauri_plugin_aptabase::Builder::new("A-SH-1344793789")
                .with_options(tauri_plugin_aptabase::InitOptions {
                    host: Some("https://aptabase.cbsk-tech.de".to_string()),
                    flush_interval: None,
                })
                .build(),
        )
        .manage(StartupRepository(Mutex::new(startup_repository)))
        .manage(StartupClone(Mutex::new(startup_clone)))
        .manage(SearchCancellationState::default())
        .plugin(tauri_plugin_dialog::init());

    // Linux installs are expected to come from the system package manager (see the
    // PKGBUILD), which owns updates itself — the self-updater is only wired up for
    // the platforms whose install method this app ships (NSIS/Windows, .app/macOS).
    #[cfg(not(target_os = "linux"))]
    let builder = builder.plugin(tauri_plugin_updater::Builder::new().build());

    builder
        .invoke_handler(tauri::generate_handler![
            open_repository,
            init_repository,
            clone_repository,
            open_repo_in_explorer,
            open_repository_file,
            detect_external_tools,
            launch_external_tool,
            launch_external_diff,
            launch_external_merge,
            get_status,
            git_lfs_status,
            git_lfs_install,
            git_lfs_track,
            git_lfs_untrack,
            git_lfs_pull,
            git_lfs_prune,
            list_branches,
            list_remotes,
            add_remote,
            update_remote,
            remove_remote,
            set_branch_upstream,
            delete_remote_branch,
            delete_remote_branches,
            list_stashes,
            checkout_branch,
            create_branch,
            rename_branch,
            rename_remote_branch,
            delete_branch,
            list_worktrees,
            add_worktree,
            remove_worktree,
            move_worktree,
            lock_worktree,
            unlock_worktree,
            prune_worktrees,
            repair_worktree,
            list_tags,
            create_tag,
            delete_tag,
            push_tag,
            cherry_pick_commit,
            cherry_pick_continue,
            cherry_pick_abort,
            stage_files,
            unstage_files,
            add_to_gitignore,
            untrack_paths,
            stash_push,
            stash_apply,
            stash_pop,
            stash_drop,
            restore_files,
            get_file_patch,
            apply_file_patch,
            commit,
            amend_commit,
            undo_last_commit,
            last_commit_message,
            commit_ai_generate,
            commit_ai_review,
            commit_ai_split,
            pull,
            push,
            fetch,
            list_commits,
            get_commit_note,
            set_commit_note,
            delete_commit_note,
            fetch_commit_notes,
            push_commit_notes,
            restore_to_commit,
            restore_file_from_commit,
            merge_branch,
            merge_continue,
            merge_abort,
            revert_commit,
            rebase_branch,
            rebase_continue,
            rebase_abort,
            list_interactive_rebase_commits,
            start_interactive_rebase,
            list_reflog,
            restore_reflog_entry,
            list_repository_files,
            open_repository_bundle,
            list_file_history,
            cancel_file_history,
            get_file_blame,
            compare_commits,
            compare_file_to_head,
            compare_file_to_parent,
            diff_file_against_working_tree,
            search_code_introductions,
            cancel_code_search,
            read_conflict,
            resolve_conflict,
            resolve_conflict_side,
            get_remote_url,
            cred_load,
            cred_save,
            cred_delete,
            list_integration_repositories,
            set_sync_badge,
            close_splashscreen,
            set_telemetry_enabled,
            emit_frontend_log,
            emit_frontend_span,
            take_startup_repository,
            take_startup_clone
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
