use super::{AppConfig, GitProfile, TokenConfig, TokenType};
use anyhow::{bail, Context, Result};
use std::collections::HashMap;
use std::path::{Path, PathBuf};

/// Configuration manager
pub struct ConfigManager {
    config: AppConfig,
    config_path: PathBuf,
    modified: bool,
}

impl ConfigManager {
    /// Create new config manager with default path
    pub fn new() -> Result<Self> {
        let config_path = AppConfig::default_path()?;
        Self::with_path(&config_path)
    }

    /// Create config manager with specific path
    pub fn with_path(path: &Path) -> Result<Self> {
        let config = AppConfig::load(path)?;
        Ok(Self {
            config,
            config_path: path.to_path_buf(),
            modified: false,
        })
    }

    /// Get configuration reference
    pub fn config(&self) -> &AppConfig {
        &self.config
    }

    /// Get mutable configuration reference
    pub fn config_mut(&mut self) -> &mut AppConfig {
        self.modified = true;
        &mut self.config
    }

    /// Save configuration if modified
    pub fn save(&mut self) -> Result<()> {
        if self.modified {
            self.config.save(&self.config_path)?;
            self.modified = false;
        }
        Ok(())
    }

    /// Force save configuration
    pub fn force_save(&self) -> Result<()> {
        self.config.save(&self.config_path)
    }

    /// Get configuration file path
    pub fn path(&self) -> &Path {
        &self.config_path
    }

    // Profile management

    /// Add a new profile
    pub fn add_profile(&mut self, name: String, profile: GitProfile) -> Result<()> {
        if self.config.profiles.contains_key(&name) {
            bail!("Profile '{}' already exists", name);
        }
        self.config.profiles.insert(name, profile);
        self.modified = true;
        Ok(())
    }

    /// Remove a profile
    pub fn remove_profile(&mut self, name: &str) -> Result<()> {
        if !self.config.profiles.contains_key(name) {
            bail!("Profile '{}' does not exist", name);
        }
        
        if self.config.default_profile.as_ref() == Some(&name.to_string()) {
            self.config.default_profile = None;
        }
        
        self.config.repo_profiles.retain(|_, v| v != name);
        
        self.config.profiles.remove(name);
        self.modified = true;
        Ok(())
    }

    /// Update a profile
    pub fn update_profile(&mut self, name: &str, profile: GitProfile) -> Result<()> {
        if !self.config.profiles.contains_key(name) {
            bail!("Profile '{}' does not exist", name);
        }
        self.config.profiles.insert(name.to_string(), profile);
        self.modified = true;
        Ok(())
    }

    /// Get a profile
    pub fn get_profile(&self, name: &str) -> Option<&GitProfile> {
        self.config.profiles.get(name)
    }

    /// Get mutable profile
    pub fn get_profile_mut(&mut self, name: &str) -> Option<&mut GitProfile> {
        self.modified = true;
        self.config.profiles.get_mut(name)
    }

    /// List all profile names
    pub fn list_profiles(&self) -> Vec<&String> {
        self.config.profiles.keys().collect()
    }

    /// Check if profile exists
    pub fn has_profile(&self, name: &str) -> bool {
        self.config.profiles.contains_key(name)
    }

    /// Set default profile
    pub fn set_default_profile(&mut self, name: Option<String>) -> Result<()> {
        if let Some(ref n) = name {
            if !self.config.profiles.contains_key(n) {
                bail!("Profile '{}' does not exist", n);
            }
        }
        self.config.default_profile = name;
        self.modified = true;
        Ok(())
    }

    /// Get default profile
    pub fn default_profile(&self) -> Option<&GitProfile> {
        self.config
            .default_profile
            .as_ref()
            .and_then(|name| self.config.profiles.get(name))
    }

    /// Get default profile name
    pub fn default_profile_name(&self) -> Option<&String> {
        self.config.default_profile.as_ref()
    }

    /// Record profile usage
    pub fn record_profile_usage(&mut self, name: &str, repo_path: Option<String>) -> Result<()> {
        if let Some(profile) = self.config.profiles.get_mut(name) {
            profile.record_usage(repo_path);
            self.modified = true;
            Ok(())
        } else {
            bail!("Profile '{}' does not exist", name);
        }
    }

