mod advanced;
mod article_list;
mod article_view;
mod dialog;
mod error;
mod feed;
mod feed_list;
mod general;
mod keybindings;
mod share;
mod user_data_size;

use crate::util::constants::DEFAULT_ARTICLE_CONTENT_WIDTH;

pub use self::advanced::{AdvancedSettings, DEFAULT_TIMEOUT_SECONDS, ProxyModel, ProxyProtocoll};
pub use self::error::SettingsError;
pub use self::feed_list::GFeedOrder;
pub use self::general::SyncIntervalType;
use self::share::ShareSettings;
pub use self::user_data_size::UserDataSize;
use article_list::ArticleListSettings;
pub use article_list::{GArticleOrder, GOrderBy};
pub use article_view::ArticleViewSettings;
pub use dialog::SettingsDialog;
use feed::FeedSettings;
use feed_list::FeedListSettings;
use general::GeneralSettings;
use glib::{Properties, prelude::*, subclass::prelude::*};
pub use keybindings::Keybindings;
use news_flash::models::FeedID;
use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT};
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::PathBuf;
use std::sync::RwLock;

static CONFIG_NAME: &str = "newsflash_gtk.json";

mod imp {
    use super::*;

    #[derive(Default, Debug, Serialize, Deserialize, Properties)]
    #[properties(wrapper_type = super::Settings)]
    pub struct Settings {
        #[property(get, set)]
        pub general: RefCell<GeneralSettings>,

        #[property(get, set)]
        pub feed_list: RefCell<FeedListSettings>,

        #[property(get, set)]
        pub article_list: RefCell<ArticleListSettings>,

        #[property(get, set)]
        pub article_view: RefCell<ArticleViewSettings>,

        #[property(get, set)]
        pub keybindings: RefCell<Keybindings>,

        #[property(get, set)]
        pub share: RefCell<ShareSettings>,

        #[serde(default)]
        pub feeds: RefCell<HashMap<FeedID, FeedSettings>>,

        pub advanced: RwLock<AdvancedSettings>,

        #[serde(skip_serializing)]
        #[serde(skip_deserializing)]
        pub path: RefCell<PathBuf>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for Settings {
        const NAME: &'static str = "NewsFlashSettings";
        type Type = super::Settings;
    }

    #[glib::derived_properties]
    impl ObjectImpl for Settings {}
}

glib::wrapper! {
    pub struct Settings(ObjectSubclass<imp::Settings>);
}

impl Serialize for Settings {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.imp().serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for Settings {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let inner = imp::Settings::deserialize(deserializer)?;
        Ok(inner.into())
    }
}

impl From<imp::Settings> for Settings {
    fn from(inner: imp::Settings) -> Self {
        let obj = glib::Object::builder::<Settings>()
            .property("general", inner.general.borrow().clone())
            .property("feed-list", inner.feed_list.borrow().clone())
            .property("article-list", inner.article_list.borrow().clone())
            .property("article-view", inner.article_view.borrow().clone())
            .property("keybindings", inner.keybindings.borrow().clone())
            .property("share", inner.share.borrow().clone())
            .build();
        let imp = obj.imp();
        *imp.advanced.write().unwrap() = inner.advanced.read().unwrap().clone();
        imp.feeds.replace(inner.feeds.borrow().clone());
        obj
    }
}

impl Default for Settings {
    fn default() -> Self {
        imp::Settings::default().into()
    }
}

impl Settings {
    pub fn open() -> Result<Self, SettingsError> {
        let path = crate::app::CONFIG_DIR.join(CONFIG_NAME);
        tracing::info!(?path, "Attempting to open Newsflash config file");

        if path.as_path().exists() {
            let data =
                fs::read_to_string(&path).inspect_err(|error| tracing::error!(%error, "failed to open config"))?;
            let mut settings = serde_json::from_str::<Self>(&data)
                .inspect_err(|error| tracing::error!(%error, "failed to deserialize config"))?;
            settings.imp().path.replace(path);
            Self::run_migrations(&mut settings)?;
            return Ok(settings);
        }

        tracing::info!("No existing config. Creating new config with default settings");
        fs::create_dir_all(crate::app::CONFIG_DIR.as_path())?;

        let settings = Settings::default();
        settings.imp().path.replace(path);
        settings.write()?;
        Ok(settings)
    }

    fn run_migrations(settings: &mut Settings) -> Result<(), SettingsError> {
        let imp = settings.imp();
        let mut changed = false;

        let content_width = imp.article_view.borrow().content_width();
        if content_width < 200 {
            tracing::warn!(%content_width, "content_width is suspiciously small. Overwriting with default value");
            imp.article_view
                .borrow()
                .set_content_width(DEFAULT_ARTICLE_CONTENT_WIDTH);
            changed = true;
        }

        if changed {
            settings.write()?;
        }
        Ok(())
    }

    pub fn write(&self) -> Result<(), SettingsError> {
        let data = serde_json::to_string_pretty(self)?;
        fs::write(&*self.imp().path.borrow(), data)?;
        Ok(())
    }

    pub fn set_accept_invalid_certs(&mut self, accept_invalid_certs: bool) -> Result<(), SettingsError> {
        self.imp().advanced.write().unwrap().accept_invalid_certs = accept_invalid_certs;
        self.write()?;
        Ok(())
    }

    pub fn set_accept_invalid_hostnames(&mut self, accept_invalid_hostnames: bool) -> Result<(), SettingsError> {
        self.imp().advanced.write().unwrap().accept_invalid_hostnames = accept_invalid_hostnames;
        self.write()?;
        Ok(())
    }

    pub fn advanced(&self) -> AdvancedSettings {
        self.imp().advanced.read().unwrap().clone()
    }

    pub fn get_feed_settings(&self, feed_id: &FeedID) -> Option<FeedSettings> {
        self.imp().feeds.borrow().get(feed_id).cloned()
    }

    pub fn set_feed_settings(&mut self, feed_id: &FeedID, settings: &FeedSettings) -> Result<(), SettingsError> {
        self.imp().feeds.borrow_mut().insert(feed_id.clone(), settings.clone());
        self.write()?;
        Ok(())
    }

    pub fn delete_old_feed_settings(&mut self, feed_ids: &HashSet<FeedID>) -> Result<(), SettingsError> {
        self.imp()
            .feeds
            .borrow_mut()
            .retain(|key, _value| feed_ids.contains(key));
        self.write()?;
        Ok(())
    }

    pub fn get_feed_header_maps(&self) -> HashMap<FeedID, HeaderMap<HeaderValue>> {
        let feed_settings = self.imp().feeds.borrow();
        let mut map = HashMap::new();

        for (feed_id, settings) in feed_settings.iter() {
            let mut header_map = HeaderMap::new();

            if let Some(custom_user_agent) = &settings.custom_user_agent
                && let Ok(user_agent_value) = HeaderValue::from_str(custom_user_agent)
            {
                header_map.insert(USER_AGENT, user_agent_value);
            }

            map.insert(feed_id.clone(), header_map);
        }

        map
    }

    pub fn get_feed_header_map(&self, feed_id: &FeedID) -> HeaderMap<HeaderValue> {
        let mut map = HeaderMap::new();
        let feed_settings = self.imp().feeds.borrow();

        let Some(feed_settings) = feed_settings.get(feed_id) else {
            return map;
        };

        if let Some(custom_user_agent) = &feed_settings.custom_user_agent
            && let Ok(user_agent_value) = HeaderValue::from_str(custom_user_agent)
        {
            map.insert(USER_AGENT, user_agent_value);
        }

        map
    }
}
