//! Configuration loading and management for mail2couch
//! 
//! This module handles loading configuration from JSON files with automatic
//! file discovery, matching the behavior of the Go implementation.

use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ConfigError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("JSON parsing error: {0}")]
    Json(#[from] serde_json::Error),
    #[error("Configuration file not found. Searched: {paths:?}")]
    NotFound { paths: Vec<PathBuf> },
    #[error("Invalid mode '{mode}' for mail source '{name}': must be 'sync' or 'archive'")]
    InvalidMode { mode: String, name: String },
}

/// Main configuration structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    #[serde(rename = "couchDb")]
    pub couch_db: CouchDbConfig,
    #[serde(rename = "mailSources")]
    pub mail_sources: Vec<MailSource>,
}

/// CouchDB connection configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CouchDbConfig {
    pub url: String,
    pub user: String,
    pub password: String,
}

/// Mail source configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MailSource {
    pub name: String,
    pub enabled: bool,
    pub protocol: String,
    pub host: String,
    pub port: u16,
    pub user: String,
    pub password: String,
    #[serde(default = "default_mode")]
    pub mode: String,
    #[serde(rename = "folderFilter", default)]
    pub folder_filter: FolderFilter,
    #[serde(rename = "messageFilter", default)]
    pub message_filter: MessageFilter,
}

/// Folder filtering configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FolderFilter {
    #[serde(default)]
    pub include: Vec<String>,
    #[serde(default)]
    pub exclude: Vec<String>,
}

/// Message filtering configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MessageFilter {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub since: Option<String>,
    #[serde(rename = "subjectKeywords", default)]
    pub subject_keywords: Vec<String>,
    #[serde(rename = "senderKeywords", default)]
    pub sender_keywords: Vec<String>,
    #[serde(rename = "recipientKeywords", default)]
    pub recipient_keywords: Vec<String>,
}

impl MessageFilter {
    /// Check if this filter has any keyword-based filters that can use IMAP SEARCH
    pub fn has_keyword_filters(&self) -> bool {
        !self.subject_keywords.is_empty() || !self.sender_keywords.is_empty()
        // Note: recipient_keywords not included as IMAP SEARCH doesn't have a TO field search
        // that works reliably across all IMAP servers
    }
}

fn default_mode() -> String {
    "archive".to_string()
}

impl MailSource {
    /// Returns true if the mail source is in sync mode
    pub fn is_sync_mode(&self) -> bool {
        self.mode == "sync"
    }

    /// Returns true if the mail source is in archive mode
    pub fn is_archive_mode(&self) -> bool {
        self.mode == "archive" || self.mode.is_empty()
    }
}

/// Command line arguments
#[derive(Debug, Clone)]
pub struct CommandLineArgs {
    pub config_path: Option<String>,
    pub max_messages: Option<u32>,
    pub dry_run: bool,
    pub generate_bash_completion: bool,
    pub help: bool,
}

impl Config {
    /// Load configuration from a file path
    pub fn load_from_path(path: &str) -> Result<Self, ConfigError> {
        let content = fs::read_to_string(path)?;
        let mut config: Config = serde_json::from_str(&content)?;
        
        // Validate and set defaults for mail sources
        for source in &mut config.mail_sources {
            if source.mode.is_empty() {
                source.mode = "archive".to_string();
            }
            if source.mode != "sync" && source.mode != "archive" {
                return Err(ConfigError::InvalidMode {
                    mode: source.mode.clone(),
                    name: source.name.clone(),
                });
            }
        }
        
        Ok(config)
    }

