use anyhow::{Context, Result};
use std::collections::BTreeMap;
use std::fs;

use crate::config::types::{ClaudeSettings, Configuration, StorageMode};
use crate::utils::get_claude_settings_path;

const PER_PID_ALIAS_PREFIX: &str = "cc_auto_switch_alias_";

/// Remove trailing commas from JSON content to make it more lenient
///
/// Handles trailing commas before `}` and `]` characters, which are common
/// in hand-edited JSON files but not valid in standard JSON.
fn strip_trailing_commas(json: &str) -> String {
    // Simple approach: remove commas that appear before closing braces/brackets
    // This handles the most common case of trailing commas
    let mut result = String::with_capacity(json.len());
    let chars: Vec<char> = json.chars().collect();
    let mut i = 0;

    while i < chars.len() {
        let c = chars[i];

        // Check if this is a comma followed by optional whitespace and then } or ]
        if c == ',' {
            // Look ahead to see if the next non-whitespace char is } or ]
            let mut j = i + 1;
            while j < chars.len() && chars[j].is_whitespace() {
                j += 1;
            }

            if j < chars.len() && (chars[j] == '}' || chars[j] == ']') {
                // Skip this trailing comma
                i += 1;
                continue;
            }
        }

        result.push(c);
        i += 1;
    }

    result
}

impl ClaudeSettings {
    /// Load Claude settings from disk
    ///
    /// Reads the JSON file from the configured Claude settings directory
    /// Returns default empty settings if file doesn't exist
    /// Creates the file with default structure if it doesn't exist
    ///
    /// # Arguments
    /// * `custom_dir` - Optional custom directory for Claude settings
    ///
    /// # Errors
    /// Returns error if file exists but cannot be read or parsed
    pub fn load(custom_dir: Option<&str>) -> Result<Self> {
        let path = get_claude_settings_path(custom_dir)?;

        if !path.exists() {
            // Create default settings file if it doesn't exist
            let default_settings = ClaudeSettings::default();
            default_settings.save(custom_dir)?;
            return Ok(default_settings);
        }

        let content = fs::read_to_string(&path)
            .with_context(|| format!("Failed to read Claude settings from {}", path.display()))?;

        // Parse with better error handling for missing env field
        let mut settings: ClaudeSettings = if content.trim().is_empty() {
            ClaudeSettings::default()
        } else {
            // Strip trailing commas to handle lenient JSON
            let cleaned_content = strip_trailing_commas(&content);

            // Try to parse the cleaned content first
            match serde_json::from_str(&cleaned_content) {
                Ok(s) => s,
                Err(e) => {
                    // Provide helpful error message with the actual parse error
                    let error_msg = format!(
                        "Failed to parse Claude settings JSON at {}:\n  {}\n\n\
                         This usually means the JSON file has invalid syntax.\n\
                         Common issues:\n\
                         - Trailing commas (e.g., {{\"key\": \"value\",}})\n\
                         - Missing quotes around keys or values\n\
                         - Unescaped special characters in strings\n\n\
                         Please fix the JSON syntax in the file.",
                        path.display(),
                        e
                    );
                    return Err(anyhow::anyhow!("{}", error_msg));
                }
            }
        };

        // Ensure env field exists (handle case where it might be missing from JSON)
        if settings.env.is_empty() && !content.contains("\"env\"") {
            settings.env = BTreeMap::new();
        }

        Ok(settings)
    }

