use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::sync::Mutex;

use crate::events;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfileGroup {
  pub id: String,
  pub name: String,
  #[serde(default)]
  pub sync_enabled: bool,
  #[serde(default)]
  pub last_sync: Option<u64>,
  /// Unix seconds of the last meaningful user edit. Source of truth for sync
  /// conflict resolution (last-write-wins); bumped on edits only.
  #[serde(default)]
  pub updated_at: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GroupWithCount {
  pub id: String,
  pub name: String,
  pub count: usize,
  #[serde(default)]
  pub sync_enabled: bool,
  #[serde(default)]
  pub last_sync: Option<u64>,
}

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

pub struct GroupManager;

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

  fn get_groups_file_path(&self) -> std::path::PathBuf {
    crate::app_dirs::data_subdir().join("groups.json")
  }

  fn load_groups_data(&self) -> Result<GroupsData, Box<dyn std::error::Error>> {
    let groups_file = self.get_groups_file_path();

    if !groups_file.exists() {
      return Ok(GroupsData { groups: Vec::new() });
    }

    let content = fs::read_to_string(groups_file)?;
    let groups_data: GroupsData = serde_json::from_str(&content)?;
    Ok(groups_data)
  }

  fn save_groups_data(&self, groups_data: &GroupsData) -> Result<(), Box<dyn std::error::Error>> {
    let groups_file = self.get_groups_file_path();

    // Ensure the parent directory exists
    if let Some(parent) = groups_file.parent() {
      fs::create_dir_all(parent)?;
    }

    let json = serde_json::to_string_pretty(groups_data)?;
    fs::write(groups_file, json)?;
    Ok(())
  }

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

  pub fn create_group(
    &self,
    _app_handle: &tauri::AppHandle,
    name: String,
  ) -> Result<ProfileGroup, Box<dyn std::error::Error>> {
    if name.trim().is_empty() {
      return Err(
        serde_json::json!({ "code": "NAME_CANNOT_BE_EMPTY" })
          .to_string()
          .into(),
      );
    }

    let mut groups_data = self.load_groups_data()?;

    // Check if group with this name already exists
    if groups_data.groups.iter().any(|g| g.name == name) {
      return Err(
        serde_json::json!({ "code": "GROUP_ALREADY_EXISTS" })
          .to_string()
          .into(),
      );
    }

    let sync_enabled = crate::sync::is_sync_configured();
    let group = ProfileGroup {
      id: uuid::Uuid::new_v4().to_string(),
      name,
      sync_enabled,
      last_sync: None,
      updated_at: Some(crate::proxy_manager::now_secs()),
    };

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

    // Emit event for reactive UI updates
    if let Err(e) = events::emit_empty("groups-changed") {
      log::error!("Failed to emit groups-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_group_sync(id).await;
        });
      }
    }

    Ok(group)
  }

  pub fn update_group(
    &self,
    _app_handle: &tauri::AppHandle,
    id: String,
    name: String,
  ) -> Result<ProfileGroup, Box<dyn std::error::Error>> {
    if name.trim().is_empty() {
      return Err(
        serde_json::json!({ "code": "NAME_CANNOT_BE_EMPTY" })
          .to_string()
          .into(),
      );
    }

    let mut groups_data = self.load_groups_data()?;

    // Check if another group with this name already exists
    if groups_data
      .groups
      .iter()
      .any(|g| g.name == name && g.id != id)
    {
      return Err(
        serde_json::json!({ "code": "GROUP_ALREADY_EXISTS" })
          .to_string()
          .into(),
      );
    }

    let group = groups_data
      .groups
      .iter_mut()
      .find(|g| g.id == id)
      .ok_or_else(|| serde_json::json!({ "code": "GROUP_NOT_FOUND" }).to_string())?;

    group.name = name;
    group.updated_at = Some(crate::proxy_manager::now_secs());
    let updated_group = group.clone();

    self.save_groups_data(&groups_data)?;

    // Emit event for reactive UI updates
    if let Err(e) = events::emit_empty("groups-changed") {
      log::error!("Failed to emit groups-changed event: {e}");
    }

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

    Ok(updated_group)
  }

  pub fn update_group_internal(
    &self,
    group: &ProfileGroup,
  ) -> Result<(), Box<dyn std::error::Error>> {
    let mut groups_data = self.load_groups_data()?;

    if let Some(existing) = groups_data.groups.iter_mut().find(|g| g.id == group.id) {
      existing.name = group.name.clone();
      existing.sync_enabled = group.sync_enabled;
      existing.last_sync = group.last_sync;
      existing.updated_at = group.updated_at;
      self.save_groups_data(&groups_data)?;
    }

    Ok(())
  }

  pub fn upsert_group_internal(
    &self,
    group: &ProfileGroup,
  ) -> Result<(), Box<dyn std::error::Error>> {
    let mut groups_data = self.load_groups_data()?;

    if let Some(existing) = groups_data.groups.iter_mut().find(|g| g.id == group.id) {
      existing.name = group.name.clone();
      existing.sync_enabled = group.sync_enabled;
      existing.last_sync = group.last_sync;
      existing.updated_at = group.updated_at;
    } else {
      groups_data.groups.push(group.clone());
    }

    self.save_groups_data(&groups_data)?;
    Ok(())
  }

  pub fn delete_group_internal(&self, id: &str) -> Result<(), Box<dyn std::error::Error>> {
    let mut groups_data = self.load_groups_data()?;
    let initial_len = groups_data.groups.len();
    groups_data.groups.retain(|g| g.id != id);
    if groups_data.groups.len() == initial_len {
      return Err(
        serde_json::json!({ "code": "GROUP_NOT_FOUND" })
          .to_string()
          .into(),
      );
    }
    self.save_groups_data(&groups_data)?;
    Ok(())
  }

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

    // Remember if sync was enabled before deleting
    let was_sync_enabled = groups_data
      .groups
      .iter()
      .find(|g| g.id == id)
      .map(|g| g.sync_enabled)
      .unwrap_or(false);

    let initial_len = groups_data.groups.len();
    groups_data.groups.retain(|g| g.id != id);

    if groups_data.groups.len() == initial_len {
      return Err(
        serde_json::json!({ "code": "GROUP_NOT_FOUND" })
          .to_string()
          .into(),
      );
    }

    self.save_groups_data(&groups_data)?;

    // If sync was enabled, also delete from S3
    if was_sync_enabled {
      let group_id_owned = id.clone();
      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_group(&group_id_owned).await {
              log::warn!("Failed to delete group {} from sync: {}", group_id_owned, e);
            } else {
              log::info!("Group {} deleted from S3 sync storage", group_id_owned);
            }
          }
          Err(e) => {
            log::debug!("Sync not configured, skipping remote deletion: {}", e);
          }
        }
      });
    }

    // Emit event for reactive UI updates
    if let Err(e) = events::emit_empty("groups-changed") {
      log::error!("Failed to emit groups-changed event: {e}");
    }

    Ok(())
  }

  pub fn get_groups_with_profile_counts(
    &self,
    profiles: &[crate::profile::BrowserProfile],
  ) -> Result<Vec<GroupWithCount>, Box<dyn std::error::Error>> {
    let groups = self.get_all_groups()?;
    let mut group_counts = HashMap::new();

    // Count profiles in each group
    for profile in profiles {
      if let Some(group_id) = &profile.group_id {
        *group_counts.entry(group_id.clone()).or_insert(0) += 1;
      }
    }

    // Create result including all groups (even those with 0 count).
    // The "Default" pseudo-group is intentionally not returned: profiles
    // without a group_id are surfaced through the "All" filter instead.
    let mut result = Vec::new();
    for group in groups {
      let count = group_counts.get(&group.id).copied().unwrap_or(0);
      result.push(GroupWithCount {
        id: group.id,
        name: group.name,
        count,
        sync_enabled: group.sync_enabled,
        last_sync: group.last_sync,
      });
    }

    Ok(result)
  }
}

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

