use serde::{Deserialize, Serialize};
use std::env;
use std::path::PathBuf;

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct ConfigFile {
    #[serde(default)]
    pub server: Server,
    #[serde(default)]
    pub display: ConfDisplay,
    #[serde(default)]
    pub analytics: Analytics,
    #[serde(default)]
    pub filtering: Filtering,
    #[serde(default)]
    pub sharing: Sharing,
    #[serde(default)]
    pub ai: Ai,
    #[serde(default)]
    pub scraping: Scraping,
    #[serde(default)]
    pub processing: Processing,
    #[serde(default)]
    pub migration: ConfMigration,
    #[serde(default)]
    pub cli: Cli,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct ContentQuality {
    pub min_content_length: u32,
    pub max_content_length: u32,
    pub min_text_html_ratio: f32,
    pub readability_threshold: f32,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct DuplicateDetection {
    pub enabled: bool,
    pub title_similarity_threshold: f32,
    pub content_similarity_threshold: f32,
    pub check_historical_days: u32,
    pub store_fingerprints: bool,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct AdPrevention {
    pub enabled: bool,
    pub block_iframes: bool,
    pub clean_content: bool,
    pub ad_patterns: Vec<String>,
    pub preserved_elements: Vec<String>,
    pub removed_elements: Vec<String>,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub enum DefaultView {
    Compact,
    Full,
    Summary,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub enum Theme {
    Light,
    Dark,
    Auto,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub enum DefaultSort {
    #[serde(rename = "added_desc")]
    AddedDesc,
    #[serde(rename = "published_desc")]
    PublishedDesc,
    #[serde(rename = "title_asc")]
    TitleAsc,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub enum DefaultFormat {
    #[serde(rename = "text")]
    Text,
    #[serde(rename = "markdown")]
    Markdown,
    #[serde(rename = "json")]
    JSON,
    #[serde(rename = "html")]
    HTML,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub enum AiProvider {
    #[serde(rename = "ollama")]
    Ollama,
    #[serde(rename = "openai")]
    OpenAi,
    #[serde(rename = "anthropic")]
    Anthropic,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub enum DefaultOutput {
    #[serde(rename = "table")]
    Table,
    #[serde(rename = "json")]
    JSON,
    #[serde(rename = "csv")]
    CSV,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct Server {
    #[serde(default = "Server::default_host")]
    pub host: String,
    #[serde(default = "Server::default_port")]
    pub port: u16,
}
impl Server {
    fn default_host() -> String {
        Server::default().host
    }
    fn default_port() -> u16 {
        Server::default().port
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct ConfDisplay {
    #[serde(default)]
    pub default_view: DefaultView,
    #[serde(default = "ConfDisplay::default_articles_per_page")]
    pub articles_per_page: u32,
    #[serde(default = "ConfDisplay::default_show_reading_time")]
    pub show_reading_time: bool,
    #[serde(default = "ConfDisplay::default_show_word_count")]
    pub show_word_count: bool,
    #[serde(default = "ConfDisplay::default_highlight_unread")]
    pub highlight_unread: bool,
    #[serde(default)]
    pub theme: Theme,
}
impl ConfDisplay {
    fn default_articles_per_page() -> u32 {
        ConfDisplay::default().articles_per_page
    }
    fn default_show_reading_time() -> bool {
        ConfDisplay::default().show_reading_time
    }
    fn default_show_word_count() -> bool {
        ConfDisplay::default().show_word_count
    }
    fn default_highlight_unread() -> bool {
        ConfDisplay::default().highlight_unread
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct Analytics {
    #[serde(default = "Analytics::default_enabled")]
    pub enabled: bool,
    #[serde(default = "Analytics::default_track_reading_time")]
    pub track_reading_time: bool,
    #[serde(default = "Analytics::default_track_scroll_position")]
    pub track_scroll_position: bool,
    #[serde(default = "Analytics::default_retention_days")]
    pub retention_days: u32,
    #[serde(default = "Analytics::default_aggregate_older_data")]
    pub aggregate_older_data: bool,
}
impl Analytics {
    fn default_enabled() -> bool {
        Analytics::default().enabled
    }
    fn default_track_reading_time() -> bool {
        Analytics::default().track_reading_time
    }
    fn default_track_scroll_position() -> bool {
        Analytics::default().track_scroll_position
    }
    fn default_retention_days() -> u32 {
        Analytics::default().retention_days
    }
    fn default_aggregate_older_data() -> bool {
        Analytics::default().aggregate_older_data
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct Filtering {
    #[serde(default = "Filtering::default_enable_smart_suggestions")]
    pub enable_smart_suggestions: bool,
    #[serde(default = "Filtering::default_max_recent_filters")]
    pub max_recent_filters: u32,
    #[serde(default = "Filtering::default_auto_save_filters")]
    pub auto_save_filters: bool,
    #[serde(default)]
    pub default_sort: DefaultSort,
    #[serde(default = "Filtering::default_enable_geographic_hierarchy")]
    pub enable_geographic_hierarchy: bool,
    #[serde(default = "Filtering::default_auto_migrate_country_filters")]
    pub auto_migrate_country_filters: bool,
}
impl Filtering {
    fn default_enable_smart_suggestions() -> bool {
        Filtering::default().enable_smart_suggestions
    }
    fn default_max_recent_filters() -> u32 {
        Filtering::default().max_recent_filters
    }
    fn default_auto_save_filters() -> bool {
        Filtering::default().auto_save_filters
    }
    fn default_enable_geographic_hierarchy() -> bool {
        Filtering::default().enable_geographic_hierarchy
    }
    fn default_auto_migrate_country_filters() -> bool {
        Filtering::default().auto_migrate_country_filters
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct Sharing {
    #[serde(default)]
    pub default_format: DefaultFormat,
    #[serde(default = "Sharing::default_include_summary")]
    pub include_summary: bool,
    #[serde(default = "Sharing::default_include_tags")]
    pub include_tags: bool,
    #[serde(default = "Sharing::default_include_source")]
    pub include_source: bool,
    #[serde(default = "Sharing::default_copy_to_clipboard")]
    pub copy_to_clipboard: bool,
    #[serde(default)]
    pub templates: SharingTemplates,
}
impl Sharing {
    fn default_include_summary() -> bool {
        Sharing::default().include_summary
    }
    fn default_include_tags() -> bool {
        Sharing::default().include_tags
    }
    fn default_include_source() -> bool {
        Sharing::default().include_source
    }
    fn default_copy_to_clipboard() -> bool {
        Sharing::default().copy_to_clipboard
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct SharingTemplates {
    #[serde(default = "SharingTemplate::default_text")]
    pub text: SharingTemplate,
    #[serde(default = "SharingTemplate::default_markdown")]
    pub markdown: SharingTemplate,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct SharingTemplate {
    pub format: String,
}
impl Default for SharingTemplate {
    fn default() -> Self {
        // Fallback empty only if used generically; specific fields use the richer defaults below.
        Self {
            format: String::new(),
        }
    }
}
impl SharingTemplate {
    pub fn default_text() -> Self {
        SharingTemplates::default().text
    }
    pub fn default_markdown() -> Self {
        SharingTemplates::default().markdown
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct Ai {
    #[serde(default = "Ai::default_enabled")]
    pub enabled: bool,
    #[serde(default)]
    pub provider: AiProvider,
    #[serde(default = "Ai::default_timeout_seconds")]
    pub timeout_seconds: u32,
    #[serde(default)]
    pub summary: AiSummary,
    #[serde(default)]
    pub tagging: AiTagging,
}
impl Ai {
    fn default_enabled() -> bool {
        Ai::default().enabled
    }
    fn default_timeout_seconds() -> u32 {
        Ai::default().timeout_seconds
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct AiSummary {
    #[serde(default = "AiSummary::default_enabled")]
    pub enabled: bool,
    #[serde(default = "AiSummary::default_temperature")]
    pub temperature: f32,
    #[serde(default = "AiSummary::default_max_tokens")]
    pub max_tokens: u32,
}
impl AiSummary {
    fn default_enabled() -> bool {
        AiSummary::default().enabled
    }
    fn default_temperature() -> f32 {
        AiSummary::default().temperature
    }
    fn default_max_tokens() -> u32 {
        AiSummary::default().max_tokens
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct AiTagging {
    #[serde(default = "AiTagging::default_enabled")]
    pub enabled: bool,
    #[serde(default = "AiTagging::default_temperature")]
    pub temperature: f32,
    #[serde(default = "AiTagging::default_max_tokens")]
    pub max_tokens: u32,
    #[serde(default = "AiTagging::default_max_tags_per_article")]
    pub max_tags_per_article: u32,
    #[serde(default = "AiTagging::default_min_confidence_threshold")]
    pub min_confidence_threshold: f32,
    #[serde(default = "AiTagging::default_enable_geographic_tagging")]
    pub enable_geographic_tagging: bool,
    #[serde(default = "AiTagging::default_enable_category_tagging")]
    pub enable_category_tagging: bool,
    #[serde(default = "AiTagging::default_geographic_hierarchy_levels")]
    pub geographic_hierarchy_levels: u32,
}
impl AiTagging {
    fn default_enabled() -> bool {
        AiTagging::default().enabled
    }
    fn default_temperature() -> f32 {
        AiTagging::default().temperature
    }
    fn default_max_tokens() -> u32 {
        AiTagging::default().max_tokens
    }
    fn default_max_tags_per_article() -> u32 {
        AiTagging::default().max_tags_per_article
    }
    fn default_min_confidence_threshold() -> f32 {
        AiTagging::default().min_confidence_threshold
    }
    fn default_enable_geographic_tagging() -> bool {
        AiTagging::default().enable_geographic_tagging
    }
    fn default_enable_category_tagging() -> bool {
        AiTagging::default().enable_category_tagging
    }
    fn default_geographic_hierarchy_levels() -> u32 {
        AiTagging::default().geographic_hierarchy_levels
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct Scraping {
    #[serde(default = "Scraping::default_timeout_seconds")]
    pub timeout_seconds: u32,
    #[serde(default = "Scraping::default_max_retries")]
    pub max_retries: u32,
    #[serde(default = "Scraping::default_max_content_length")]
    pub max_content_length: u32,
    #[serde(default = "Scraping::default_respect_robots_txt")]
    pub respect_robots_txt: bool,
    #[serde(default = "Scraping::default_rate_limit_delay_ms")]
    pub rate_limit_delay_ms: u32,
    #[serde(default)]
    pub content_quality: ContentQuality,
    #[serde(default)]
    pub duplicate_detection: DuplicateDetection,
    #[serde(default)]
    pub ad_prevention: AdPrevention,
}
impl Scraping {
    fn default_timeout_seconds() -> u32 {
        Scraping::default().timeout_seconds
    }
    fn default_max_retries() -> u32 {
        Scraping::default().max_retries
    }
    fn default_max_content_length() -> u32 {
        Scraping::default().max_content_length
    }
    fn default_respect_robots_txt() -> bool {
        Scraping::default().respect_robots_txt
    }
    fn default_rate_limit_delay_ms() -> u32 {
        Scraping::default().rate_limit_delay_ms
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct Processing {
    #[serde(default = "Processing::default_batch_size")]
    pub batch_size: u32,
    #[serde(default = "Processing::default_max_concurrent")]
    pub max_concurrent: u32,
    #[serde(default = "Processing::default_retry_attempts")]
    pub retry_attempts: u32,
    #[serde(default = "Processing::default_priority_manual")]
    pub priority_manual: bool,
    #[serde(default = "Processing::default_auto_mark_read_on_view")]
    pub auto_mark_read_on_view: bool,
}
impl Processing {
    fn default_batch_size() -> u32 {
        Processing::default().batch_size
    }
    fn default_max_concurrent() -> u32 {
        Processing::default().max_concurrent
    }
    fn default_retry_attempts() -> u32 {
        Processing::default().retry_attempts
    }
    fn default_priority_manual() -> bool {
        Processing::default().priority_manual
    }
    fn default_auto_mark_read_on_view() -> bool {
        Processing::default().auto_mark_read_on_view
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct ConfMigration {
    #[serde(default = "ConfMigration::default_auto_convert_country_filters")]
    pub auto_convert_country_filters: bool,
    #[serde(default = "ConfMigration::default_preserve_legacy_data")]
    pub preserve_legacy_data: bool,
    #[serde(default = "ConfMigration::default_migration_batch_size")]
    pub migration_batch_size: u32,
}
impl ConfMigration {
    fn default_auto_convert_country_filters() -> bool {
        ConfMigration::default().auto_convert_country_filters
    }
    fn default_preserve_legacy_data() -> bool {
        ConfMigration::default().preserve_legacy_data
    }
    fn default_migration_batch_size() -> u32 {
        ConfMigration::default().migration_batch_size
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct Cli {
    #[serde(default)]
    pub default_output: DefaultOutput,
    #[serde(default = "Cli::default_pager_command")]
    pub pager_command: String,
    #[serde(default = "Cli::default_show_progress")]
    pub show_progress: bool,
    #[serde(default = "Cli::default_auto_confirm_bulk")]
    pub auto_confirm_bulk: bool,
    #[serde(default = "Cli::default_show_geographic_hierarchy")]
    pub show_geographic_hierarchy: bool,
}
impl Cli {
    fn default_pager_command() -> String {
        Cli::default().pager_command
    }
    fn default_show_progress() -> bool {
        Cli::default().show_progress
    }
    fn default_auto_confirm_bulk() -> bool {
        Cli::default().auto_confirm_bulk
    }
    fn default_show_geographic_hierarchy() -> bool {
        Cli::default().show_geographic_hierarchy
    }
}

impl Default for ContentQuality {
    fn default() -> Self {
        Self {
            min_content_length: 100,
            max_content_length: 500_000,
            min_text_html_ratio: 0.3,
            readability_threshold: 0.6,
        }
    }
}

impl Default for DuplicateDetection {
    fn default() -> Self {
        Self {
            enabled: true,
            title_similarity_threshold: 0.90,
            content_similarity_threshold: 0.85,
            check_historical_days: 30,
            store_fingerprints: true,
        }
    }
}

impl Default for AdPrevention {
    fn default() -> Self {
        Self {
            enabled: true,
            block_iframes: true,
            clean_content: true,
            ad_patterns: vec![
                "sponsored-content".to_string(),
                "advertisement".to_string(),
                "promoted-post".to_string(),
                "partner-content".to_string(),
                "ad-wrapper".to_string(),
                "sponsored".to_string(),
            ],
            preserved_elements: vec![
                "article".to_string(),
                "p".to_string(),
                "h1".to_string(),
                "h2".to_string(),
                "h3".to_string(),
                "img".to_string(),
                "figure".to_string(),
                "figcaption".to_string(),
            ],
            removed_elements: vec![
                "aside".to_string(),
                "iframe".to_string(),
                "script".to_string(),
                "style".to_string(),
                "ins".to_string(),
                "form".to_string(),
                "button".to_string(),
                ".ad".to_string(),
                "#ad".to_string(),
                "[class*='ad-']".to_string(),
                "[id*='ad-']".to_string(),
            ],
        }
    }
}

impl DefaultView {
    pub fn display_name(&self) -> &str {
        match self {
            DefaultView::Compact => "Compact",
            DefaultView::Full => "Full Article",
            DefaultView::Summary => "Summary",
        }
    }

    pub fn from_string(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "compact" => Some(DefaultView::Compact),
            "full" => Some(DefaultView::Full),
            "summary" => Some(DefaultView::Summary),
            _ => None,
        }
    }
}

impl Default for DefaultView {
    fn default() -> Self {
        DefaultView::Compact
    }
}

impl Theme {
    pub fn display_name(&self) -> &str {
        match self {
            Theme::Light => "Light Mode",
            Theme::Dark => "Dark Mode",
            Theme::Auto => "Auto (System)",
        }
    }

    pub fn is_dark_mode(&self, system_prefers_dark: bool) -> bool {
        match self {
            Theme::Light => false,
            Theme::Dark => true,
            Theme::Auto => system_prefers_dark,
        }
    }
}

impl Default for Theme {
    fn default() -> Self {
        Theme::Auto
    }
}

impl DefaultSort {
    pub fn display_name(&self) -> &str {
        match self {
            DefaultSort::AddedDesc => "Recently Added",
            DefaultSort::PublishedDesc => "Recently Published",
            DefaultSort::TitleAsc => "Title A-Z",
        }
    }

    pub fn to_sql_order(&self) -> &str {
        match self {
            DefaultSort::AddedDesc => "added_at DESC",
            DefaultSort::PublishedDesc => "published_at DESC",
            DefaultSort::TitleAsc => "title ASC",
        }
    }
}

impl Default for DefaultSort {
    fn default() -> Self {
        DefaultSort::AddedDesc
    }
}

impl DefaultFormat {
    pub fn display_name(&self) -> &str {
        match self {
            DefaultFormat::Text => "Plain Text",
            DefaultFormat::Markdown => "Markdown",
            DefaultFormat::JSON => "JSON",
            DefaultFormat::HTML => "HTML",
        }
    }

    pub fn mime_type(&self) -> &str {
        match self {
            DefaultFormat::Text => "text/plain",
            DefaultFormat::Markdown => "text/markdown",
            DefaultFormat::JSON => "application/json",
            DefaultFormat::HTML => "text/html",
        }
    }
}

impl Default for DefaultFormat {
    fn default() -> Self {
        DefaultFormat::Text
    }
}

impl AiProvider {
    pub fn display_name(&self) -> &str {
        match self {
            AiProvider::Ollama => "Ollama",
            AiProvider::OpenAi => "OpenAI",
            AiProvider::Anthropic => "Anthropic",
        }
    }
}

impl Default for AiProvider {
    fn default() -> Self {
        AiProvider::Ollama
    }
}

impl DefaultOutput {
    pub fn display_name(&self) -> &str {
        match self {
            DefaultOutput::Table => "Table",
            DefaultOutput::JSON => "JSON",
            DefaultOutput::CSV => "CSV",
        }
    }
}

impl Default for DefaultOutput {
    fn default() -> Self {
        DefaultOutput::Table
    }
}

impl Default for Cli {
    fn default() -> Self {
        Self {
            default_output: DefaultOutput::Table,
            pager_command: "less -R".to_string(),
            show_progress: true,
            auto_confirm_bulk: false,
            show_geographic_hierarchy: true,
        }
    }
}

impl Default for ConfMigration {
    fn default() -> Self {
        Self {
            auto_convert_country_filters: true,
            preserve_legacy_data: true,
            migration_batch_size: 100,
        }
    }
}

impl Default for Processing {
    fn default() -> Self {
        Self {
            batch_size: 10,
            max_concurrent: 5,
            retry_attempts: 3,
            priority_manual: true,
            auto_mark_read_on_view: false,
        }
    }
}

impl Default for Scraping {
    fn default() -> Self {
        Self {
            timeout_seconds: 30,
            max_retries: 3,
            max_content_length: 100_000,
            respect_robots_txt: true,
            rate_limit_delay_ms: 1000,
            content_quality: ContentQuality::default(),
            duplicate_detection: DuplicateDetection::default(),
            ad_prevention: AdPrevention::default(),
        }
    }
}

impl Default for Ai {
    fn default() -> Self {
        Self {
            enabled: true,
            provider: AiProvider::default(),
            timeout_seconds: 120,
            summary: AiSummary::default(),
            tagging: AiTagging::default(),
        }
    }
}

impl Default for AiSummary {
    fn default() -> Self {
        Self {
            enabled: true,
            temperature: 0.1,
            max_tokens: 1024,
        }
    }
}

impl Default for AiTagging {
    fn default() -> Self {
        Self {
            enabled: true,
            temperature: 0.3,
            max_tokens: 200,
            max_tags_per_article: 10,
            min_confidence_threshold: 0.7,
            enable_geographic_tagging: true,
            enable_category_tagging: true,
            geographic_hierarchy_levels: 3,
        }
    }
}

impl Default for Sharing {
    fn default() -> Self {
        Self {
            default_format: DefaultFormat::default(),
            include_summary: true,
            include_tags: true,
            include_source: true,
            copy_to_clipboard: true,
            templates: SharingTemplates::default(),
        }
    }
}

impl Default for SharingTemplates {
    fn default() -> Self {
        Self {
            text: SharingTemplate {
                format: r#"📰 {title}

{summary}

🏷️ Tags: {tags}
🌍 Location: {geographic_tags}
🔗 Source: {url}
📅 Published: {published_at}

Shared via Owly News Summariser
"#
                .to_string(),
            },
            markdown: SharingTemplate {
                format: r#"# {title}

{summary}

**Tags:** {tags}
**Location:** {geographic_tags}
**Source:** [{url}]({url})
**Published:** {published_at}

---
*Shared via Owly News Summariser*
"#
                .to_string(),
            },
        }
    }
}

impl Default for Filtering {
    fn default() -> Self {
        Self {
            enable_smart_suggestions: true,
            max_recent_filters: 10,
            auto_save_filters: true,
            default_sort: DefaultSort::AddedDesc,
            enable_geographic_hierarchy: true,
            auto_migrate_country_filters: true,
        }
    }
}

impl Default for Analytics {
    fn default() -> Self {
        Self {
            enabled: true,
            track_reading_time: true,
            track_scroll_position: true,
            retention_days: 365,
            aggregate_older_data: true,
        }
    }
}

impl Default for ConfDisplay {
    fn default() -> Self {
        Self {
            default_view: DefaultView::default(),
            articles_per_page: 50,
            show_reading_time: true,
            show_word_count: false,
            highlight_unread: true,
            theme: Theme::default(),
        }
    }
}

impl Default for Server {
    fn default() -> Self {
        Self {
            host: "127.0.0.1".to_string(),
            port: 8090,
        }
    }
}
impl Default for ConfigFile {
    fn default() -> Self {
        Self {
            server: Server::default(),
            display: ConfDisplay::default(),
            analytics: Analytics::default(),
            filtering: Filtering::default(),
            sharing: Sharing::default(),
            ai: Ai::default(),
            scraping: Scraping::default(),
            processing: Processing::default(),
            migration: ConfMigration::default(),
            cli: Cli::default(),
        }
    }
}

impl ConfigFile {
    pub fn load_from_file(app_settings: &AppSettings) -> Result<Self, Box<dyn std::error::Error>> {
        let config_path_str = &app_settings.config_path;
        let config_path = PathBuf::from(config_path_str);

        if !config_path.exists() {
            let config_file = ConfigFile::default();
            config_file.save_to_file(app_settings)?;
            return Ok(config_file);
        }

        let contents = std::fs::read_to_string(config_path_str)?;
        let config: ConfigFile = toml::from_str(&contents)?;
        Ok(config)
    }

    pub fn save_to_file(
        &self,
        app_settings: &AppSettings,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let contents = toml::to_string_pretty(self)?;
        std::fs::write(&app_settings.config_path, contents)?;
        Ok(())
    }
}

#[derive(Deserialize, Debug)]
pub struct AppSettings {
    pub config_path: String,
    pub db_path: String,
    pub migration_path: String,
    pub config: ConfigFile,
}

impl Default for AppSettings {
    fn default() -> Self {
        Self {
            config_path: Self::get_config_path(),
            db_path: Self::get_db_path(),
            migration_path: Self::get_migration_path(),
            config: ConfigFile::default(),
        }
    }
}

impl AppSettings {
    const FALLBACK_DIR: &'static str = "/tmp";
    const PROD_DB_PATH: &'static str = "/var/lib/owly-news/owlynews.sqlite3";
    const PROD_MIGRATION_PATH: &'static str = "/usr/share/owly-news/migrations";

    pub fn get_app_settings() -> Self {
        AppSettings::default()
    }

    fn get_base_path(env_var: &str, fallback: &str) -> String {
        env::var(env_var).unwrap_or_else(|_| fallback.to_string())
    }

    fn get_db_path() -> String {
        if cfg!(debug_assertions) {
            let manifest_dir = Self::get_base_path("CARGO_MANIFEST_DIR", Self::FALLBACK_DIR);
            format!("{}/owlynews.sqlite3", manifest_dir)
        } else {
            Self::PROD_DB_PATH.to_string()
        }
    }

    fn get_config_path() -> String {
        if cfg!(debug_assertions) {
            let manifest_dir = Self::get_base_path("CARGO_MANIFEST_DIR", Self::FALLBACK_DIR);
            format!("{}/config.toml", manifest_dir)
        } else {
            let home = Self::get_base_path("HOME", Self::FALLBACK_DIR);
            format!("{}/.config/owly-news/config.toml", home)
        }
    }

    fn get_migration_path() -> String {
        if cfg!(debug_assertions) {
            let manifest_dir = Self::get_base_path("CARGO_MANIFEST_DIR", Self::FALLBACK_DIR);
            format!("{}/migrations", manifest_dir)
        } else {
            Self::PROD_MIGRATION_PATH.to_string()
        }
    }

    pub fn database_url(&self) -> String {
        format!("sqlite:{}", self.db_path)
    }

    pub fn ensure_default_directory(&self) -> Result<(), std::io::Error> {
        // Always create the database directory
        if let Some(parent) = std::path::Path::new(&self.db_path).parent() {
            std::fs::create_dir_all(parent)?;
        }

        // In production mode, also create the config directory
        if !cfg!(debug_assertions) {
            if let Some(parent) = std::path::Path::new(&self.config_path).parent() {
                std::fs::create_dir_all(parent)?;
            }
        }

        Ok(())
    }
}
