use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Git profile containing user identity and authentication settings
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GitProfile {
    /// Profile display name
    pub name: String,

    /// Git user name
    pub user_name: String,

    /// Git user email
    pub user_email: String,

    /// Profile settings
    #[serde(default)]
    pub settings: ProfileSettings,

    /// SSH configuration
    #[serde(default)]
    pub ssh: Option<SshConfig>,

    /// GPG configuration
    #[serde(default)]
    pub gpg: Option<GpgConfig>,

    /// Signing key (for commit/tag signing)
    #[serde(default)]
    pub signing_key: Option<String>,

    /// Profile description
    #[serde(default)]
    pub description: Option<String>,

    /// Is this a work profile
    #[serde(default)]
    pub is_work: bool,

    /// Company/Organization name (for work profiles)
    #[serde(default)]
    pub organization: Option<String>,

    /// Personal Access Tokens (PATs) for different services
    #[serde(default)]
    pub tokens: HashMap<String, TokenConfig>,

    /// Usage statistics
    #[serde(default)]
    pub usage: UsageStats,
}

impl GitProfile {
    /// Create a new basic profile
    pub fn new(name: String, user_name: String, user_email: String) -> Self {
        Self {
            name,
            user_name,
            user_email,
            settings: ProfileSettings::default(),
            ssh: None,
            gpg: None,
            signing_key: None,
            description: None,
            is_work: false,
            organization: None,
            tokens: HashMap::new(),
            usage: UsageStats::default(),
        }
    }

    /// Create a builder for fluent API
    pub fn builder() -> GitProfileBuilder {
        GitProfileBuilder::default()
    }

    /// Validate the profile
    pub fn validate(&self) -> Result<()> {
        if self.user_name.is_empty() {
            bail!("User name cannot be empty");
        }
        
        if self.user_email.is_empty() {
            bail!("User email cannot be empty");
        }
        
        crate::utils::validators::validate_email(&self.user_email)?;
        
        if let Some(ref ssh) = self.ssh {
            ssh.validate()?;
        }
        
        if let Some(ref gpg) = self.gpg {
            gpg.validate()?;
        }
        
        for token in self.tokens.values() {
            token.validate()?;
        }
        
        Ok(())
    }

    /// Check if profile has SSH configured
    pub fn has_ssh(&self) -> bool {
        self.ssh.is_some()
    }

    /// Check if profile has GPG configured
    pub fn has_gpg(&self) -> bool {
        self.gpg.is_some() || self.signing_key.is_some()
    }

    /// Check if profile has any tokens configured
    pub fn has_tokens(&self) -> bool {
        !self.tokens.is_empty()
    }

    /// Get signing key (from GPG config or direct)
    pub fn signing_key(&self) -> Option<&str> {
        self.signing_key
            .as_ref()
            .map(|s| s.as_str())
            .or_else(|| self.gpg.as_ref().map(|g| g.key_id.as_str()))
    }

    /// Add a token to the profile
    pub fn add_token(&mut self, service: String, token: TokenConfig) {
        self.tokens.insert(service, token);
    }

    /// Get a token for a specific service
    pub fn get_token(&self, service: &str) -> Option<&TokenConfig> {
        self.tokens.get(service)
    }

    /// Remove a token from the profile
    pub fn remove_token(&mut self, service: &str) {
        self.tokens.remove(service);
    }

    /// Record usage of this profile
    pub fn record_usage(&mut self, repo_path: Option<String>) {
        self.usage.last_used = Some(chrono::Utc::now().to_rfc3339());
        self.usage.total_uses += 1;
        
        if let Some(repo) = repo_path {
            let count = self.usage.repo_usage.entry(repo).or_insert(0);
            *count += 1;
        }
    }

    /// Get usage statistics
    pub fn usage_stats(&self) -> &UsageStats {
        &self.usage
    }

