use super::{AppConfig, GitProfile, TokenConfig};
use crate::utils::keyring::{
    KeyringManager, get_default_base_url, get_default_model, provider_needs_api_key,
};
use anyhow::{Context, Result, bail};
// use std::collections::HashMap;
use std::path::{Path, PathBuf};

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

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 = if path.exists() {
            AppConfig::load(path)?
        } else {
            AppConfig::default()
        };
        Ok(Self {
            config,
            config_path: path.to_path_buf(),
            modified: false,
            keyring: KeyringManager::new(),
        })
    }

    /// Create config manager with fresh config (ignoring existing)
    pub fn with_path_fresh(path: &Path) -> Result<Self> {
        Ok(Self {
            config: AppConfig::default(),
            config_path: path.to_path_buf(),
            modified: true,
            keyring: KeyringManager::new(),
        })
    }

    /// 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
            && !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 (stores token in keyring)
    pub fn add_token_to_profile(
        &mut self,
        profile_name: &str,
        service: String,
        token: TokenConfig,
    ) -> Result<()> {
        if !self.config.profiles.contains_key(profile_name) {
            bail!("Profile '{}' does not exist", profile_name);
        }

        if let Some(profile) = self.config.profiles.get_mut(profile_name) {
            profile.add_token(service, token);
            self.modified = true;
        }

        Ok(())
    }

    /// Store a PAT token in keyring for a profile
    pub fn store_pat_for_profile(
        &self,
        profile_name: &str,
        service: &str,
        token_value: &str,
    ) -> Result<()> {
        let profile = self
            .get_profile(profile_name)
            .ok_or_else(|| anyhow::anyhow!("Profile '{}' not found", profile_name))?;

        let user_email = &profile.user_email;

        self.keyring
            .store_pat(profile_name, user_email, service, token_value)
    }

    /// Get a PAT token from keyring for a profile
    pub fn get_pat_for_profile(&self, profile_name: &str, service: &str) -> Result<Option<String>> {
        let profile = self
            .get_profile(profile_name)
            .ok_or_else(|| anyhow::anyhow!("Profile '{}' not found", profile_name))?;

        let user_email = &profile.user_email;

        self.keyring.get_pat(profile_name, user_email, service)
    }

    /// Check if a PAT token exists for a profile
    pub fn has_pat_for_profile(&self, profile_name: &str, service: &str) -> bool {
        if let Some(profile) = self.get_profile(profile_name) {
            let user_email = &profile.user_email;
            self.keyring.has_pat(profile_name, user_email, service)
        } else {
            false
        }
    }

    /// Remove a token from a profile (deletes from keyring)
    pub fn remove_token_from_profile(&mut self, profile_name: &str, service: &str) -> Result<()> {
        if !self.config.profiles.contains_key(profile_name) {
            bail!("Profile '{}' does not exist", profile_name);
        }

        let user_email = self
            .config
            .profiles
            .get(profile_name)
            .unwrap()
            .user_email
            .clone();
        let services: Vec<String> = self
            .config
            .profiles
            .get(profile_name)
            .unwrap()
            .tokens
            .keys()
            .cloned()
            .collect();

        if !services.contains(&service.to_string()) {
            bail!(
                "Token for service '{}' not found in profile '{}'",
                service,
                profile_name
            );
        }

        self.keyring
            .delete_pat(profile_name, &user_email, service)?;

        if let Some(profile) = self.config.profiles.get_mut(profile_name) {
            profile.remove_token(service);
            self.modified = true;
        }

        Ok(())
    }

    /// Delete all PAT tokens for a profile (used when removing a profile)
    pub fn delete_all_pats_for_profile(&self, profile_name: &str) -> Result<()> {
        if let Some(profile) = self.get_profile(profile_name) {
            let user_email = &profile.user_email;
            let services: Vec<String> = profile.tokens.keys().cloned().collect();

            self.keyring
                .delete_all_pats_for_profile(profile_name, user_email, &services)?;
        }
        Ok(())
    }

    // /// 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)
    }

    /// Find a profile that matches the given user config (name, email, signing_key)
    pub fn find_matching_profile(
        &self,
        user_name: &str,
        user_email: &str,
        signing_key: Option<&str>,
    ) -> Option<&GitProfile> {
        for profile in self.config.profiles.values() {
            let name_match = profile.user_name == user_name;
            let email_match = profile.user_email == user_email;
            let key_match = match (signing_key, profile.signing_key()) {
                (Some(git_key), Some(profile_key)) => git_key == profile_key,
                (None, None) => true,
                (Some(_), None) => false,
                (None, Some(_)) => false,
            };

            if name_match && email_match && key_match {
                return Some(profile);
            }
        }
        None
    }

    /// Find profiles that partially match (same name or same email)
    pub fn find_partial_matches(&self, user_name: &str, user_email: &str) -> Vec<&GitProfile> {
        self.config
            .profiles
            .values()
            .filter(|p| p.user_name == user_name || p.user_email == user_email)
            .collect()
    }

    /// Get repo profile mapping
    pub fn get_repo_profile_name(&self, repo_path: &str) -> Option<&String> {
        self.config.repo_profiles.get(repo_path)
    }

    /// Find profiles whose repo_patterns match the given repo path
    pub fn match_profiles_by_repo_pattern(&self, repo_path: &str) -> Vec<&GitProfile> {
        self.config
            .profiles
            .values()
            .filter(|p| {
                p.settings.repo_patterns.iter().any(|pattern| {
                    let trimmed = pattern.trim_matches('*');
                    repo_path.ends_with(trimmed)
                        || repo_path.starts_with(trimmed)
                        || repo_path == trimmed
                })
            })
            .collect()
    }

    // 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) {
        let default_model = get_default_model(&provider);
        self.config.llm.provider = provider.clone();
        if self.config.llm.model.is_empty() || self.config.llm.model == "llama2" {
            self.config.llm.model = default_model.to_string();
        }
        self.modified = true;
    }

    /// Get model
    pub fn llm_model(&self) -> &str {
        &self.config.llm.model
    }

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

    /// Get base URL (returns provider default if not set)
    pub fn llm_base_url(&self) -> String {
        match &self.config.llm.base_url {
            Some(url) => url.clone(),
            None => get_default_base_url(&self.config.llm.provider).to_string(),
        }
    }

    /// Set base URL
    pub fn set_llm_base_url(&mut self, url: Option<String>) {
        self.config.llm.base_url = url;
        self.modified = true;
    }

    /// Get API key from configured storage method
    pub fn get_api_key(&self) -> Option<String> {
        // First try environment variables (always checked)
        if let Some(key) = self
            .keyring
            .get_api_key(&self.config.llm.provider)
            .unwrap_or(None)
        {
            return Some(key);
        }

        // Then try config file if configured
        if self.config.llm.api_key_storage == "config" {
            return self.config.llm.api_key.clone();
        }

        None
    }

    /// Store API key in configured storage method
    pub fn set_api_key(&self, api_key: &str) -> Result<()> {
        match self.config.llm.api_key_storage.as_str() {
            "keyring" => {
                if !self.keyring.is_available() {
                    bail!(
                        "Keyring is not available. Set QUICOMMIT_API_KEY environment variable instead or change api_key_storage to 'config'."
                    );
                }
                self.keyring
                    .store_api_key(&self.config.llm.provider, api_key)
            }
            "config" => {
                // We can't modify self.config here since self is immutable
                // This will be handled by the caller updating the config
                Ok(())
            }
            "environment" => {
                bail!(
                    "API key storage set to 'environment'. Please set QUICOMMIT_{}_API_KEY environment variable.",
                    self.config.llm.provider.to_uppercase()
                );
            }
            _ => {
                bail!(
                    "Invalid API key storage method: {}",
                    self.config.llm.api_key_storage
                );
            }
        }
    }

    /// Delete API key from configured storage method
    pub fn delete_api_key(&self) -> Result<()> {
        match self.config.llm.api_key_storage.as_str() {
            "keyring" => {
                if self.keyring.is_available() {
                    self.keyring.delete_api_key(&self.config.llm.provider)?;
                }
            }
            "config" => {
                // We can't modify self.config here since self is immutable
                // This will be handled by the caller updating the config
            }
            "environment" => {
                // Environment variables are not managed by the app
            }
            _ => {
                bail!(
                    "Invalid API key storage method: {}",
                    self.config.llm.api_key_storage
                );
            }
        }
        Ok(())
    }

    /// Check if API key is configured
    pub fn has_api_key(&self) -> bool {
        if !provider_needs_api_key(&self.config.llm.provider) {
            return true;
        }

        // Check environment variables
        if self
            .keyring
            .get_api_key(&self.config.llm.provider)
            .unwrap_or(None)
            .is_some()
        {
            return true;
        }

        // Check config file if configured
        if self.config.llm.api_key_storage == "config" {
            return self.config.llm.api_key.is_some();
        }

        false
    }

    /// Get keyring manager reference
    pub fn keyring(&self) -> &KeyringManager {
        &self.keyring
    }

    // /// Configure LLM provider with all settings
    // pub fn configure_llm(&mut self, provider: String, model: Option<String>, base_url: Option<String>, api_key: Option<&str>) -> Result<()> {
    //     self.set_llm_provider(provider.clone());

    //     if let Some(m) = model {
    //         self.set_llm_model(m);
    //     }

    //     self.set_llm_base_url(base_url);

    //     if let Some(key) = api_key {
    //         if provider_needs_api_key(&provider) {
    //             self.set_api_key(key)?;
    //         }
    //     }

    //     Ok(())
    // }

    // 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;
    }

    // Language configuration

    // /// Get output language
    // pub fn output_language(&self) -> &str {
    //     &self.config.language.output_language
    // }

    /// Set output language
    pub fn set_output_language(&mut self, language: String) {
        self.config.language.output_language = language;
        self.modified = true;
    }

    /// Get language enum from config
    pub fn get_language(&self) -> Option<super::Language> {
        super::Language::from_str(&self.config.language.output_language)
    }

    /// Check if commit types should be kept in English
    pub fn keep_types_english(&self) -> bool {
        self.config.language.keep_types_english
    }

    /// Set keep types English flag
    pub fn set_keep_types_english(&mut self, keep: bool) {
        self.config.language.keep_types_english = keep;
        self.modified = true;
    }

    /// Check if changelog types should be kept in English
    pub fn keep_changelog_types_english(&self) -> bool {
        self.config.language.keep_changelog_types_english
    }

    /// Set keep changelog types English flag
    pub fn set_keep_changelog_types_english(&mut self, keep: bool) {
        self.config.language.keep_changelog_types_english = keep;
        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,
            keyring: KeyringManager::new(),
        }
    }
}
