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, TokenConfig, TokenType,
    UsageStats, ProfileComparison
};

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

    /// Default profile name
    pub default_profile: Option<String>,

    /// All configured profiles
    #[serde(default)]
    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)]
    pub repo_profiles: HashMap<String, String>,

    /// Whether to encrypt sensitive data
    #[serde(default = "default_true")]
    pub encrypt_sensitive: bool,

    /// Theme settings
    #[serde(default)]
    pub theme: ThemeConfig,
}

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(),
            encrypt_sensitive: true,
            theme: ThemeConfig::default(),
        }
    }
}

/// LLM configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmConfig {
    /// Default LLM provider
    #[serde(default = "default_llm_provider")]
    pub provider: String,

    /// OpenAI configuration
    #[serde(default)]
    pub openai: OpenAiConfig,

    /// Ollama configuration
    #[serde(default)]
    pub ollama: OllamaConfig,

    /// Anthropic Claude configuration
    #[serde(default)]
    pub anthropic: AnthropicConfig,

    /// Kimi (Moonshot AI) configuration
    #[serde(default)]
    pub kimi: KimiConfig,

    /// DeepSeek configuration
    #[serde(default)]
    pub deepseek: DeepSeekConfig,

    /// OpenRouter configuration
    #[serde(default)]
    pub openrouter: OpenRouterConfig,

    /// Custom API configuration
    #[serde(default)]
    pub custom: Option<CustomLlmConfig>,

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

impl Default for LlmConfig {
    fn default() -> Self {
        Self {
            provider: default_llm_provider(),
            openai: OpenAiConfig::default(),
            ollama: OllamaConfig::default(),
            anthropic: AnthropicConfig::default(),
            kimi: KimiConfig::default(),
            deepseek: DeepSeekConfig::default(),
            openrouter: OpenRouterConfig::default(),
            custom: None,
            max_tokens: default_max_tokens(),
            temperature: default_temperature(),
            timeout: default_timeout(),
        }
    }
}

/// OpenAI API configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenAiConfig {
    /// API key
    pub api_key: Option<String>,

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

    /// API base URL (for custom endpoints)
    #[serde(default = "default_openai_base_url")]
    pub base_url: String,
}

impl Default for OpenAiConfig {
    fn default() -> Self {
        Self {
            api_key: None,
            model: default_openai_model(),
            base_url: default_openai_base_url(),
        }
    }
}

/// Ollama configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OllamaConfig {
    /// Ollama server URL
    #[serde(default = "default_ollama_url")]
    pub url: String,

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

impl Default for OllamaConfig {
    fn default() -> Self {
        Self {
            url: default_ollama_url(),
            model: default_ollama_model(),
        }
    }
}

/// Anthropic Claude configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnthropicConfig {
    /// API key
    pub api_key: Option<String>,

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

impl Default for AnthropicConfig {
    fn default() -> Self {
        Self {
            api_key: None,
            model: default_anthropic_model(),
        }
    }
}

/// Kimi (Moonshot AI) configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KimiConfig {
    /// API key
    pub api_key: Option<String>,

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

    /// API base URL (for custom endpoints)
    #[serde(default = "default_kimi_base_url")]
    pub base_url: String,
}

impl Default for KimiConfig {
    fn default() -> Self {
        Self {
            api_key: None,
            model: default_kimi_model(),
            base_url: default_kimi_base_url(),
        }
    }
}

/// DeepSeek configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeepSeekConfig {
    /// API key
    pub api_key: Option<String>,

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

    /// API base URL (for custom endpoints)
    #[serde(default = "default_deepseek_base_url")]
    pub base_url: String,
}

impl Default for DeepSeekConfig {
    fn default() -> Self {
        Self {
            api_key: None,
            model: default_deepseek_model(),
            base_url: default_deepseek_base_url(),
        }
    }
}

/// OpenRouter configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenRouterConfig {
    /// API key
    pub api_key: Option<String>,

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

    /// API base URL (for custom endpoints)
    #[serde(default = "default_openrouter_base_url")]
    pub base_url: String,
}

impl Default for OpenRouterConfig {
    fn default() -> Self {
        Self {
            api_key: None,
            model: default_openrouter_model(),
            base_url: default_openrouter_base_url(),
        }
    }
}

/// Custom LLM API configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CustomLlmConfig {
    /// API endpoint URL
    pub url: String,

    /// API key (optional)
    pub api_key: Option<String>,

    /// Model name
    pub model: String,

    /// Request format template (JSON)
    pub request_template: String,

    /// Response path to extract content (e.g., "choices.0.message.content")
    pub response_path: String,
}