    /// Save Claude settings to disk
    ///
    /// Writes the current state to the configured Claude settings directory
    /// Creates the directory structure if it doesn't exist
    /// Ensures the env field is properly serialized
    ///
    /// # Arguments
    /// * `custom_dir` - Optional custom directory for Claude settings
    ///
    /// # Errors
    /// Returns error if directory cannot be created or file cannot be written
    pub fn save(&self, custom_dir: Option<&str>) -> Result<()> {
        let path = get_claude_settings_path(custom_dir)?;

        // Create directory if it doesn't exist
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)
                .with_context(|| format!("Failed to create directory {}", parent.display()))?;
        }

        // The custom Serialize implementation handles env field inclusion automatically
        let settings_to_save = self;

        let json = serde_json::to_string_pretty(&settings_to_save)
            .with_context(|| "Failed to serialize Claude settings")?;

        fs::write(&path, json).with_context(|| format!("Failed to write to {}", path.display()))?;

        Ok(())
    }

    /// Switch to a specific API configuration
    ///
    /// Updates the environment variables with the provided configuration
    /// Ensures env field exists before updating
    ///
    /// # Arguments
    /// * `config` - Configuration containing token, URL, and optional model settings to apply
    pub fn switch_to_config(&mut self, config: &Configuration) {
        // Ensure env field exists
        if self.env.is_empty() {
            self.env = BTreeMap::new();
        }

        // Remove all Anthropic environment variables to ensure clean state
        let env_fields = Configuration::get_env_field_names();
        for field in &env_fields {
            self.env.remove(*field);
        }

        // Set required environment variables
        self.env
            .insert("ANTHROPIC_AUTH_TOKEN".to_string(), config.token.clone());
        self.env
            .insert("ANTHROPIC_BASE_URL".to_string(), config.url.clone());

        // Set model configurations only if provided (don't set empty values)
        if let Some(model) = &config.model
            && !model.is_empty()
        {
            self.env
                .insert("ANTHROPIC_MODEL".to_string(), model.clone());
        }

        if let Some(small_fast_model) = &config.small_fast_model
            && !small_fast_model.is_empty()
        {
            self.env.insert(
                "ANTHROPIC_SMALL_FAST_MODEL".to_string(),
                small_fast_model.clone(),
            );
        }

        // Set additional configuration values that should not be removed
        if let Some(max_thinking_tokens) = config.max_thinking_tokens {
            self.env.insert(
                "ANTHROPIC_MAX_THINKING_TOKENS".to_string(),
                max_thinking_tokens.to_string(),
            );
        }

        if let Some(timeout) = config.api_timeout_ms {
            self.env
                .insert("API_TIMEOUT_MS".to_string(), timeout.to_string());
        }

        if let Some(flag) = config.claude_code_disable_nonessential_traffic {
            self.env.insert(
                "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC".to_string(),
                flag.to_string(),
            );
        }

        if let Some(model) = &config.anthropic_default_sonnet_model
            && !model.is_empty()
        {
            self.env
                .insert("ANTHROPIC_DEFAULT_SONNET_MODEL".to_string(), model.clone());
        }

        if let Some(model) = &config.anthropic_default_opus_model
            && !model.is_empty()
        {
            self.env
                .insert("ANTHROPIC_DEFAULT_OPUS_MODEL".to_string(), model.clone());
        }

        if let Some(model) = &config.anthropic_default_haiku_model
            && !model.is_empty()
        {
            self.env
                .insert("ANTHROPIC_DEFAULT_HAIKU_MODEL".to_string(), model.clone());
        }

        if let Some(model) = &config.claude_code_subagent_model
            && !model.is_empty()
        {
            self.env
                .insert("CLAUDE_CODE_SUBAGENT_MODEL".to_string(), model.clone());
        }

        if let Some(flag) = config.claude_code_disable_nonstreaming_fallback {
            self.env.insert(
                "CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK".to_string(),
                flag.to_string(),
            );
        }

        if let Some(level) = &config.claude_code_effort_level
            && !level.is_empty()
        {
            self.env
                .insert("CLAUDE_CODE_EFFORT_LEVEL".to_string(), level.clone());
        }
    }

    /// Remove Anthropic environment variables
    ///
    /// Clears all Anthropic-related environment variables from settings
    /// Used to reset to default Claude behavior
    pub fn remove_anthropic_env(&mut self) {
        // Ensure env field exists
        if self.env.is_empty() {
            self.env = BTreeMap::new();
        }

        // Remove all environment variables that can be set by configurations
        let env_fields = Configuration::get_env_field_names();
        for field in &env_fields {
            self.env.remove(*field);
        }
    }

    /// Switch to a specific API configuration with specified storage mode
    ///
    /// Updates the settings.json file based on the storage mode:
    /// - Env mode: Launch Claude with environment variables (cleans settings.json)
    /// - Config mode: Write to env field in settings.json (settings file persistence)
    ///
    /// # Arguments
    /// * `config` - Configuration containing token, URL, and optional model settings to apply
    /// * `mode` - Storage mode to use (Env or Config)
    /// * `custom_dir` - Optional custom directory for Claude settings
    ///
    /// # Errors
    /// Returns error if settings cannot be saved
    pub fn switch_to_config_with_mode(
        &mut self,
        config: &Configuration,
        mode: StorageMode,
        custom_dir: Option<&str>,
    ) -> Result<()> {
        match mode {
            StorageMode::Env => {
                // Env mode: Clean settings.json and use environment variables
                // NOTE: This will affect existing Claude sessions

                // Get environment variable names that should be cleared
                // This excludes user preference fields like DISABLE_NONESSENTIAL_TRAFFIC
                let clearable_env_fields = Configuration::get_clearable_env_field_names();

                let mut removed_fields = Vec::new();

                // Check and remove Anthropic variables from env field
                for field in &clearable_env_fields {
                    if self.env.remove(*field).is_some() {
                        removed_fields.push(field.to_string());
                    }
                }

                // If fields were removed, report what was cleaned and save
                if !removed_fields.is_empty() {
                    eprintln!("🧹 Cleaning settings.json for env mode:");
                    eprintln!("   Removed configurable fields:");
                    for field in &removed_fields {
                        eprintln!("   - {}", field);
                    }
                    eprintln!();
                    eprintln!(
                        "   Settings.json cleaned. Environment variables will be used instead."
                    );

                    // Save the cleaned settings
                    self.save(custom_dir)?;
                }

                // Env mode: Environment variables will be set directly when launching Claude
            }
            StorageMode::Config => {
                // Config mode: Write Anthropic settings to env field with UPPERCASE names
                // Check for conflicts with system environment variables (not settings.json)
                // settings.json env field is managed by this tool, so it's expected to have values

                // Get all environment variable names that can be set by configurations
                let anthropic_env_fields = Configuration::get_env_field_names();

                let mut conflicts = Vec::new();

                // Check system environment variables for Anthropic variables
                // This is the real conflict - user may have set these in their shell
                for field in &anthropic_env_fields {
                    if std::env::var(field).is_ok() {
                        conflicts.push(format!("system env: {}", field));
                    }
                }

                // If conflicts found, report error and exit
                if !conflicts.is_empty() {
                    eprintln!("❌ Conflict detected in config mode:");
                    eprintln!("   Found existing Anthropic configuration in system environment:");
                    for conflict in &conflicts {
                        eprintln!("   - {}", conflict);
                    }
                    eprintln!();
                    eprintln!(
                        "   Config mode cannot work when Anthropic environment variables are set in system env."
                    );
                    eprintln!("   Please:");
                    eprintln!("   1. Unset system environment variables, or");
                    eprintln!("   2. Use 'env' mode instead");
                    return Err(anyhow::anyhow!(
                        "Config mode conflict: Anthropic environment variables exist in system env"
                    ));
                }

                // Apply the new configuration to env field (overwrite existing values)
                self.switch_to_config(config);

                // Add the additional fields that switch_to_config doesn't handle
                if let Some(max_thinking_tokens) = config.max_thinking_tokens {
                    self.env.insert(
                        "ANTHROPIC_MAX_THINKING_TOKENS".to_string(),
                        max_thinking_tokens.to_string(),
                    );
                }

                if let Some(timeout) = config.api_timeout_ms {
                    self.env
                        .insert("API_TIMEOUT_MS".to_string(), timeout.to_string());
                }

                if let Some(flag) = config.claude_code_disable_nonessential_traffic {
                    self.env.insert(
                        "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC".to_string(),
                        flag.to_string(),
                    );
                }

                if let Some(model) = &config.anthropic_default_sonnet_model
                    && !model.is_empty()
                {
                    self.env
                        .insert("ANTHROPIC_DEFAULT_SONNET_MODEL".to_string(), model.clone());
                }

                if let Some(model) = &config.anthropic_default_opus_model
                    && !model.is_empty()
                {
                    self.env
                        .insert("ANTHROPIC_DEFAULT_OPUS_MODEL".to_string(), model.clone());
                }

                if let Some(model) = &config.anthropic_default_haiku_model
                    && !model.is_empty()
                {
                    self.env
                        .insert("ANTHROPIC_DEFAULT_HAIKU_MODEL".to_string(), model.clone());
                }

                self.save(custom_dir)?;
            }
        }

        Ok(())
    }

    /// Write the current alias for a specific session (per-PID file)
    ///
    /// Creates `~/.claude/cc_auto_switch_alias_<PID>` for per-session isolation.
    /// This allows multiple Claude sessions to display different aliases.
    ///
    /// # Arguments
    /// * `alias` - The alias name to write
    ///
    /// # Errors
    /// Returns error if the file cannot be written
    pub fn write_current_alias_for_pid(alias: &str) -> Result<()> {
        let pid = std::process::id();
        let path = Self::get_current_alias_for_pid(pid)?;

        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)
                .with_context(|| format!("Failed to create directory {}", parent.display()))?;
        }

        fs::write(&path, alias)
            .with_context(|| format!("Failed to write current alias to {}", path.display()))?;

        Ok(())
    }

    /// Clear the per-PID alias file
    ///
    /// Removes `~/.claude/cc_auto_switch_alias_<PID>` if it exists.
    pub fn clear_current_alias_for_pid() -> Result<()> {
        let pid = std::process::id();
        let path = Self::get_current_alias_for_pid(pid)?;
        if path.exists() {
            fs::remove_file(&path)
                .with_context(|| format!("Failed to remove {}", path.display()))?;
        }
        Ok(())
    }

    /// Get the path to the per-PID alias file
    fn get_current_alias_for_pid(pid: u32) -> Result<std::path::PathBuf> {
        let config_file = crate::config::get_config_storage_path()?;
        let config_dir = config_file
            .parent()
            .context("Could not get config directory")?;
        Ok(config_dir.join(format!("{PER_PID_ALIAS_PREFIX}{pid}")))
    }

    /// Clean up orphaned per-PID alias files
    ///
    /// Scans the config directory for alias files whose corresponding processes
    /// have terminated, and removes them. Also removes the legacy global
    /// `cc_auto_switch_current_alias` file (no longer used after v0.1.26).
    pub fn cleanup_orphan_alias_files() -> Result<()> {
        let config_file = crate::config::get_config_storage_path()?;
        let config_dir = config_file
            .parent()
            .context("Could not get config directory")?;

        let legacy_global = config_dir.join("cc_auto_switch_current_alias");
        if legacy_global.exists() {
            let _ = fs::remove_file(&legacy_global);
        }

        for entry in fs::read_dir(config_dir)? {
            let entry = entry?;
            let file_name = entry.file_name();
            let file_name_str = file_name.to_string_lossy();

            if let Some(pid_str) = file_name_str.strip_prefix(PER_PID_ALIAS_PREFIX)
                && let Ok(pid) = pid_str.parse::<u32>()
                && !Self::is_process_running(pid)
            {
                let _ = fs::remove_file(entry.path());
            }
        }

        Ok(())
    }

    #[cfg(unix)]
    fn is_process_running(pid: u32) -> bool {
        unsafe { libc::kill(pid as i32, 0) == 0 }
    }

    #[cfg(not(unix))]
    fn is_process_running(_pid: u32) -> bool {
        false
    }
}

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

    #[test]
    fn test_strip_trailing_commas_simple() {
        let input = r#"{"a": 1,}"#;
        let expected = r#"{"a": 1}"#;
        assert_eq!(strip_trailing_commas(input), expected);
    }

    #[test]
    fn test_strip_trailing_commas_nested_object() {
        let input = r#"{"env": {"KEY": "value",},}"#;
        let expected = r#"{"env": {"KEY": "value"}}"#;
        assert_eq!(strip_trailing_commas(input), expected);
    }

    #[test]
    fn test_strip_trailing_commas_array() {
        let input = r#"{"items": [1, 2, 3,],}"#;
        let expected = r#"{"items": [1, 2, 3]}"#;
        assert_eq!(strip_trailing_commas(input), expected);
    }

    #[test]
    fn test_strip_trailing_commas_multiline() {
        let input = r#"{
  "env": {
    "KEY": "value",
  },
}"#;
        let expected = r#"{
  "env": {
    "KEY": "value"
  }
}"#;
        assert_eq!(strip_trailing_commas(input), expected);
    }

    #[test]
    fn test_strip_trailing_commas_no_trailing() {
        let input = r#"{"a": 1, "b": 2}"#;
        assert_eq!(strip_trailing_commas(input), input);
    }

    #[test]
    fn test_strip_trailing_commas_complex() {
        let input = r#"{
  "env": {
    "ANTHROPIC_AUTH_TOKEN": "token",
    "ANTHROPIC_BASE_URL": "https://api.example.com",
  },
  "model": "claude-3-opus",
}"#;
        let expected = r#"{
  "env": {
    "ANTHROPIC_AUTH_TOKEN": "token",
    "ANTHROPIC_BASE_URL": "https://api.example.com"
  },
  "model": "claude-3-opus"
}"#;
        assert_eq!(strip_trailing_commas(input), expected);
    }

    #[test]
    fn test_strip_trailing_commas_preserves_inner_commas() {
        let input = r#"{"a": 1, "b": 2, "c": 3,}"#;
        let expected = r#"{"a": 1, "b": 2, "c": 3}"#;
        assert_eq!(strip_trailing_commas(input), expected);
    }

    #[test]
    fn test_per_pid_alias_write_and_clear() {
        use std::process;

        let pid = process::id();
        let test_alias = "test-alias-123";

        // Write per-PID alias
        ClaudeSettings::write_current_alias_for_pid(test_alias).unwrap();

        // Verify file exists and contains correct alias
        let path = ClaudeSettings::get_current_alias_for_pid(pid).unwrap();
        assert!(path.exists());
        let content = fs::read_to_string(&path).unwrap();
        assert_eq!(content, test_alias);

        // Clear per-PID alias
        ClaudeSettings::clear_current_alias_for_pid().unwrap();

        // Verify file is removed
        assert!(!path.exists());
    }

    #[test]
    fn test_per_pid_alias_path_format() {
        let pid = 12345u32;
        let path = ClaudeSettings::get_current_alias_for_pid(pid).unwrap();
        let filename = path.file_name().unwrap().to_str().unwrap();
        assert_eq!(filename, "cc_auto_switch_alias_12345");
    }
}
