mod engine;
use serde::{Deserialize, Serialize};
use std::env;
use std::fs;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use tauri::Manager;

use engine::{
  aria2_add_torrent, aria2_add_uri, aria2_change_global_option, aria2_get_task_detail,
  aria2_list_tasks, aria2_pause_all, aria2_pause_task, aria2_remove_task,
  aria2_remove_task_record, aria2_resume_all, aria2_resume_task, detect_aria2_binary,
  engine_start, engine_status, engine_stop,
  load_torrent_file,
  open_path_in_file_manager, stop_engine_for_exit, EngineState,
};

#[tauri::command]
async fn focus_main_window(app: tauri::AppHandle) -> Result<(), String> {
  let window = app
    .get_webview_window("main")
    .ok_or_else(|| "메인 창을 찾을 수 없습니다.".to_string())?;

  window
    .show()
    .map_err(|error| format!("창 표시 실패: {error}"))?;
  window
    .unminimize()
    .map_err(|error| format!("창 복원 실패: {error}"))?;
  window
    .set_focus()
    .map_err(|error| format!("창 포커스 실패: {error}"))?;
  Ok(())
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ExternalAddRequest {
  url: String,
  out: Option<String>,
  dir: Option<String>,
  referer: Option<String>,
  user_agent: Option<String>,
  authorization: Option<String>,
  cookie: Option<String>,
  proxy: Option<String>,
  split: Option<u32>,
}

fn default_extension_ids() -> Vec<String> {
  vec![
    "alaohbbicffclloghmknhlmfdbobcigc".to_string(),
    "makoclohjdpempbndoaljeadpngefhcf".to_string(),
  ]
}

fn collect_extension_ids() -> Vec<String> {
  let mut ids = default_extension_ids();
  for key in ["GDOWN_EXTENSION_ID", "GDOWN_EXTENSION_IDS"] {
    if let Ok(raw) = env::var(key) {
      for id in raw.split([',', ' ', '\n', '\t']) {
        let trimmed = id.trim();
        if trimmed.is_empty() {
          continue;
        }
        if !ids.iter().any(|v| v == trimmed) {
          ids.push(trimmed.to_string());
        }
      }
    }
  }
  ids
}

#[cfg(target_os = "macos")]
fn merge_existing_allowed_origins(manifest_path: &Path, ids: &mut Vec<String>) {
  let content = match fs::read_to_string(manifest_path) {
    Ok(v) => v,
    Err(_) => return,
  };
  let parsed: serde_json::Value = match serde_json::from_str(&content) {
    Ok(v) => v,
    Err(_) => return,
  };
  let Some(origins) = parsed.get("allowed_origins").and_then(|v| v.as_array()) else {
    return;
  };

  for origin in origins {
    let Some(value) = origin.as_str() else {
      continue;
    };
    let prefix = "chrome-extension://";
    if !value.starts_with(prefix) {
      continue;
    }
    let id = value.trim_start_matches(prefix).trim_end_matches('/');
    if id.is_empty() {
      continue;
    }
    if !ids.iter().any(|v| v == id) {
      ids.push(id.to_string());
    }
  }
}

fn resolve_node_path() -> Option<PathBuf> {
  if let Ok(path_var) = env::var("PATH") {
    for dir in env::split_paths(&path_var) {
      let node = dir.join("node");
      if node.is_file() {
        return Some(node);
      }
    }
  }

  for candidate in ["/usr/local/bin/node", "/opt/homebrew/bin/node", "/usr/bin/node"] {
    let path = PathBuf::from(candidate);
    if path.is_file() {
      return Some(path);
    }
  }
  None
}

#[cfg(target_os = "macos")]
fn ensure_native_host_installed() -> Result<PathBuf, String> {
  let host_name = "org.gdown.nativehost";
  let home = env::var("HOME").map_err(|err| format!("HOME 경로 확인 실패: {err}"))?;
  let tools_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
    .join("..")
    .join("tools")
    .join("native-host");
  let host_script = tools_dir.join("host.mjs");
  if !host_script.is_file() {
    return Err(format!("native host script not found: {}", host_script.display()));
  }

  let node_path = resolve_node_path().ok_or_else(|| "node 경로를 찾을 수 없습니다.".to_string())?;
  let runtime_dir = tools_dir.join(".runtime");
  fs::create_dir_all(&runtime_dir).map_err(|err| format!("runtime 디렉터리 생성 실패: {err}"))?;

  let runner_path = runtime_dir.join("run-host-macos.sh");
  let runner_content = format!(
    "#!/usr/bin/env bash\nset -euo pipefail\nexec \"{}\" \"{}\"\n",
    node_path.display(),
    host_script.display()
  );
  fs::write(&runner_path, runner_content).map_err(|err| format!("runner 생성 실패: {err}"))?;

  #[cfg(unix)]
  {
    use std::os::unix::fs::PermissionsExt;
    let perm = fs::Permissions::from_mode(0o755);
    fs::set_permissions(&runner_path, perm).map_err(|err| format!("runner 권한 설정 실패: {err}"))?;
  }

  let chrome_hosts_dir = Path::new(&home)
    .join("Library")
    .join("Application Support")
    .join("Google")
    .join("Chrome")
    .join("NativeMessagingHosts");
  fs::create_dir_all(&chrome_hosts_dir).map_err(|err| format!("native host 디렉터리 생성 실패: {err}"))?;
  let manifest_path = chrome_hosts_dir.join(format!("{host_name}.json"));

  let mut extension_ids = collect_extension_ids();
  merge_existing_allowed_origins(&manifest_path, &mut extension_ids);
  let allowed_origins = extension_ids
    .into_iter()
    .map(|id| format!("chrome-extension://{id}/"))
    .collect::<Vec<String>>();

  let manifest = serde_json::json!({
    "name": host_name,
    "description": "gdown Native Messaging Host",
    "path": runner_path,
    "type": "stdio",
    "allowed_origins": allowed_origins,
  });

  let manifest_text = serde_json::to_string_pretty(&manifest)
    .map_err(|err| format!("manifest 직렬화 실패: {err}"))?;
  fs::write(&manifest_path, manifest_text).map_err(|err| format!("manifest 쓰기 실패: {err}"))?;

  Ok(manifest_path)
}

fn external_add_queue_path() -> Result<PathBuf, String> {
  let home = std::env::var("HOME").map_err(|err| format!("HOME 경로 확인 실패: {err}"))?;
  Ok(PathBuf::from(home).join(".gdown").join("external_add_queue.jsonl"))
}

#[tauri::command]
fn take_external_add_requests() -> Result<Vec<ExternalAddRequest>, String> {
  let path = external_add_queue_path()?;
  let parent = path
    .parent()
    .ok_or_else(|| "큐 디렉터리 경로를 계산할 수 없습니다.".to_string())?;
  fs::create_dir_all(parent).map_err(|err| format!("큐 디렉터리 생성 실패: {err}"))?;

  if !path.exists() {
    return Ok(Vec::new());
  }

  let content = fs::read_to_string(&path).map_err(|err| format!("큐 읽기 실패: {err}"))?;
  let mut requests: Vec<ExternalAddRequest> = Vec::new();
  for line in content.lines() {
    let trimmed = line.trim();
    if trimmed.is_empty() {
      continue;
    }
    if let Ok(item) = serde_json::from_str::<ExternalAddRequest>(trimmed) {
      requests.push(item);
    }
  }

  let mut file = fs::OpenOptions::new()
    .write(true)
    .truncate(true)
    .open(&path)
    .map_err(|err| format!("큐 초기화 실패: {err}"))?;
  file
    .write_all(b"")
    .map_err(|err| format!("큐 파일 쓰기 실패: {err}"))?;

  Ok(requests)
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
  tauri::Builder::default()
    .manage(EngineState::default())
    .plugin(tauri_plugin_dialog::init())
    .plugin(tauri_plugin_deep_link::init())
    .on_window_event(|window, event| {
      if let tauri::WindowEvent::CloseRequested { .. } = event {
        if window.label() == "main" {
          let state = window.state::<EngineState>();
          stop_engine_for_exit(&state);
        }
      }
    })
    .setup(|app| {
      #[cfg(target_os = "macos")]
      {
        if let Err(err) = ensure_native_host_installed() {
          eprintln!("[gdown] native host auto-install skipped: {err}");
        }
      }

      if cfg!(debug_assertions) {
        app.handle().plugin(
          tauri_plugin_log::Builder::default()
            .level(log::LevelFilter::Info)
            .build(),
        )?;
      }
      Ok(())
    })
    .invoke_handler(tauri::generate_handler![
      engine_start,
      engine_stop,
      engine_status,
      detect_aria2_binary,
      aria2_add_torrent,
      aria2_add_uri,
      aria2_change_global_option,
      aria2_get_task_detail,
      aria2_list_tasks,
      aria2_pause_task,
      aria2_resume_task,
      aria2_remove_task,
      aria2_remove_task_record,
      aria2_pause_all,
      aria2_resume_all,
      load_torrent_file,
      open_path_in_file_manager,
      focus_main_window,
      take_external_add_requests
    ])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}