    /// Get profile usage statistics
    pub fn get_profile_usage(&self, name: &str) -> Option<&super::UsageStats> {
        self.config.profiles.get(name).map(|p| &p.usage)
    }

    // Token management

    /// Add a token to a profile
    pub fn add_token_to_profile(&mut self, profile_name: &str, service: String, token: TokenConfig) -> Result<()> {
        if let Some(profile) = self.config.profiles.get_mut(profile_name) {
            profile.add_token(service, token);
            self.modified = true;
            Ok(())
        } else {
            bail!("Profile '{}' does not exist", profile_name);
        }
    }

    /// Get a token from a profile
    pub fn get_token_from_profile(&self, profile_name: &str, service: &str) -> Option<&TokenConfig> {
        self.config.profiles.get(profile_name)?.get_token(service)
    }

    /// Remove a token from a profile
    pub fn remove_token_from_profile(&mut self, profile_name: &str, service: &str) -> Result<()> {
        if let Some(profile) = self.config.profiles.get_mut(profile_name) {
            profile.remove_token(service);
            self.modified = true;
            Ok(())
        } else {
            bail!("Profile '{}' does not exist", profile_name);
        }
    }

    /// List all tokens in a profile
    pub fn list_profile_tokens(&self, profile_name: &str) -> Option<Vec<&String>> {
        self.config.profiles.get(profile_name).map(|p| p.tokens.keys().collect())
    }

    // Repository profile management

    /// Get profile for repository
    pub fn get_repo_profile(&self, repo_path: &str) -> Option<&GitProfile> {
        self.config
            .repo_profiles
            .get(repo_path)
            .and_then(|name| self.config.profiles.get(name))
    }

    /// Set profile for repository
    pub fn set_repo_profile(&mut self, repo_path: String, profile_name: String) -> Result<()> {
        if !self.config.profiles.contains_key(&profile_name) {
            bail!("Profile '{}' does not exist", profile_name);
        }
        self.config.repo_profiles.insert(repo_path, profile_name);
        self.modified = true;
        Ok(())
    }

    /// Remove repository profile mapping
    pub fn remove_repo_profile(&mut self, repo_path: &str) {
        self.config.repo_profiles.remove(repo_path);
        self.modified = true;
    }

    /// List repository profile mappings
    pub fn list_repo_profiles(&self) -> &HashMap<String, String> {
        &self.config.repo_profiles
    }

    /// Get effective profile for a repository (repo-specific -> default)
    pub fn get_effective_profile(&self, repo_path: Option<&str>) -> Option<&GitProfile> {
        if let Some(path) = repo_path {
            if let Some(profile) = self.get_repo_profile(path) {
                return Some(profile);
            }
        }
        self.default_profile()
    }

    /// Check and compare profile with git configuration
    pub fn check_profile_config(&self, profile_name: &str, repo: &git2::Repository) -> Result<super::ProfileComparison> {
        let profile = self.get_profile(profile_name)
            .ok_or_else(|| anyhow::anyhow!("Profile '{}' not found", profile_name))?;
        profile.compare_with_git_config(repo)
    }

    // LLM configuration

    /// Get LLM provider
    pub fn llm_provider(&self) -> &str {
        &self.config.llm.provider
    }

    /// Set LLM provider
    pub fn set_llm_provider(&mut self, provider: String) {
        self.config.llm.provider = provider;
        self.modified = true;
    }

    /// Get OpenAI API key
    pub fn openai_api_key(&self) -> Option<&String> {
        self.config.llm.openai.api_key.as_ref()
    }

    /// Set OpenAI API key
    pub fn set_openai_api_key(&mut self, key: String) {
        self.config.llm.openai.api_key = Some(key);
        self.modified = true;
    }

    /// Get Anthropic API key
    pub fn anthropic_api_key(&self) -> Option<&String> {
        self.config.llm.anthropic.api_key.as_ref()
    }