    /// Apply this profile to a git repository (local config)
    pub fn apply_to_repo(&self, repo: &git2::Repository) -> Result<()> {
        let mut config = repo.config()?;
        
        config.set_str("user.name", &self.user_name)?;
        config.set_str("user.email", &self.user_email)?;
        
        if let Some(key) = self.signing_key() {
            config.set_str("user.signingkey", key)?;
            
            if self.settings.auto_sign_commits {
                config.set_bool("commit.gpgsign", true)?;
            }
            
            if self.settings.auto_sign_tags {
                config.set_bool("tag.gpgsign", true)?;
            }
        }
        
        if let Some(ref ssh) = self.ssh {
            if let Some(ref key_path) = ssh.private_key_path {
                let path_str = key_path.display().to_string();
                #[cfg(target_os = "windows")]
                {
                    config.set_str("core.sshCommand", 
                        &format!("ssh -i \"{}\"", path_str.replace('\\', "/")))?;
                }
                #[cfg(not(target_os = "windows"))]
                {
                    config.set_str("core.sshCommand", 
                        &format!("ssh -i '{}'", path_str))?;
                }
            }
        }
        
        Ok(())
    }

    /// Apply this profile globally
    pub fn apply_global(&self) -> Result<()> {
        let mut config = git2::Config::open_default()?;
        
        config.set_str("user.name", &self.user_name)?;
        config.set_str("user.email", &self.user_email)?;
        
        if let Some(key) = self.signing_key() {
            config.set_str("user.signingkey", key)?;
            
            if self.settings.auto_sign_commits {
                config.set_bool("commit.gpgsign", true)?;
            }
            
            if self.settings.auto_sign_tags {
                config.set_bool("tag.gpgsign", true)?;
            }
        }
        
        if let Some(ref ssh) = self.ssh {
            if let Some(ref key_path) = ssh.private_key_path {
                let path_str = key_path.display().to_string();
                #[cfg(target_os = "windows")]
                {
                    config.set_str("core.sshCommand", 
                        &format!("ssh -i \"{}\"", path_str.replace('\\', "/")))?;
                }
                #[cfg(not(target_os = "windows"))]
                {
                    config.set_str("core.sshCommand", 
                        &format!("ssh -i '{}'", path_str))?;
                }
            }
        }
        
        Ok(())
    }

    /// Compare with current git configuration
    pub fn compare_with_git_config(&self, repo: &git2::Repository) -> Result<ProfileComparison> {
        let config = repo.config()?;
        
        let git_user_name = config.get_string("user.name").ok();
        let git_user_email = config.get_string("user.email").ok();
        let git_signing_key = config.get_string("user.signingkey").ok();
        
        let mut comparison = ProfileComparison {
            profile_name: self.name.clone(),
            matches: true,
            differences: vec![],
        };
        
        if git_user_name.as_deref() != Some(&self.user_name) {
            comparison.matches = false;
            comparison.differences.push(ConfigDifference {
                key: "user.name".to_string(),
                profile_value: self.user_name.clone(),
                git_value: git_user_name.unwrap_or_else(|| "<not set>".to_string()),
            });
        }
        
        if git_user_email.as_deref() != Some(&self.user_email) {
            comparison.matches = false;
            comparison.differences.push(ConfigDifference {
                key: "user.email".to_string(),
                profile_value: self.user_email.clone(),
                git_value: git_user_email.unwrap_or_else(|| "<not set>".to_string()),
            });
        }
        
        if let Some(profile_key) = self.signing_key() {
            if git_signing_key.as_deref() != Some(profile_key) {
                comparison.matches = false;
                comparison.differences.push(ConfigDifference {
                    key: "user.signingkey".to_string(),
                    profile_value: profile_key.to_string(),
                    git_value: git_signing_key.unwrap_or_else(|| "<not set>".to_string()),
                });
            }
        }
        
        Ok(comparison)
    }
}

/// Profile settings
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfileSettings {
    /// Automatically sign commits
    #[serde(default)]
    pub auto_sign_commits: bool,

    /// Automatically sign tags
    #[serde(default)]
    pub auto_sign_tags: bool,

    /// Default commit format for this profile
    #[serde(default)]
    pub default_commit_format: Option<super::CommitFormat>,

    /// Use this profile for specific repositories (path patterns)
    #[serde(default)]
    pub repo_patterns: Vec<String>,

    /// Preferred LLM provider for this profile
    #[serde(default)]
    pub llm_provider: Option<String>,

    /// Custom commit message template
    #[serde(default)]
    pub commit_template: Option<String>,
}

impl Default for ProfileSettings {
    fn default() -> Self {
        Self {
            auto_sign_commits: false,
            auto_sign_tags: false,
            default_commit_format: None,
            repo_patterns: vec![],
            llm_provider: None,
            commit_template: None,
        }
    }
}

