use crate::local_ai::resource::WatchDiskEvent;
use flowy_error::{FlowyError, FlowyResult};
use std::path::PathBuf;
use std::process::Command;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
use tracing::{error, trace};

#[cfg(windows)]
use winreg::{enums::*, RegKey};

#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
#[allow(dead_code)]
pub struct WatchContext {
  watcher: notify::RecommendedWatcher,
  pub path: PathBuf,
}

#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
#[allow(dead_code)]
pub fn watch_offline_app() -> FlowyResult<(WatchContext, UnboundedReceiver<WatchDiskEvent>)> {
  use notify::{Event, Watcher};

  let install_path = install_path().ok_or_else(|| {
    FlowyError::internal().with_context("Unsupported platform for offline app watching")
  })?;
  let (tx, rx) = unbounded_channel();
  let app_path = ollama_plugin_path();
  let mut watcher = notify::recommended_watcher(move |res: Result<Event, _>| match res {
    Ok(event) => {
      if event.paths.iter().any(|path| path == &app_path) {
        trace!("watch event: {:?}", event);
        match event.kind {
          notify::EventKind::Create(_) => {
            if let Err(err) = tx.send(WatchDiskEvent::Create) {
              error!("watch send error: {:?}", err)
            }
          },
          notify::EventKind::Remove(_) => {
            if let Err(err) = tx.send(WatchDiskEvent::Remove) {
              error!("watch send error: {:?}", err)
            }
          },
          _ => {
            trace!("unhandle watch event: {:?}", event);
          },
        }
      }
    },
    Err(e) => error!("watch error: {:?}", e),
  })
  .map_err(|err| FlowyError::internal().with_context(err))?;
  watcher
    .watch(&install_path, notify::RecursiveMode::NonRecursive)
    .map_err(|err| FlowyError::internal().with_context(err))?;

  Ok((
    WatchContext {
      watcher,
      path: install_path,
    },
    rx,
  ))
}

#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
pub(crate) fn install_path() -> Option<PathBuf> {
  None
}

#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
pub(crate) fn install_path() -> Option<PathBuf> {
  #[cfg(target_os = "windows")]
  return None;

  #[cfg(target_os = "macos")]
  return Some(PathBuf::from("/usr/local/bin"));

  #[cfg(target_os = "linux")]
  return None;
}

#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
pub fn is_plugin_ready() -> bool {
  ollama_plugin_path().exists() || ollama_plugin_command_available()
}

#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
pub fn is_plugin_ready() -> bool {
  false
}

#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
pub(crate) fn ollama_plugin_path() -> PathBuf {
  PathBuf::new()
}

#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
pub(crate) fn ollama_plugin_path() -> std::path::PathBuf {
  #[cfg(target_os = "windows")]
  {
    // Use LOCALAPPDATA for a user-specific installation path on Windows.
    let local_appdata =
      std::env::var("LOCALAPPDATA").unwrap_or_else(|_| "C:\\Program Files".to_string());
    std::path::PathBuf::from(local_appdata).join("Programs\\appflowy_plugin\\ollama_ai_plugin.exe")
  }

  #[cfg(target_os = "macos")]
  {
    let offline_app = "ollama_ai_plugin";
    std::path::PathBuf::from(format!("/usr/local/bin/{}", offline_app))
  }

  #[cfg(target_os = "linux")]
  {
    let offline_app = "ollama_ai_plugin";
    std::path::PathBuf::from(format!("/usr/local/bin/{}", offline_app))
  }
}

pub(crate) fn ollama_plugin_command_available() -> bool {
  if cfg!(windows) {
    #[cfg(windows)]
    {
      use std::os::windows::process::CommandExt;
      const CREATE_NO_WINDOW: u32 = 0x08000000;
      let output = Command::new("cmd")
        .args(&["/C", "where", "ollama_ai_plugin"])
        .creation_flags(CREATE_NO_WINDOW)
        .output();
      if let Ok(output) = output {
        if !output.stdout.is_empty() {
          return true;
        }
      }

      // 2. Fallback: Check registry PATH for the executable
      let path_dirs = get_windows_path_dirs();
      let plugin_exe = "ollama_ai_plugin.exe"; // Adjust name if needed

      path_dirs.iter().any(|dir| {
        let full_path = std::path::Path::new(dir).join(plugin_exe);
        full_path.exists()
      })
    }

    #[cfg(not(windows))]
    false
  } else {
    let output = Command::new("command")
      .args(["-v", "ollama_ai_plugin"])
      .output();
    match output {
      Ok(o) => !o.stdout.is_empty(),
      _ => false,
    }
  }
}

#[cfg(windows)]
fn get_windows_path_dirs() -> Vec<String> {
  let mut paths = Vec::new();

  // Check HKEY_CURRENT_USER\Environment
  let hkcu = RegKey::predef(HKEY_CURRENT_USER);
  if let Ok(env) = hkcu.open_subkey("Environment") {
    if let Ok(path) = env.get_value::<String, _>("Path") {
      paths.extend(path.split(';').map(|s| s.trim().to_string()));
    }
  }

  // Check HKEY_LOCAL_MACHINE\SYSTEM\...\Environment
  let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
  if let Ok(env) = hklm.open_subkey(r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment")
  {
    if let Ok(path) = env.get_value::<String, _>("Path") {
      paths.extend(path.split(';').map(|s| s.trim().to_string()));
    }
  }
  paths
}

#[cfg(test)]
mod tests {
  use crate::local_ai::watch::ollama_plugin_command_available;

  #[test]
  fn test_command_import() {
    let result = ollama_plugin_command_available();
    println!("ollama plugin exist: {:?}", result);
  }
}