    /// Set Anthropic API key
    pub fn set_anthropic_api_key(&mut self, key: String) {
        self.config.llm.anthropic.api_key = Some(key);
        self.modified = true;
    }

    /// Get Kimi API key
    pub fn kimi_api_key(&self) -> Option<&String> {
        self.config.llm.kimi.api_key.as_ref()
    }

    /// Set Kimi API key
    pub fn set_kimi_api_key(&mut self, key: String) {
        self.config.llm.kimi.api_key = Some(key);
        self.modified = true;
    }

    /// Get Kimi base URL
    pub fn kimi_base_url(&self) -> &str {
        &self.config.llm.kimi.base_url
    }

    /// Set Kimi base URL
    pub fn set_kimi_base_url(&mut self, url: String) {
        self.config.llm.kimi.base_url = url;
        self.modified = true;
    }

    /// Get DeepSeek API key
    pub fn deepseek_api_key(&self) -> Option<&String> {
        self.config.llm.deepseek.api_key.as_ref()
    }

    /// Set DeepSeek API key
    pub fn set_deepseek_api_key(&mut self, key: String) {
        self.config.llm.deepseek.api_key = Some(key);
        self.modified = true;
    }

    /// Get DeepSeek base URL
    pub fn deepseek_base_url(&self) -> &str {
        &self.config.llm.deepseek.base_url
    }

    /// Set DeepSeek base URL
    pub fn set_deepseek_base_url(&mut self, url: String) {
        self.config.llm.deepseek.base_url = url;
        self.modified = true;
    }

    /// Get OpenRouter API key
    pub fn openrouter_api_key(&self) -> Option<&String> {
        self.config.llm.openrouter.api_key.as_ref()
    }

    /// Set OpenRouter API key
    pub fn set_openrouter_api_key(&mut self, key: String) {
        self.config.llm.openrouter.api_key = Some(key);
        self.modified = true;
    }

    /// Get OpenRouter base URL
    pub fn openrouter_base_url(&self) -> &str {
        &self.config.llm.openrouter.base_url
    }

    /// Set OpenRouter base URL
    pub fn set_openrouter_base_url(&mut self, url: String) {
        self.config.llm.openrouter.base_url = url;
        self.modified = true;
    }

    // Commit configuration

    /// Get commit format
    pub fn commit_format(&self) -> super::CommitFormat {
        self.config.commit.format
    }

    /// Set commit format
    pub fn set_commit_format(&mut self, format: super::CommitFormat) {
        self.config.commit.format = format;
        self.modified = true;
    }

    /// Check if auto-generate is enabled
    pub fn auto_generate_commits(&self) -> bool {
        self.config.commit.auto_generate
    }

    /// Set auto-generate commits
    pub fn set_auto_generate_commits(&mut self, enabled: bool) {
        self.config.commit.auto_generate = enabled;
        self.modified = true;
    }

    // Tag configuration

    /// Get version prefix
    pub fn version_prefix(&self) -> &str {
        &self.config.tag.version_prefix
    }

    /// Set version prefix
    pub fn set_version_prefix(&mut self, prefix: String) {
        self.config.tag.version_prefix = prefix;
        self.modified = true;
    }

    // Changelog configuration

    /// Get changelog path
    pub fn changelog_path(&self) -> &str {
        &self.config.changelog.path
    }

    /// Set changelog path
    pub fn set_changelog_path(&mut self, path: String) {
        self.config.changelog.path = path;
        self.modified = true;
    }

    /// Export configuration to TOML string
    pub fn export(&self) -> Result<String> {
        toml::to_string_pretty(&self.config)
            .context("Failed to serialize config")
    }

    /// Import configuration from TOML string
    pub fn import(&mut self, toml_str: &str) -> Result<()> {
        self.config = toml::from_str(toml_str)
            .context("Failed to parse config")?;
        self.modified = true;
        Ok(())
    }

    /// Reset to default configuration
    pub fn reset(&mut self) {
        self.config = AppConfig::default();
        self.modified = true;
    }
}

impl Default for ConfigManager {
    fn default() -> Self {
        Self {
            config: AppConfig::default(),
            config_path: PathBuf::new(),
            modified: false,
        }
    }
}
