use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};

pub mod manager;
pub mod profile;

pub use profile::{GitProfile, ProfileComparison, TokenConfig, TokenType};

/// Application configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
    /// Configuration version for migration
    #[serde(default = "default_version")]
    pub version: String,

    /// Default profile name
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default_profile: Option<String>,

    /// All configured profiles
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub profiles: HashMap<String, GitProfile>,

    /// LLM configuration
    #[serde(default)]
    pub llm: LlmConfig,

    /// Commit configuration
    #[serde(default)]
    pub commit: CommitConfig,

    /// Tag configuration
    #[serde(default)]
    pub tag: TagConfig,

    /// Changelog configuration
    #[serde(default)]
    pub changelog: ChangelogConfig,

    /// Repository-specific profile mappings
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub repo_profiles: HashMap<String, String>,

    /// Language settings
    #[serde(default)]
    pub language: LanguageConfig,
}

impl Default for AppConfig {
    fn default() -> Self {
        Self {
            version: default_version(),
            default_profile: None,
            profiles: HashMap::new(),
            llm: LlmConfig::default(),
            commit: CommitConfig::default(),
            tag: TagConfig::default(),
            changelog: ChangelogConfig::default(),
            repo_profiles: HashMap::new(),
            language: LanguageConfig::default(),
        }
    }
}

/// LLM configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmConfig {
    /// Current LLM provider (ollama, openai, anthropic, kimi, deepseek, openrouter)
    #[serde(default = "default_llm_provider")]
    pub provider: String,

    /// Model to use
    #[serde(default = "default_model")]
    pub model: String,

    /// API base URL (optional, will use provider default if not set)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub base_url: Option<String>,

    /// Maximum tokens for generation
    #[serde(default = "default_max_tokens")]
    pub max_tokens: u32,

    /// Temperature for generation
    #[serde(default = "default_temperature")]
    pub temperature: f32,

    /// Timeout in seconds
    #[serde(default = "default_timeout")]
    pub timeout: u64,

    /// API key storage method (keyring, config, environment)
    #[serde(default = "default_api_key_storage")]
    pub api_key_storage: String,

    /// API key (stored in config for fallback)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub api_key: Option<String>,

    /// Enable thinking/reasoning mode (deepseek, kimi, anthropic)
    #[serde(default)]
    pub thinking_enabled: bool,

    /// Budget tokens for thinking mode (Anthropic Claude 4)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub thinking_budget_tokens: Option<u32>,
}

fn default_api_key_storage() -> String {
    "keyring".to_string()
}

impl Default for LlmConfig {
    fn default() -> Self {
        Self {
            provider: default_llm_provider(),
            model: default_model(),
            base_url: None,
            max_tokens: default_max_tokens(),
            temperature: default_temperature(),
            timeout: default_timeout(),
            api_key_storage: default_api_key_storage(),
            api_key: None,
            thinking_enabled: false,
            thinking_budget_tokens: None,
        }
    }
}

/// Commit configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitConfig {
    /// Default commit format
    #[serde(default = "default_commit_format")]
    pub format: CommitFormat,

    /// Enable AI generation by default
    #[serde(default = "default_true")]
    pub auto_generate: bool,
}

impl Default for CommitConfig {
    fn default() -> Self {
        Self {
            format: default_commit_format(),
            auto_generate: true,
        }
    }
}

/// Commit format
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum CommitFormat {
    Conventional,
    Commitlint,
}

impl std::fmt::Display for CommitFormat {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CommitFormat::Conventional => write!(f, "conventional"),
            CommitFormat::Commitlint => write!(f, "commitlint"),
        }
    }
}

/// Tag configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TagConfig {
    /// Default version prefix (e.g., "v")
    #[serde(default = "default_version_prefix")]
    pub version_prefix: String,

    /// Enable AI generation for tag messages
    #[serde(default = "default_true")]
    pub auto_generate: bool,
}

impl Default for TagConfig {
    fn default() -> Self {
        Self {
            version_prefix: default_version_prefix(),
            auto_generate: true,
        }
    }
}

/// Changelog configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChangelogConfig {
    /// Changelog file path
    #[serde(default = "default_changelog_path")]
    pub path: String,

    /// Enable AI generation for changelog entries
    #[serde(default = "default_true")]
    pub auto_generate: bool,
}

impl Default for ChangelogConfig {
    fn default() -> Self {
        Self {
            path: default_changelog_path(),
            auto_generate: true,
        }
    }
}