/// 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,

    /// Allow empty commits
    #[serde(default)]
    pub allow_empty: bool,

    /// Sign commits with GPG
    #[serde(default)]
    pub gpg_sign: bool,

    /// Default scope (optional)
    pub default_scope: Option<String>,

    /// Maximum subject length
    #[serde(default = "default_max_subject_length")]
    pub max_subject_length: usize,

    /// Require scope
    #[serde(default)]
    pub require_scope: bool,

    /// Require body for certain types
    #[serde(default)]
    pub require_body: bool,

    /// Types that require body
    #[serde(default = "default_body_required_types")]
    pub body_required_types: Vec<String>,
}

impl Default for CommitConfig {
    fn default() -> Self {
        Self {
            format: default_commit_format(),
            auto_generate: true,
            allow_empty: false,
            gpg_sign: false,
            default_scope: None,
            max_subject_length: default_max_subject_length(),
            require_scope: false,
            require_body: false,
            body_required_types: default_body_required_types(),
        }
    }
}

/// 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,

    /// Sign tags with GPG
    #[serde(default)]
    pub gpg_sign: bool,

    /// Include changelog in annotated tags
    #[serde(default = "default_true")]
    pub include_changelog: bool,

    /// Default annotation template
    #[serde(default)]
    pub annotation_template: Option<String>,
}

impl Default for TagConfig {
    fn default() -> Self {
        Self {
            version_prefix: default_version_prefix(),
            auto_generate: true,
            gpg_sign: false,
            include_changelog: true,
            annotation_template: None,
        }
    }
}

/// 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,

    /// Changelog format
    #[serde(default = "default_changelog_format")]
    pub format: ChangelogFormat,

    /// Include commit hashes
    #[serde(default)]
    pub include_hashes: bool,

    /// Include authors
    #[serde(default)]
    pub include_authors: bool,

    /// Group by type
    #[serde(default = "default_true")]
    pub group_by_type: bool,

    /// Custom categories
    #[serde(default)]
    pub custom_categories: Vec<ChangelogCategory>,
}

impl Default for ChangelogConfig {
    fn default() -> Self {
        Self {
            path: default_changelog_path(),
            auto_generate: true,
            format: default_changelog_format(),
            include_hashes: false,
            include_authors: false,
            group_by_type: true,
            custom_categories: vec![],
        }
    }
}

/// Changelog format
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum ChangelogFormat {
    KeepAChangelog,
    GitHubReleases,
    Custom,
}

/// Changelog category mapping
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChangelogCategory {
    /// Category title
    pub title: String,

    /// Commit types included in this category
    pub types: Vec<String>,
}

/// Theme configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThemeConfig {
    /// Enable colors
    #[serde(default = "default_true")]
    pub colors: bool,

    /// Enable icons
    #[serde(default = "default_true")]
    pub icons: bool,

    /// Preferred date format
    #[serde(default = "default_date_format")]
    pub date_format: String,
}

impl Default for ThemeConfig {
    fn default() -> Self {
        Self {
            colors: true,
            icons: true,
            date_format: default_date_format(),
        }
    }
}

// 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_max_tokens() -> u32 {
    500
}

fn default_temperature() -> f32 {
    0.6
}

fn default_timeout() -> u64 {
    30
}

fn default_openai_model() -> String {
    "gpt-4".to_string()
}

fn default_openai_base_url() -> String {
    "https://api.openai.com/v1".to_string()
}

fn default_ollama_url() -> String {
    "http://localhost:11434".to_string()
}

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

fn default_anthropic_model() -> String {
    "claude-3-sonnet-20240229".to_string()
}

fn default_kimi_model() -> String {
    "moonshot-v1-8k".to_string()
}

fn default_kimi_base_url() -> String {
    "https://api.moonshot.cn/v1".to_string()
}

fn default_deepseek_model() -> String {
    "deepseek-chat".to_string()
}

fn default_deepseek_base_url() -> String {
    "https://api.deepseek.com/v1".to_string()
}

fn default_openrouter_model() -> String {
    "openai/gpt-3.5-turbo".to_string()
}

fn default_openrouter_base_url() -> String {
    "https://openrouter.ai/api/v1".to_string()
}

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

fn default_max_subject_length() -> usize {
    100
}

fn default_body_required_types() -> Vec<String> {
    vec!["feat".to_string(), "fix".to_string()]
}

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

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

fn default_changelog_format() -> ChangelogFormat {
    ChangelogFormat::KeepAChangelog
}

fn default_date_format() -> String {
    "%Y-%m-%d".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"))
    }

    /// Get profile for a repository
    pub fn get_profile_for_repo(&self, repo_path: &str) -> Option<&GitProfile> {
        let profile_name = self.repo_profiles.get(repo_path)?;
        self.profiles.get(profile_name)
    }

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