/// SSH configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SshConfig {
    /// SSH private key path
    pub private_key_path: Option<std::path::PathBuf>,

    /// SSH public key path
    pub public_key_path: Option<std::path::PathBuf>,

    /// SSH key passphrase (encrypted)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub passphrase: Option<String>,

    /// SSH agent forwarding
    #[serde(default)]
    pub agent_forwarding: bool,

    /// Custom SSH command
    #[serde(default)]
    pub ssh_command: Option<String>,

    /// Known hosts file
    #[serde(default)]
    pub known_hosts_file: Option<std::path::PathBuf>,
}

impl SshConfig {
    /// Validate SSH configuration
    pub fn validate(&self) -> Result<()> {
        if let Some(ref path) = self.private_key_path {
            if !path.exists() {
                bail!("SSH private key does not exist: {:?}", path);
            }
        }
        
        if let Some(ref path) = self.public_key_path {
            if !path.exists() {
                bail!("SSH public key does not exist: {:?}", path);
            }
        }
        
        Ok(())
    }

    /// Get SSH command for git
    pub fn git_ssh_command(&self) -> Option<String> {
        if let Some(ref cmd) = self.ssh_command {
            Some(cmd.clone())
        } else if let Some(ref key_path) = self.private_key_path {
            let path_str = key_path.display().to_string();
            #[cfg(target_os = "windows")]
            {
                Some(format!("ssh -i \"{}\"", path_str.replace('\\', "/")))
            }
            #[cfg(not(target_os = "windows"))]
            {
                Some(format!("ssh -i '{}'", path_str))
            }
        } else {
            None
        }
    }
}

/// GPG configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GpgConfig {
    /// GPG key ID
    pub key_id: String,

    /// GPG executable path
    #[serde(default = "default_gpg_program")]
    pub program: String,

    /// GPG home directory
    #[serde(default)]
    pub home_dir: Option<std::path::PathBuf>,

    /// Key passphrase (encrypted)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub passphrase: Option<String>,

    /// Use GPG agent
    #[serde(default = "default_true")]
    pub use_agent: bool,
}

impl GpgConfig {
    /// Validate GPG configuration
    pub fn validate(&self) -> Result<()> {
        crate::utils::validators::validate_gpg_key_id(&self.key_id)?;
        Ok(())
    }

    /// Get GPG program path
    pub fn program(&self) -> &str {
        &self.program
    }
}

/// Token configuration for services (GitHub, GitLab, etc.)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenConfig {
    /// Token type (personal, oauth, etc.)
    #[serde(default)]
    pub token_type: TokenType,

    /// Token scopes/permissions
    #[serde(default)]
    pub scopes: Vec<String>,

    /// Expiration date (RFC3339)
    #[serde(default)]
    pub expires_at: Option<String>,

    /// Last used timestamp
    #[serde(default)]
    pub last_used: Option<String>,

    /// Description
    #[serde(default)]
    pub description: Option<String>,

    /// Indicates if a token is stored in keyring
    #[serde(default)]
    pub has_token: bool,
}

impl TokenConfig {
    /// Create a new token config (token stored separately in keyring)
    pub fn new(token_type: TokenType) -> Self {
        Self {
            token_type,
            scopes: vec![],
            expires_at: None,
            last_used: None,
            description: None,
            has_token: true,
        }
    }

    /// Create a new token config without token
    pub fn without_token(token_type: TokenType) -> Self {
        Self {
            token_type,
            scopes: vec![],
            expires_at: None,
            last_used: None,
            description: None,
            has_token: false,
        }
    }

    /// Validate token configuration
    pub fn validate(&self) -> Result<()> {
        if !self.has_token && self.token_type != TokenType::None {
            bail!("Token is required for {:?}", self.token_type);
        }
        Ok(())
    }

    /// Record token usage
    pub fn record_usage(&mut self) {
        self.last_used = Some(chrono::Utc::now().to_rfc3339());
    }

    /// Mark that a token is stored
    pub fn set_has_token(&mut self, has_token: bool) {
        self.has_token = has_token;
    }
}

/// Token type
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum TokenType {
    None,
    Personal,
    OAuth,
    Deploy,
    App,
}

impl Default for TokenType {
    fn default() -> Self {
        Self::None
    }
}

impl std::fmt::Display for TokenType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TokenType::None => write!(f, "none"),
            TokenType::Personal => write!(f, "personal"),
            TokenType::OAuth => write!(f, "oauth"),
            TokenType::Deploy => write!(f, "deploy"),
            TokenType::App => write!(f, "app"),
        }
    }
}