// Helper function to get groups with counts
pub fn get_groups_with_counts(profiles: &[crate::profile::BrowserProfile]) -> Vec<GroupWithCount> {
  let group_manager = GROUP_MANAGER.lock().unwrap();
  group_manager
    .get_groups_with_profile_counts(profiles)
    .unwrap_or_default()
}

// Tauri commands
#[tauri::command]
pub async fn get_profile_groups() -> Result<Vec<ProfileGroup>, String> {
  let group_manager = GROUP_MANAGER.lock().unwrap();
  group_manager
    .get_all_groups()
    .map_err(|e| format!("Failed to get profile groups: {e}"))
}

#[tauri::command]
pub async fn get_groups_with_profile_counts() -> Result<Vec<GroupWithCount>, String> {
  let profile_manager = crate::profile::ProfileManager::instance();
  let profiles = profile_manager
    .list_profiles()
    .map_err(|e| format!("Failed to list profiles: {e}"))?;
  Ok(get_groups_with_counts(&profiles))
}

#[tauri::command]
pub async fn create_profile_group(
  app_handle: tauri::AppHandle,
  name: String,
) -> Result<ProfileGroup, String> {
  let group_manager = GROUP_MANAGER.lock().unwrap();
  group_manager
    .create_group(&app_handle, name)
    .map_err(|e| e.to_string())
}

#[tauri::command]
pub async fn update_profile_group(
  app_handle: tauri::AppHandle,
  group_id: String,
  name: String,
) -> Result<ProfileGroup, String> {
  let group_manager = GROUP_MANAGER.lock().unwrap();
  group_manager
    .update_group(&app_handle, group_id, name)
    .map_err(|e| e.to_string())
}

#[tauri::command]
pub async fn delete_profile_group(
  app_handle: tauri::AppHandle,
  group_id: String,
) -> Result<(), String> {
  let group_manager = GROUP_MANAGER.lock().unwrap();
  group_manager
    .delete_group(&app_handle, group_id)
    .map_err(|e| e.to_string())
}

#[tauri::command]
pub async fn assign_profiles_to_group(
  app_handle: tauri::AppHandle,
  profile_ids: Vec<String>,
  group_id: Option<String>,
) -> Result<(), String> {
  let profile_manager = crate::profile::ProfileManager::instance();
  profile_manager
    .assign_profiles_to_group(&app_handle, profile_ids, group_id)
    .map_err(|e| format!("Failed to assign profiles to group: {e}"))
}

#[tauri::command]
pub async fn delete_selected_profiles(
  app_handle: tauri::AppHandle,
  profile_ids: Vec<String>,
) -> Result<(), String> {
  let profile_manager = crate::profile::ProfileManager::instance();
  profile_manager
    .delete_multiple_profiles(&app_handle, profile_ids)
    .map_err(|e| format!("Failed to delete profiles: {e}"))
}
