use directories::ProjectDirs;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use url::Url;

use crate::error::{CliError, Result};

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Config {
    #[serde(default)]
    pub webmention: WebmentionConfig,
    #[serde(default)]
    pub micropub: MicropubConfig,
    #[serde(default)]
    pub indieauth: IndieAuthConfig,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct WebmentionConfig {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub endpoint: Option<Url>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MicropubConfig {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub endpoint: Option<Url>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct IndieAuthConfig {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub authorization_endpoint: Option<Url>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub token_endpoint: Option<Url>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub client_id: Option<Url>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub redirect_uri: Option<Url>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub scope: Option<String>,
}

#[derive(Debug, Clone, clap::Args)]
pub struct ConfigArgs {
    #[arg(long, env = "INDIEWEB_CONFIG", global = true)]
    pub config: Option<PathBuf>,

    #[arg(long, env = "INDIEWEB_TOKEN", global = true)]
    pub token: Option<String>,

    #[arg(long, global = true)]
    pub verbose: bool,
}

impl Config {
    pub fn load(args: &ConfigArgs) -> Result<Self> {
        if let Some(config_path) = &args.config {
            return Self::from_path(config_path);
        }

        if let Some(project_dirs) = ProjectDirs::from("org", "indieweb", "indieweb") {
            let config_path = project_dirs.config_dir().join("config.toml");
            if config_path.exists() {
                return Self::from_path(&config_path);
            }
        }

        Ok(Self::default())
    }

    pub fn from_path(path: &PathBuf) -> Result<Self> {
        let contents = std::fs::read_to_string(path)?;
        let config: Self = toml::from_str(&contents)?;
        Ok(config)
    }

    pub fn save(&self, path: &PathBuf) -> Result<()> {
        if let Some(parent) = path.parent() {
            std::fs::create_dir_all(parent)?;
        }
        let contents = toml::to_string_pretty(self).map_err(|e| CliError::Config(Box::new(e)))?;
        std::fs::write(path, contents)?;
        Ok(())
    }

    pub fn config_path() -> Option<PathBuf> {
        ProjectDirs::from("org", "indieweb", "indieweb")
            .map(|dirs| dirs.config_dir().join("config.toml"))
    }

    pub fn data_dir() -> Option<PathBuf> {
        ProjectDirs::from("org", "indieweb", "indieweb")
            .map(|dirs| dirs.data_local_dir().to_path_buf())
    }
}

impl ConfigArgs {
    pub fn into_config(self) -> Result<(Config, ConfigArgs)> {
        let config = Config::load(&self)?;
        Ok((config, self))
    }
}