/// Usage statistics for a profile
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct UsageStats {
    /// Total number of times this profile has been used
    #[serde(default)]
    pub total_uses: u64,

    /// Last used timestamp (RFC3339)
    #[serde(default)]
    pub last_used: Option<String>,

    /// Repository-specific usage counts
    #[serde(default)]
    pub repo_usage: HashMap<String, u64>,
}

/// Comparison result between profile and git configuration
#[derive(Debug, Clone)]
pub struct ProfileComparison {
    pub profile_name: String,
    pub matches: bool,
    pub differences: Vec<ConfigDifference>,
}

/// Configuration difference
#[derive(Debug, Clone)]
pub struct ConfigDifference {
    pub key: String,
    pub profile_value: String,
    pub git_value: String,
}

fn default_gpg_program() -> String {
    if cfg!(target_os = "windows") {
        "gpg.exe".to_string()
    } else {
        "gpg".to_string()
    }
}

fn default_true() -> bool {
    true
}

/// Git profile builder
#[derive(Default)]
pub struct GitProfileBuilder {
    name: Option<String>,
    user_name: Option<String>,
    user_email: Option<String>,
    settings: ProfileSettings,
    ssh: Option<SshConfig>,
    gpg: Option<GpgConfig>,
    signing_key: Option<String>,
    description: Option<String>,
    is_work: bool,
    organization: Option<String>,
    tokens: HashMap<String, TokenConfig>,
}

impl GitProfileBuilder {
    pub fn name(mut self, name: impl Into<String>) -> Self {
        self.name = Some(name.into());
        self
    }

    pub fn user_name(mut self, user_name: impl Into<String>) -> Self {
        self.user_name = Some(user_name.into());
        self
    }

    pub fn user_email(mut self, user_email: impl Into<String>) -> Self {
        self.user_email = Some(user_email.into());
        self
    }

    pub fn settings(mut self, settings: ProfileSettings) -> Self {
        self.settings = settings;
        self
    }

    pub fn ssh(mut self, ssh: SshConfig) -> Self {
        self.ssh = Some(ssh);
        self
    }

    pub fn gpg(mut self, gpg: GpgConfig) -> Self {
        self.gpg = Some(gpg);
        self
    }

    pub fn signing_key(mut self, key: impl Into<String>) -> Self {
        self.signing_key = Some(key.into());
        self
    }

    pub fn description(mut self, desc: impl Into<String>) -> Self {
        self.description = Some(desc.into());
        self
    }

    pub fn work(mut self, is_work: bool) -> Self {
        self.is_work = is_work;
        self
    }

    pub fn organization(mut self, org: impl Into<String>) -> Self {
        self.organization = Some(org.into());
        self
    }

    pub fn token(mut self, service: impl Into<String>, token: TokenConfig) -> Self {
        self.tokens.insert(service.into(), token);
        self
    }

    pub fn build(self) -> Result<GitProfile> {
        let name = self.name.ok_or_else(|| anyhow::anyhow!("Name is required"))?;
        let user_name = self.user_name.ok_or_else(|| anyhow::anyhow!("User name is required"))?;
        let user_email = self.user_email.ok_or_else(|| anyhow::anyhow!("User email is required"))?;

        Ok(GitProfile {
            name,
            user_name,
            user_email,
            settings: self.settings,
            ssh: self.ssh,
            gpg: self.gpg,
            signing_key: self.signing_key,
            description: self.description,
            is_work: self.is_work,
            organization: self.organization,
            tokens: self.tokens,
            usage: UsageStats::default(),
        })
    }
}

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

    #[test]
    fn test_profile_builder() {
        let profile = GitProfile::builder()
            .name("personal")
            .user_name("John Doe")
            .user_email("john@example.com")
            .description("Personal profile")
            .build()
            .unwrap();

        assert_eq!(profile.name, "personal");
        assert_eq!(profile.user_name, "John Doe");
        assert_eq!(profile.user_email, "john@example.com");
        assert!(profile.validate().is_ok());
    }

    #[test]
    fn test_profile_validation() {
        let profile = GitProfile::new(
            "test".to_string(),
            "".to_string(),
            "invalid-email".to_string(),
        );
        
        assert!(profile.validate().is_err());
    }

    #[test]
    fn test_token_config() {
        let token = TokenConfig::new(TokenType::Personal);
        assert!(token.validate().is_ok());
    }
}