/// Language configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LanguageConfig {
    /// Output language for messages (en, zh, etc.)
    #[serde(default = "default_output_language")]
    pub output_language: String,

    /// Keep commit types in English
    #[serde(default = "default_true")]
    pub keep_types_english: bool,

    /// Keep changelog types in English
    #[serde(default = "default_true")]
    pub keep_changelog_types_english: bool,
}

impl Default for LanguageConfig {
    fn default() -> Self {
        Self {
            output_language: default_output_language(),
            keep_types_english: true,
            keep_changelog_types_english: true,
        }
    }
}

/// Supported languages
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Language {
    English,
    Chinese,
    Japanese,
    Korean,
    Spanish,
    French,
    German,
}

impl Language {
    pub fn from_str(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "en" | "english" => Some(Language::English),
            "zh" | "chinese" | "zh-cn" | "zh-tw" => Some(Language::Chinese),
            "ja" | "japanese" => Some(Language::Japanese),
            "ko" | "korean" => Some(Language::Korean),
            "es" | "spanish" => Some(Language::Spanish),
            "fr" | "french" => Some(Language::French),
            "de" | "german" => Some(Language::German),
            _ => None,
        }
    }

    pub fn to_code(&self) -> &str {
        match self {
            Language::English => "en",
            Language::Chinese => "zh",
            Language::Japanese => "ja",
            Language::Korean => "ko",
            Language::Spanish => "es",
            Language::French => "fr",
            Language::German => "de",
        }
    }

    pub fn display_name(&self) -> &str {
        match self {
            Language::English => "English",
            Language::Chinese => "中文",
            Language::Japanese => "日本語",
            Language::Korean => "한국어",
            Language::Spanish => "Español",
            Language::French => "Français",
            Language::German => "Deutsch",
        }
    }
}

// Default value functions

fn default_version() -> String {
    "1".to_string()
}

fn default_true() -> bool {
    true
}

fn default_llm_provider() -> String {
    "ollama".to_string()
}

fn default_model() -> String {
    "llama2".to_string()
}

fn default_max_tokens() -> u32 {
    500
}

fn default_temperature() -> f32 {
    0.6
}

fn default_timeout() -> u64 {
    30
}

fn default_commit_format() -> CommitFormat {
    CommitFormat::Conventional
}

fn default_version_prefix() -> String {
    "v".to_string()
}

fn default_changelog_path() -> String {
    "CHANGELOG.md".to_string()
}

fn default_output_language() -> String {
    "en".to_string()
}

impl AppConfig {
    /// Load configuration from file
    pub fn load(path: &Path) -> Result<Self> {
        if path.exists() {
            let content = fs::read_to_string(path)
                .with_context(|| format!("Failed to read config file: {:?}", path))?;
            let config: AppConfig = toml::from_str(&content)
                .with_context(|| format!("Failed to parse config file: {:?}", path))?;
            Ok(config)
        } else {
            Ok(Self::default())
        }
    }

    /// Save configuration to file
    pub fn save(&self, path: &Path) -> Result<()> {
        let content = toml::to_string_pretty(self).context("Failed to serialize config")?;

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

        fs::write(path, content)
            .with_context(|| format!("Failed to write config file: {:?}", path))?;

        Ok(())
    }

    /// Get default config path
    pub fn default_path() -> Result<PathBuf> {
        let config_dir = dirs::config_dir().context("Could not find config directory")?;
        Ok(config_dir.join("quicommit").join("config.toml"))
    }
}

/// Encrypted PAT data for export
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptedPat {
    /// Profile name
    pub profile_name: String,
    /// Service name (e.g., github, gitlab)
    pub service: String,
    /// User email (for keyring lookup)
    pub user_email: String,
    /// Encrypted token value
    pub encrypted_token: String,
}

/// Export data container with optional encrypted PATs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportData {
    /// Configuration content (TOML string)
    pub config: String,
    /// Encrypted PATs (only present when exporting with encryption)
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub encrypted_pats: Vec<EncryptedPat>,
    /// Export version for future compatibility
    #[serde(default = "default_export_version")]
    pub export_version: String,
}

fn default_export_version() -> String {
    "1".to_string()
}

impl ExportData {
    pub fn new(config: String) -> Self {
        Self {
            config,
            encrypted_pats: Vec::new(),
            export_version: default_export_version(),
        }
    }

    pub fn with_encrypted_pats(config: String, pats: Vec<EncryptedPat>) -> Self {
        Self {
            config,
            encrypted_pats: pats,
            export_version: default_export_version(),
        }
    }

    pub fn has_encrypted_pats(&self) -> bool {
        !self.encrypted_pats.is_empty()
    }
}