    /// Find configuration file in standard locations
    /// Searches in the same order as the Go implementation:
    /// 1. Path specified by command line argument
    /// 2. ./config.json (current directory)
    /// 3. ./config/config.json (config subdirectory)
    /// 4. ~/.config/mail2couch/config.json (user config directory)
    /// 5. ~/.mail2couch.json (user home directory)
    pub fn find_config_file(args: &CommandLineArgs) -> Result<PathBuf, ConfigError> {
        // If a specific path was provided, check it first
        if let Some(path) = &args.config_path {
            let path_buf = PathBuf::from(path);
            if path_buf.exists() {
                return Ok(path_buf);
            }
            return Err(ConfigError::NotFound {
                paths: vec![path_buf],
            });
        }

        // List of candidate locations
        let mut candidates = vec![
            PathBuf::from("config.json"),
            PathBuf::from("config/config.json"),
        ];

        // Add user directory paths
        if let Some(home_dir) = dirs::home_dir() {
            candidates.push(home_dir.join(".config").join("mail2couch").join("config.json"));
            candidates.push(home_dir.join(".mail2couch.json"));
        }

        // Try each candidate
        for candidate in &candidates {
            if candidate.exists() {
                return Ok(candidate.clone());
            }
        }

        Err(ConfigError::NotFound {
            paths: candidates,
        })
    }

    /// Load configuration with automatic file discovery
    pub fn load_with_discovery(args: &CommandLineArgs) -> Result<(Self, PathBuf), ConfigError> {
        let config_path = Self::find_config_file(args)?;
        let config = Self::load_from_path(config_path.to_str().unwrap())?;
        Ok((config, config_path))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use tempfile::tempdir;

    #[test]
    fn test_config_parsing() {
        let config_json = r#"
        {
            "couchDb": {
                "url": "http://localhost:5984",
                "user": "admin",
                "password": "password"
            },
            "mailSources": [
                {
                    "name": "Test Account",
                    "enabled": true,
                    "protocol": "imap",
                    "host": "imap.example.com",
                    "port": 993,
                    "user": "test@example.com",
                    "password": "testpass",
                    "mode": "archive",
                    "folderFilter": {
                        "include": ["INBOX", "Sent"],
                        "exclude": ["Trash"]
                    },
                    "messageFilter": {
                        "since": "2024-01-01",
                        "subjectKeywords": ["urgent"]
                    }
                }
            ]
        }
        "#;

        let config: Config = serde_json::from_str(config_json).unwrap();
        
        assert_eq!(config.couch_db.url, "http://localhost:5984");
        assert_eq!(config.mail_sources.len(), 1);
        
        let source = &config.mail_sources[0];
        assert_eq!(source.name, "Test Account");
        assert_eq!(source.mode, "archive");
        assert!(source.is_archive_mode());
        assert!(!source.is_sync_mode());
    }

    #[test]
    fn test_default_mode() {
        let config_json = r#"
        {
            "couchDb": {
                "url": "http://localhost:5984", 
                "user": "admin",
                "password": "password"
            },
            "mailSources": [
                {
                    "name": "Test Account",
                    "enabled": true,
                    "protocol": "imap",
                    "host": "imap.example.com",
                    "port": 993,
                    "user": "test@example.com",
                    "password": "testpass"
                }
            ]
        }
        "#;

        let config: Config = serde_json::from_str(config_json).unwrap();
        let source = &config.mail_sources[0];
        assert_eq!(source.mode, "archive");
    }

    #[test]
    fn test_config_file_discovery() {
        let temp_dir = tempdir().unwrap();
        let config_path = temp_dir.path().join("config.json");
        
        let config_content = r#"
        {
            "couchDb": {"url": "http://localhost:5984", "user": "admin", "password": "password"},
            "mailSources": []
        }
        "#;
        
        fs::write(&config_path, config_content).unwrap();
        
        // Change to temp directory for relative path test
        let original_dir = std::env::current_dir().unwrap();
        std::env::set_current_dir(&temp_dir).unwrap();
        
        let args = CommandLineArgs {
            config_path: None,
            max_messages: None,
            dry_run: false,
            generate_bash_completion: false,
            help: false,
        };
        
        let found_path = Config::find_config_file(&args).unwrap();
        assert_eq!(found_path, PathBuf::from("config.json"));
        
        // Restore original directory
        std::env::set_current_dir(original_dir).unwrap();
    }
}