use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use std::sync::Mutex;
use std::time::{SystemTime, UNIX_EPOCH};

use crate::events;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Extension {
  pub id: String,
  pub name: String,
  pub file_name: String,
  pub file_type: String,
  pub browser_compatibility: Vec<String>,
  pub created_at: u64,
  pub updated_at: u64,
  #[serde(default)]
  pub sync_enabled: bool,
  #[serde(default)]
  pub last_sync: Option<u64>,
  #[serde(default)]
  pub version: Option<String>,
  #[serde(default)]
  pub description: Option<String>,
  #[serde(default)]
  pub author: Option<String>,
  #[serde(default)]
  pub homepage_url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtensionGroup {
  pub id: String,
  pub name: String,
  pub extension_ids: Vec<String>,
  pub created_at: u64,
  pub updated_at: u64,
  #[serde(default)]
  pub sync_enabled: bool,
  #[serde(default)]
  pub last_sync: Option<u64>,
}

#[derive(Debug, Serialize, Deserialize)]
struct ExtensionGroupsData {
  groups: Vec<ExtensionGroup>,
}

fn now_secs() -> u64 {
  SystemTime::now()
    .duration_since(UNIX_EPOCH)
    .unwrap_or_default()
    .as_secs()
}

fn extensions_base_dir() -> PathBuf {
  crate::app_dirs::extensions_dir()
}

fn extension_groups_file() -> PathBuf {
  crate::app_dirs::data_subdir().join("extension_groups.json")
}

fn determine_browser_compatibility(file_type: &str) -> Vec<String> {
  match file_type {
    "crx" | "zip" => vec!["chromium".to_string()],
    _ => vec![],
  }
}

fn get_file_type(file_name: &str) -> Option<String> {
  let ext = file_name.rsplit('.').next()?.to_lowercase();
  match ext.as_str() {
    "crx" | "zip" => Some(ext),
    _ => None,
  }
}

fn find_zip_start(data: &[u8]) -> usize {
  for i in 0..data.len().saturating_sub(3) {
    if data[i] == 0x50 && data[i + 1] == 0x4B && data[i + 2] == 0x03 && data[i + 3] == 0x04 {
      return i;
    }
  }
  0
}

#[allow(clippy::type_complexity)]
fn extract_manifest_metadata(
  file_data: &[u8],
  file_type: &str,
) -> (
  Option<String>,
  Option<String>,
  Option<String>,
  Option<String>,
  Option<String>,
) {
  let zip_start = if file_type == "crx" {
    find_zip_start(file_data)
  } else {
    0
  };

  let cursor = std::io::Cursor::new(&file_data[zip_start..]);
  let mut archive = match zip::ZipArchive::new(cursor) {
    Ok(a) => a,
    Err(_) => return (None, None, None, None, None),
  };

  let manifest_content = if let Ok(mut file) = archive.by_name("manifest.json") {
    let mut contents = String::new();
    if std::io::Read::read_to_string(&mut file, &mut contents).is_ok() {
      Some(contents)
    } else {
      None
    }
  } else {
    None
  };

  let manifest_content = match manifest_content {
    Some(c) => c,
    None => return (None, None, None, None, None),
  };

  let manifest: serde_json::Value = match serde_json::from_str(&manifest_content) {
    Ok(v) => v,
    Err(_) => return (None, None, None, None, None),
  };

  let name = manifest
    .get("name")
    .and_then(|v| v.as_str())
    .map(|s| s.to_string());
  let version = manifest
    .get("version")
    .and_then(|v| v.as_str())
    .map(|s| s.to_string());
  let description = manifest
    .get("description")
    .and_then(|v| v.as_str())
    .map(|s| s.to_string());
  let author = manifest
    .get("author")
    .and_then(|v| v.as_str())
    .map(|s| s.to_string());
  let homepage_url = manifest
    .get("homepage_url")
    .or_else(|| manifest.get("homepage"))
    .and_then(|v| v.as_str())
    .map(|s| s.to_string());

  (name, version, description, author, homepage_url)
}

fn extract_icon_from_archive(file_data: &[u8], file_type: &str) -> Option<(Vec<u8>, String)> {
  let zip_start = if file_type == "crx" {
    find_zip_start(file_data)
  } else {
    0
  };

  let cursor = std::io::Cursor::new(&file_data[zip_start..]);
  let mut archive = match zip::ZipArchive::new(cursor) {
    Ok(a) => a,
    Err(_) => return None,
  };

  let icon_path = {
    let manifest_content = if let Ok(mut file) = archive.by_name("manifest.json") {
      let mut contents = String::new();
      if std::io::Read::read_to_string(&mut file, &mut contents).is_ok() {
        Some(contents)
      } else {
        None
      }
    } else {
      None
    };

    let manifest_content = manifest_content?;
    let manifest: serde_json::Value = serde_json::from_str(&manifest_content).ok()?;

    let mut best_path: Option<String> = None;
    let mut best_size: u32 = 0;

    if let Some(icons) = manifest.get("icons").and_then(|v| v.as_object()) {
      for (size_str, path_val) in icons {
        if let (Ok(size), Some(path)) = (size_str.parse::<u32>(), path_val.as_str()) {
          if size > best_size {
            best_size = size;
            best_path = Some(path.to_string());
          }
        }
      }
    }

    if best_path.is_none() {
      for key in &["action", "browser_action"] {
        if let Some(action) = manifest.get(*key) {
          if let Some(icon) = action.get("default_icon") {
            if let Some(path) = icon.as_str() {
              best_path = Some(path.to_string());
            } else if let Some(icons) = icon.as_object() {
              for (size_str, path_val) in icons {
                if let (Ok(size), Some(path)) = (size_str.parse::<u32>(), path_val.as_str()) {
                  if size > best_size {
                    best_size = size;
                    best_path = Some(path.to_string());
                  }
                }
              }
            }
          }
        }
      }
    }

    best_path
  };

  let icon_path = icon_path?;

  let clean_path = icon_path.trim_start_matches('/');
  let mut file = archive.by_name(clean_path).ok()?;
  let mut data = Vec::new();
  std::io::Read::read_to_end(&mut file, &mut data).ok()?;

  let ext = clean_path
    .rsplit('.')
    .next()
    .unwrap_or("png")
    .to_lowercase();

  Some((data, ext))
}

pub struct ExtensionManager;

impl ExtensionManager {
  pub fn new() -> Self {
    Self
  }

  fn get_extension_dir(&self, ext_id: &str) -> PathBuf {
    extensions_base_dir().join(ext_id)
  }

  fn get_metadata_path(&self, ext_id: &str) -> PathBuf {
    self.get_extension_dir(ext_id).join("metadata.json")
  }

  fn get_file_dir(&self, ext_id: &str) -> PathBuf {
    self.get_extension_dir(ext_id).join("file")
  }

  pub fn get_file_dir_public(&self, ext_id: &str) -> PathBuf {
    self.get_file_dir(ext_id)
  }

  // Extension CRUD

  pub fn add_extension(
    &self,
    name: String,
    file_name: String,
    file_data: Vec<u8>,
  ) -> Result<Extension, Box<dyn std::error::Error>> {
    let file_type =
      get_file_type(&file_name).ok_or_else(|| format!("Unsupported file type: {file_name}"))?;

    let browser_compatibility = determine_browser_compatibility(&file_type);
    if browser_compatibility.is_empty() {
      return Err(format!("Unsupported file type: {file_name}").into());
    }
    let now = now_secs();

    let (manifest_name, version, description, author, homepage_url) =
      extract_manifest_metadata(&file_data, &file_type);

    let final_name = if manifest_name.is_some() {
      manifest_name.clone().unwrap_or(name)
    } else {
      name
    };

    let ext = Extension {
      id: uuid::Uuid::new_v4().to_string(),
      name: final_name,
      file_name: file_name.clone(),
      file_type,
      browser_compatibility,
      created_at: now,
      updated_at: now,
      sync_enabled: crate::sync::is_sync_configured(),
      last_sync: None,
      version,
      description,
      author,
      homepage_url,
    };

    let file_dir = self.get_file_dir(&ext.id);
    fs::create_dir_all(&file_dir)?;
    fs::write(file_dir.join(&file_name), &file_data)?;

    if let Some((icon_data, icon_ext)) = extract_icon_from_archive(&file_data, &ext.file_type) {
      let icon_path = self
        .get_extension_dir(&ext.id)
        .join(format!("icon.{icon_ext}"));
      let _ = fs::write(icon_path, icon_data);
    }

    let metadata_path = self.get_metadata_path(&ext.id);
    let json = serde_json::to_string_pretty(&ext)?;
    fs::write(metadata_path, json)?;

    if let Err(e) = events::emit_empty("extensions-changed") {
      log::error!("Failed to emit extensions-changed event: {e}");
    }

    if ext.sync_enabled {
      if let Some(scheduler) = crate::sync::get_global_scheduler() {
        let id = ext.id.clone();
        tauri::async_runtime::spawn(async move {
          scheduler.queue_extension_sync(id).await;
        });
      }
    }

    Ok(ext)
  }

  pub fn get_extension(&self, id: &str) -> Result<Extension, Box<dyn std::error::Error>> {
    let metadata_path = self.get_metadata_path(id);
    if !metadata_path.exists() {
      return Err(format!("Extension with id '{id}' not found").into());
    }
    let content = fs::read_to_string(metadata_path)?;
    let ext: Extension = serde_json::from_str(&content)?;
    Ok(ext)
  }

  pub fn list_extensions(&self) -> Result<Vec<Extension>, Box<dyn std::error::Error>> {
    let base = extensions_base_dir();
    if !base.exists() {
      return Ok(Vec::new());
    }

    let mut extensions = Vec::new();
    for entry in fs::read_dir(base)? {
      let entry = entry?;
      if entry.file_type()?.is_dir() {
        let metadata_path = entry.path().join("metadata.json");
        if metadata_path.exists() {
          let content = fs::read_to_string(&metadata_path)?;
          if let Ok(ext) = serde_json::from_str::<Extension>(&content) {
            extensions.push(ext);
          }
        }
      }
    }

    extensions.sort_by_key(|a| a.created_at);
    Ok(extensions)
  }

  pub fn update_extension(
    &self,
    id: &str,
    name: Option<String>,
    file_name: Option<String>,
    file_data: Option<Vec<u8>>,
  ) -> Result<Extension, Box<dyn std::error::Error>> {
    let mut ext = self.get_extension(id)?;

    let explicit_name_provided = name.is_some();
    if let Some(new_name) = name {
      ext.name = new_name;
    }

    if let (Some(new_file_name), Some(data)) = (file_name, file_data) {
      let new_file_type = get_file_type(&new_file_name)
        .ok_or_else(|| format!("Unsupported file type: {new_file_name}"))?;

      // Remove old file
      let file_dir = self.get_file_dir(id);
      if file_dir.exists() {
        fs::remove_dir_all(&file_dir)?;
      }
      fs::create_dir_all(&file_dir)?;
      fs::write(file_dir.join(&new_file_name), &data)?;

      ext.file_name = new_file_name;
      ext.file_type = new_file_type.clone();
      ext.browser_compatibility = determine_browser_compatibility(&new_file_type);

      let (manifest_name, version, description, author, homepage_url) =
        extract_manifest_metadata(&data, &new_file_type);
      if let Some(v) = version {
        ext.version = Some(v);
      }
      if let Some(d) = description {
        ext.description = Some(d);
      }
      if let Some(a) = author {
        ext.author = Some(a);
      }
      if let Some(h) = homepage_url {
        ext.homepage_url = Some(h);
      }
      if let Some(mn) = manifest_name {
        if !explicit_name_provided {
          ext.name = mn;
        }
      }

      if let Some((icon_data, icon_ext)) = extract_icon_from_archive(&data, &new_file_type) {
        let icon_path = self.get_extension_dir(id).join(format!("icon.{icon_ext}"));
        let _ = fs::write(icon_path, icon_data);
      }
    }

    ext.updated_at = now_secs();

    let metadata_path = self.get_metadata_path(id);
    let json = serde_json::to_string_pretty(&ext)?;
    fs::write(metadata_path, json)?;

    if let Err(e) = events::emit_empty("extensions-changed") {
      log::error!("Failed to emit extensions-changed event: {e}");
    }

    if ext.sync_enabled {
      if let Some(scheduler) = crate::sync::get_global_scheduler() {
        let eid = ext.id.clone();
        tauri::async_runtime::spawn(async move {
          scheduler.queue_extension_sync(eid).await;
        });
      }
    }

    Ok(ext)
  }

  pub fn delete_extension(
    &self,
    app_handle: &tauri::AppHandle,
    id: &str,
  ) -> Result<(), Box<dyn std::error::Error>> {
    let ext = self.get_extension(id)?;
    let ext_dir = self.get_extension_dir(id);
    if ext_dir.exists() {
      fs::remove_dir_all(&ext_dir)?;
    }

    // Remove from all groups
    let mut groups_data = self.load_groups_data()?;
    for group in &mut groups_data.groups {
      group.extension_ids.retain(|eid| eid != id);
    }
    self.save_groups_data(&groups_data)?;

    if let Err(e) = events::emit_empty("extensions-changed") {
      log::error!("Failed to emit extensions-changed event: {e}");
    }

    if ext.sync_enabled {
      let ext_id = id.to_string();
      let app_handle_clone = app_handle.clone();
      tauri::async_runtime::spawn(async move {
        match crate::sync::SyncEngine::create_from_settings(&app_handle_clone).await {
          Ok(engine) => {
            if let Err(e) = engine.delete_extension(&ext_id).await {
              log::warn!("Failed to delete extension {} from sync: {}", ext_id, e);
            }
          }
          Err(e) => {
            log::debug!("Sync not configured, skipping remote deletion: {}", e);
          }
        }
      });
    }

    Ok(())
  }

  // Extension Group CRUD

  fn load_groups_data(&self) -> Result<ExtensionGroupsData, Box<dyn std::error::Error>> {
    let path = extension_groups_file();
    if !path.exists() {
      return Ok(ExtensionGroupsData { groups: Vec::new() });
    }
    let content = fs::read_to_string(path)?;
    let data: ExtensionGroupsData = serde_json::from_str(&content)?;
    Ok(data)
  }

  fn save_groups_data(&self, data: &ExtensionGroupsData) -> Result<(), Box<dyn std::error::Error>> {
    let path = extension_groups_file();
    if let Some(parent) = path.parent() {
      fs::create_dir_all(parent)?;
    }
    let json = serde_json::to_string_pretty(data)?;
    fs::write(path, json)?;
    Ok(())
  }

  pub fn create_group(&self, name: String) -> Result<ExtensionGroup, Box<dyn std::error::Error>> {
    let mut data = self.load_groups_data()?;

    if data.groups.iter().any(|g| g.name == name) {
      return Err(format!("Extension group with name '{name}' already exists").into());
    }

    let now = now_secs();
    let group = ExtensionGroup {
      id: uuid::Uuid::new_v4().to_string(),
      name,
      extension_ids: Vec::new(),
      created_at: now,
      updated_at: now,
      sync_enabled: crate::sync::is_sync_configured(),
      last_sync: None,
    };

    data.groups.push(group.clone());
    self.save_groups_data(&data)?;

    if let Err(e) = events::emit_empty("extensions-changed") {
      log::error!("Failed to emit extensions-changed event: {e}");
    }

    if group.sync_enabled {
      if let Some(scheduler) = crate::sync::get_global_scheduler() {
        let id = group.id.clone();
        tauri::async_runtime::spawn(async move {
          scheduler.queue_extension_group_sync(id).await;
        });
      }
    }

    Ok(group)
  }

  pub fn get_group(&self, id: &str) -> Result<ExtensionGroup, Box<dyn std::error::Error>> {
    let data = self.load_groups_data()?;
    data
      .groups
      .into_iter()
      .find(|g| g.id == id)
      .ok_or_else(|| format!("Extension group with id '{id}' not found").into())
  }

  pub fn list_groups(&self) -> Result<Vec<ExtensionGroup>, Box<dyn std::error::Error>> {
    let data = self.load_groups_data()?;
    Ok(data.groups)
  }

  pub fn update_group(
    &self,
    id: &str,
    name: Option<String>,
    extension_ids: Option<Vec<String>>,
  ) -> Result<ExtensionGroup, Box<dyn std::error::Error>> {
    let mut data = self.load_groups_data()?;

    if let Some(ref new_name) = name {
      if data
        .groups
        .iter()
        .any(|g| g.name == *new_name && g.id != id)
      {
        return Err(format!("Extension group with name '{new_name}' already exists").into());
      }
    }

    let group = data
      .groups
      .iter_mut()
      .find(|g| g.id == id)
      .ok_or_else(|| format!("Extension group with id '{id}' not found"))?;

    if let Some(new_name) = name {
      group.name = new_name;
    }
    if let Some(new_ids) = extension_ids {
      group.extension_ids = new_ids;
    }
    group.updated_at = now_secs();

    let updated = group.clone();
    self.save_groups_data(&data)?;

    if let Err(e) = events::emit_empty("extensions-changed") {
      log::error!("Failed to emit extensions-changed event: {e}");
    }

    if updated.sync_enabled {
      if let Some(scheduler) = crate::sync::get_global_scheduler() {
        let gid = updated.id.clone();
        tauri::async_runtime::spawn(async move {
          scheduler.queue_extension_group_sync(gid).await;
        });
      }
    }

    Ok(updated)
  }

  pub fn delete_group(
    &self,
    app_handle: &tauri::AppHandle,
    id: &str,
  ) -> Result<(), Box<dyn std::error::Error>> {
    let mut data = self.load_groups_data()?;

    let was_sync_enabled = data
      .groups
      .iter()
      .find(|g| g.id == id)
      .map(|g| g.sync_enabled)
      .unwrap_or(false);

    let initial_len = data.groups.len();
    data.groups.retain(|g| g.id != id);
    if data.groups.len() == initial_len {
      return Err(format!("Extension group with id '{id}' not found").into());
    }
    self.save_groups_data(&data)?;

    // Clear extension_group_id from profiles that used this group
    let profile_manager = crate::profile::ProfileManager::instance();
    if let Ok(profiles) = profile_manager.list_profiles() {
      for mut p in profiles {
        if p.extension_group_id.as_deref() == Some(id) {
          p.extension_group_id = None;
          let _ = profile_manager.save_profile(&p);
        }
      }
    }

    if was_sync_enabled {
      let group_id_owned = id.to_string();
      let app_handle_clone = app_handle.clone();
      tauri::async_runtime::spawn(async move {
        match crate::sync::SyncEngine::create_from_settings(&app_handle_clone).await {
          Ok(engine) => {
            if let Err(e) = engine.delete_extension_group(&group_id_owned).await {
              log::warn!(
                "Failed to delete extension group {} from sync: {}",
                group_id_owned,
                e
              );
            }
          }
          Err(e) => {
            log::debug!("Sync not configured, skipping remote deletion: {}", e);
          }
        }
      });
    }

    if let Err(e) = events::emit_empty("extensions-changed") {
      log::error!("Failed to emit extensions-changed event: {e}");
    }

    Ok(())
  }

  pub fn add_extension_to_group(
    &self,
    group_id: &str,
    extension_id: &str,
  ) -> Result<ExtensionGroup, Box<dyn std::error::Error>> {
    // Verify extension exists
    let _ = self.get_extension(extension_id)?;

    let mut data = self.load_groups_data()?;
    let group = data
      .groups
      .iter_mut()
      .find(|g| g.id == group_id)
      .ok_or_else(|| format!("Extension group with id '{group_id}' not found"))?;

    if !group.extension_ids.contains(&extension_id.to_string()) {
      group.extension_ids.push(extension_id.to_string());
      group.updated_at = now_secs();
    }

    let updated = group.clone();
    self.save_groups_data(&data)?;

    if let Err(e) = events::emit_empty("extensions-changed") {
      log::error!("Failed to emit extensions-changed event: {e}");
    }

    if updated.sync_enabled {
      if let Some(scheduler) = crate::sync::get_global_scheduler() {
        let gid = updated.id.clone();
        tauri::async_runtime::spawn(async move {
          scheduler.queue_extension_group_sync(gid).await;
        });
      }
    }

    Ok(updated)
  }

  pub fn remove_extension_from_group(
    &self,
    group_id: &str,
    extension_id: &str,
  ) -> Result<ExtensionGroup, Box<dyn std::error::Error>> {
    let mut data = self.load_groups_data()?;
    let group = data
      .groups
      .iter_mut()
      .find(|g| g.id == group_id)
      .ok_or_else(|| format!("Extension group with id '{group_id}' not found"))?;

    group.extension_ids.retain(|eid| eid != extension_id);
    group.updated_at = now_secs();

    let updated = group.clone();
    self.save_groups_data(&data)?;

    if let Err(e) = events::emit_empty("extensions-changed") {
      log::error!("Failed to emit extensions-changed event: {e}");
    }

    if updated.sync_enabled {
      if let Some(scheduler) = crate::sync::get_global_scheduler() {
        let gid = updated.id.clone();
        tauri::async_runtime::spawn(async move {
          scheduler.queue_extension_group_sync(gid).await;
        });
      }
    }

    Ok(updated)
  }

  // Sync helpers

  pub fn update_extension_internal(
    &self,
    ext: &Extension,
  ) -> Result<(), Box<dyn std::error::Error>> {
    let metadata_path = self.get_metadata_path(&ext.id);
    if let Some(parent) = metadata_path.parent() {
      fs::create_dir_all(parent)?;
    }
    let json = serde_json::to_string_pretty(ext)?;
    fs::write(metadata_path, json)?;
    Ok(())
  }

  pub fn upsert_extension_internal(
    &self,
    ext: &Extension,
  ) -> Result<(), Box<dyn std::error::Error>> {
    self.update_extension_internal(ext)
  }

  pub fn delete_extension_internal(&self, id: &str) -> Result<(), Box<dyn std::error::Error>> {
    let ext_dir = self.get_extension_dir(id);
    if ext_dir.exists() {
      fs::remove_dir_all(&ext_dir)?;
    }
    // Remove from all groups
    let mut groups_data = self.load_groups_data()?;
    for group in &mut groups_data.groups {
      group.extension_ids.retain(|eid| eid != id);
    }
    self.save_groups_data(&groups_data)?;
    Ok(())
  }

  pub fn update_group_internal(
    &self,
    group: &ExtensionGroup,
  ) -> Result<(), Box<dyn std::error::Error>> {
    let mut data = self.load_groups_data()?;
    if let Some(existing) = data.groups.iter_mut().find(|g| g.id == group.id) {
      existing.name = group.name.clone();
      existing.extension_ids = group.extension_ids.clone();
      existing.sync_enabled = group.sync_enabled;
      existing.last_sync = group.last_sync;
      existing.updated_at = group.updated_at;
      self.save_groups_data(&data)?;
    }
    Ok(())
  }

  pub fn upsert_group_internal(
    &self,
    group: &ExtensionGroup,
  ) -> Result<(), Box<dyn std::error::Error>> {
    let mut data = self.load_groups_data()?;
    if let Some(existing) = data.groups.iter_mut().find(|g| g.id == group.id) {
      existing.name = group.name.clone();
      existing.extension_ids = group.extension_ids.clone();
      existing.sync_enabled = group.sync_enabled;
      existing.last_sync = group.last_sync;
      existing.updated_at = group.updated_at;
    } else {
      data.groups.push(group.clone());
    }
    self.save_groups_data(&data)?;
    Ok(())
  }

  pub fn delete_group_internal(&self, id: &str) -> Result<(), Box<dyn std::error::Error>> {
    let mut data = self.load_groups_data()?;
    data.groups.retain(|g| g.id != id);
    self.save_groups_data(&data)?;
    Ok(())
  }

  // Compatibility validation

  pub fn validate_group_compatibility(
    &self,
    group_id: &str,
    browser: &str,
  ) -> Result<(), Box<dyn std::error::Error>> {
    let group = self.get_group(group_id)?;
    let browser_type = match browser {
      "wayfern" => "chromium",
      _ => return Err(format!("Extensions are not supported for browser '{browser}'").into()),
    };

    for ext_id in &group.extension_ids {
      let ext = self.get_extension(ext_id)?;
      if !ext
        .browser_compatibility
        .contains(&browser_type.to_string())
      {
        return Err(
          format!(
            "Extension '{}' ({}) is not compatible with {} browsers",
            ext.name, ext.file_type, browser_type
          )
          .into(),
        );
      }
    }

    Ok(())
  }

  // Launch-time installation

  pub fn install_extensions_for_profile(
    &self,
    profile: &crate::profile::BrowserProfile,
    _profile_data_path: &std::path::Path,
  ) -> Result<Vec<String>, Box<dyn std::error::Error>> {
    let group_id = match &profile.extension_group_id {
      Some(id) => id,
      None => return Ok(Vec::new()),
    };

    let group = self.get_group(group_id)?;
    if group.extension_ids.is_empty() {
      return Ok(Vec::new());
    }

    if profile.browser.as_str() != "wayfern" {
      return Ok(Vec::new());
    }

    let mut extension_paths = Vec::new();

    // Unpack Chromium extensions and return paths for --load-extension
    let unpacked_base = extensions_base_dir().join("unpacked");
    if unpacked_base.exists() {
      fs::remove_dir_all(&unpacked_base)?;
    }
    fs::create_dir_all(&unpacked_base)?;

    for ext_id in &group.extension_ids {
      if let Ok(ext) = self.get_extension(ext_id) {
        if !ext.browser_compatibility.contains(&"chromium".to_string()) {
          continue;
        }
        let src_file = self.get_file_dir(ext_id).join(&ext.file_name);
        if src_file.exists() {
          let unpack_dir = unpacked_base.join(ext_id);
          fs::create_dir_all(&unpack_dir)?;

          // Extract .crx or .zip
          match Self::unpack_extension(&src_file, &unpack_dir) {
            Ok(()) => {
              extension_paths.push(unpack_dir.to_string_lossy().to_string());
            }
            Err(e) => {
              log::warn!("Failed to unpack extension '{}': {}", ext.name, e);
            }
          }
        }
      }
    }

    Ok(extension_paths)
  }

  fn unpack_extension(
    src: &std::path::Path,
    dest: &std::path::Path,
  ) -> Result<(), Box<dyn std::error::Error>> {
    let data = fs::read(src)?;
    let mut archive = match zip::ZipArchive::new(std::io::Cursor::new(data.as_slice())) {
      Ok(a) => a,
      Err(e) => {
        // CRX files have a header before the ZIP data — try skipping the CRX header
        if let Some(zip_start) = Self::find_zip_start(&data) {
          zip::ZipArchive::new(std::io::Cursor::new(&data[zip_start..]))
            .map_err(|e2| format!("Failed to open CRX as zip after header skip: {e2}"))?
        } else {
          return Err(format!("Failed to open as zip: {e}").into());
        }
      }
    };
    for i in 0..archive.len() {
      let mut file = archive.by_index(i)?;
      let out_path = dest.join(file.mangled_name());

      if file.is_dir() {
        fs::create_dir_all(&out_path)?;
      } else {
        if let Some(parent) = out_path.parent() {
          fs::create_dir_all(parent)?;
        }
        let mut out_file = fs::File::create(&out_path)?;
        std::io::copy(&mut file, &mut out_file)?;
      }
    }

    Ok(())
  }

  fn find_zip_start(data: &[u8]) -> Option<usize> {
    // ZIP local file header magic: PK\x03\x04
    let magic = [0x50, 0x4B, 0x03, 0x04];
    data.windows(4).position(|window| window == magic)
  }

  pub fn ensure_icons_extracted(&self) {
    let extensions = match self.list_extensions() {
      Ok(exts) => exts,
      Err(_) => return,
    };

    for ext in extensions {
      let ext_dir = self.get_extension_dir(&ext.id);
      let has_icon = ext_dir
        .read_dir()
        .map(|entries| {
          entries
            .filter_map(|e| e.ok())
            .any(|e| e.file_name().to_string_lossy().starts_with("icon."))
        })
        .unwrap_or(false);

      if has_icon {
        continue;
      }

      let file_dir = self.get_file_dir(&ext.id);
      let file_path = file_dir.join(&ext.file_name);
      if let Ok(file_data) = fs::read(&file_path) {
        if let Some((icon_data, icon_ext)) = extract_icon_from_archive(&file_data, &ext.file_type) {
          let icon_path = ext_dir.join(format!("icon.{icon_ext}"));
          let _ = fs::write(icon_path, icon_data);
        }
      }

      let needs_meta_backfill = ext.version.is_none() && ext.description.is_none();

      if needs_meta_backfill {
        let file_path = file_dir.join(&ext.file_name);
        if let Ok(file_data) = fs::read(&file_path) {
          let mut updated_ext = ext.clone();
          let mut changed = false;

          if needs_meta_backfill {
            let (manifest_name, version, description, author, homepage_url) =
              extract_manifest_metadata(&file_data, &ext.file_type);
            if version.is_some()
              || description.is_some()
              || author.is_some()
              || homepage_url.is_some()
              || manifest_name.is_some()
            {
              if let Some(v) = version {
                updated_ext.version = Some(v);
              }
              if let Some(d) = description {
                updated_ext.description = Some(d);
              }
              if let Some(a) = author {
                updated_ext.author = Some(a);
              }
              if let Some(h) = homepage_url {
                updated_ext.homepage_url = Some(h);
              }
              changed = true;
            }
          }

          if changed {
            let metadata_path = self.get_metadata_path(&ext.id);
            if let Ok(json) = serde_json::to_string_pretty(&updated_ext) {
              let _ = fs::write(metadata_path, json);
            }
          }
        }
      }
    }
  }

  pub fn get_extension_icon(&self, ext_id: &str) -> Option<String> {
    let ext_dir = self.get_extension_dir(ext_id);
    let entries = ext_dir.read_dir().ok()?;
    for entry in entries.filter_map(|e| e.ok()) {
      let name = entry.file_name().to_string_lossy().to_string();
      if name.starts_with("icon.") {
        let icon_path = entry.path();
        let data = fs::read(&icon_path).ok()?;
        let ext = name.rsplit('.').next().unwrap_or("png");
        let mime = match ext {
          "png" => "image/png",
          "jpg" | "jpeg" => "image/jpeg",
          "svg" => "image/svg+xml",
          "gif" => "image/gif",
          "webp" => "image/webp",
          _ => "image/png",
        };
        use base64::Engine;
        let b64 = base64::engine::general_purpose::STANDARD.encode(&data);
        return Some(format!("data:{};base64,{}", mime, b64));
      }
    }
    None
  }
}

// Global instance
lazy_static::lazy_static! {
  pub static ref EXTENSION_MANAGER: Mutex<ExtensionManager> = Mutex::new(ExtensionManager::new());
}

// Tauri commands

#[tauri::command]
pub async fn list_extensions() -> Result<Vec<Extension>, String> {
  let mgr = EXTENSION_MANAGER.lock().unwrap();
  mgr
    .list_extensions()
    .map_err(|e| format!("Failed to list extensions: {e}"))
}

#[tauri::command]
pub fn get_extension_icon(extension_id: String) -> Option<String> {
  let manager = crate::extension_manager::ExtensionManager::new();
  manager.get_extension_icon(&extension_id)
}

#[tauri::command]
pub async fn add_extension(
  name: String,
  file_name: String,
  file_data: Vec<u8>,
) -> Result<Extension, String> {
  let mgr = EXTENSION_MANAGER.lock().unwrap();
  mgr
    .add_extension(name, file_name, file_data)
    .map_err(|e| format!("Failed to add extension: {e}"))
}

#[tauri::command]
pub async fn update_extension(
  extension_id: String,
  name: Option<String>,
  file_name: Option<String>,
  file_data: Option<Vec<u8>>,
) -> Result<Extension, String> {
  let mgr = EXTENSION_MANAGER.lock().unwrap();
  mgr
    .update_extension(&extension_id, name, file_name, file_data)
    .map_err(|e| format!("Failed to update extension: {e}"))
}

#[tauri::command]
pub async fn delete_extension(
  app_handle: tauri::AppHandle,
  extension_id: String,
) -> Result<(), String> {
  let mgr = EXTENSION_MANAGER.lock().unwrap();
  mgr
    .delete_extension(&app_handle, &extension_id)
    .map_err(|e| format!("Failed to delete extension: {e}"))
}

#[tauri::command]
pub async fn list_extension_groups() -> Result<Vec<ExtensionGroup>, String> {
  let mgr = EXTENSION_MANAGER.lock().unwrap();
  mgr
    .list_groups()
    .map_err(|e| format!("Failed to list extension groups: {e}"))
}

#[tauri::command]
pub async fn create_extension_group(name: String) -> Result<ExtensionGroup, String> {
  let mgr = EXTENSION_MANAGER.lock().unwrap();
  mgr
    .create_group(name)
    .map_err(|e| format!("Failed to create extension group: {e}"))
}

#[tauri::command]
pub async fn update_extension_group(
  group_id: String,
  name: Option<String>,
  extension_ids: Option<Vec<String>>,
) -> Result<ExtensionGroup, String> {
  let mgr = EXTENSION_MANAGER.lock().unwrap();
  mgr
    .update_group(&group_id, name, extension_ids)
    .map_err(|e| format!("Failed to update extension group: {e}"))
}

#[tauri::command]
pub async fn delete_extension_group(
  app_handle: tauri::AppHandle,
  group_id: String,
) -> Result<(), String> {
  let mgr = EXTENSION_MANAGER.lock().unwrap();
  mgr
    .delete_group(&app_handle, &group_id)
    .map_err(|e| format!("Failed to delete extension group: {e}"))
}

#[tauri::command]
pub async fn add_extension_to_group(
  group_id: String,
  extension_id: String,
) -> Result<ExtensionGroup, String> {
  let mgr = EXTENSION_MANAGER.lock().unwrap();
  mgr
    .add_extension_to_group(&group_id, &extension_id)
    .map_err(|e| format!("Failed to add extension to group: {e}"))
}

#[tauri::command]
pub async fn remove_extension_from_group(
  group_id: String,
  extension_id: String,
) -> Result<ExtensionGroup, String> {
  let mgr = EXTENSION_MANAGER.lock().unwrap();
  mgr
    .remove_extension_from_group(&group_id, &extension_id)
    .map_err(|e| format!("Failed to remove extension from group: {e}"))
}

#[tauri::command]
pub async fn assign_extension_group_to_profile(
  profile_id: String,
  extension_group_id: Option<String>,
) -> Result<crate::profile::BrowserProfile, String> {
  // Validate compatibility if assigning a group
  if let Some(ref group_id) = extension_group_id {
    let profile_manager = crate::profile::ProfileManager::instance();
    let profiles = profile_manager
      .list_profiles()
      .map_err(|e| format!("Failed to list profiles: {e}"))?;
    let profile = profiles
      .iter()
      .find(|p| p.id.to_string() == profile_id)
      .ok_or_else(|| format!("Profile '{profile_id}' not found"))?;

    let mgr = EXTENSION_MANAGER.lock().unwrap();
    mgr
      .validate_group_compatibility(group_id, &profile.browser)
      .map_err(|e| format!("{e}"))?;
  }

  let profile_manager = crate::profile::ProfileManager::instance();
  profile_manager
    .update_profile_extension_group(&profile_id, extension_group_id)
    .map_err(|e| format!("Failed to assign extension group: {e}"))
}

#[tauri::command]
pub async fn get_extension_group_for_profile(
  profile_id: String,
) -> Result<Option<ExtensionGroup>, String> {
  let profile_manager = crate::profile::ProfileManager::instance();
  let profiles = profile_manager
    .list_profiles()
    .map_err(|e| format!("Failed to list profiles: {e}"))?;
  let profile = profiles
    .iter()
    .find(|p| p.id.to_string() == profile_id)
    .ok_or_else(|| format!("Profile '{profile_id}' not found"))?;

  match &profile.extension_group_id {
    Some(group_id) => {
      let mgr = EXTENSION_MANAGER.lock().unwrap();
      match mgr.get_group(group_id) {
        Ok(group) => Ok(Some(group)),
        Err(_) => Ok(None),
      }
    }
    None => Ok(None),
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn test_get_file_type() {
    assert_eq!(get_file_type("ext.crx"), Some("crx".to_string()));
    assert_eq!(get_file_type("ext.zip"), Some("zip".to_string()));
    assert_eq!(get_file_type("ublock.xpi"), None);
    assert_eq!(get_file_type("readme.txt"), None);
    assert_eq!(get_file_type("noext"), None);
  }

  #[test]
  fn test_determine_browser_compatibility() {
    assert_eq!(
      determine_browser_compatibility("crx"),
      vec!["chromium".to_string()]
    );
    assert_eq!(
      determine_browser_compatibility("zip"),
      vec!["chromium".to_string()]
    );
    assert_eq!(determine_browser_compatibility("xpi"), Vec::<String>::new());
  }

  #[test]
  fn test_extension_manager_crud() {
    let tmp = tempfile::tempdir().unwrap();
    let _guard = crate::app_dirs::set_test_data_dir(tmp.path().to_path_buf());

    let mgr = ExtensionManager::new();

    // List empty
    let exts = mgr.list_extensions().unwrap();
    assert!(exts.is_empty());

    // Add
    let ext = mgr
      .add_extension(
        "Test Ext".to_string(),
        "test.zip".to_string(),
        vec![0, 1, 2, 3],
      )
      .unwrap();
    assert_eq!(ext.name, "Test Ext");
    assert_eq!(ext.file_type, "zip");
    assert_eq!(ext.browser_compatibility, vec!["chromium".to_string()]);

    // Get
    let fetched = mgr.get_extension(&ext.id).unwrap();
    assert_eq!(fetched.name, "Test Ext");

    // List
    let exts = mgr.list_extensions().unwrap();
    assert_eq!(exts.len(), 1);

    // Update name
    let updated = mgr
      .update_extension(&ext.id, Some("Updated".to_string()), None, None)
      .unwrap();
    assert_eq!(updated.name, "Updated");

    // Delete
    mgr.delete_extension_internal(&ext.id).unwrap();
    let exts = mgr.list_extensions().unwrap();
    assert!(exts.is_empty());
  }

  #[test]
  fn test_extension_group_crud() {
    let tmp = tempfile::tempdir().unwrap();
    let _guard = crate::app_dirs::set_test_data_dir(tmp.path().to_path_buf());

    let mgr = ExtensionManager::new();

    // Create group
    let group = mgr.create_group("My Group".to_string()).unwrap();
    assert_eq!(group.name, "My Group");
    assert!(group.extension_ids.is_empty());

    // List groups
    let groups = mgr.list_groups().unwrap();
    assert_eq!(groups.len(), 1);

    // Add extension
    let ext = mgr
      .add_extension(
        "Test Ext".to_string(),
        "test.zip".to_string(),
        vec![0, 1, 2, 3],
      )
      .unwrap();

    // Add to group
    let updated = mgr.add_extension_to_group(&group.id, &ext.id).unwrap();
    assert_eq!(updated.extension_ids.len(), 1);

    // Remove from group
    let updated = mgr.remove_extension_from_group(&group.id, &ext.id).unwrap();
    assert!(updated.extension_ids.is_empty());

    // Duplicate name check
    let err = mgr.create_group("My Group".to_string());
    assert!(err.is_err());
  }

  #[test]
  fn test_validate_group_compatibility() {
    let tmp = tempfile::tempdir().unwrap();
    let _guard = crate::app_dirs::set_test_data_dir(tmp.path().to_path_buf());

    let mgr = ExtensionManager::new();

    let chrome_ext = mgr
      .add_extension(
        "Chromium Ext".to_string(),
        "test.crx".to_string(),
        vec![0, 1, 2, 3],
      )
      .unwrap();
    let chrome_group = mgr.create_group("Chromium Group".to_string()).unwrap();
    mgr
      .add_extension_to_group(&chrome_group.id, &chrome_ext.id)
      .unwrap();

    assert!(mgr
      .validate_group_compatibility(&chrome_group.id, "wayfern")
      .is_ok());
  }

  #[test]
  fn test_find_zip_start() {
    let data = vec![0x00, 0x00, 0x50, 0x4B, 0x03, 0x04, 0xFF];
    assert_eq!(ExtensionManager::find_zip_start(&data), Some(2));

    let data = vec![0x50, 0x4B, 0x03, 0x04, 0xFF];
    assert_eq!(ExtensionManager::find_zip_start(&data), Some(0));

    let data = vec![0x00, 0x00, 0x00];
    assert_eq!(ExtensionManager::find_zip_start(&data), None);
  }

  #[test]
  fn test_delete_extension_removes_from_groups() {
    let tmp = tempfile::tempdir().unwrap();
    let _guard = crate::app_dirs::set_test_data_dir(tmp.path().to_path_buf());

    let mgr = ExtensionManager::new();

    let ext = mgr
      .add_extension("Test".to_string(), "test.zip".to_string(), vec![0, 1, 2, 3])
      .unwrap();

    let group = mgr.create_group("G1".to_string()).unwrap();
    mgr.add_extension_to_group(&group.id, &ext.id).unwrap();

    // Delete extension should remove from group
    mgr.delete_extension_internal(&ext.id).unwrap();

    let updated_group = mgr.get_group(&group.id).unwrap();
    assert!(updated_group.extension_ids.is_empty());
  }
}
