use crate::error::AybError;
use crate::server::config::{AybConfig, WebHostingMethod};
use crate::templating::TemplateString;
use serde::Deserialize;
use url::Url;

#[derive(Clone, Deserialize)]
pub struct WebFrontendDetails {
    base_url: Url,
    endpoints: WebFrontendEndpoints,
}

#[derive(Clone, Deserialize)]
pub struct WebFrontendEndpoints {
    profile: TemplateString,
    confirmation: TemplateString,
}

pub fn local_base_url(config: &AybConfig) -> String {
    format!("http://localhost:{}", config.port)
}

pub fn public_base_url(config: &AybConfig) -> String {
    if let Some(ref public_url) = config.public_url {
        public_url.clone()
    } else {
        local_base_url(config)
    }
}

impl WebFrontendDetails {
    async fn from_url(url: &Url) -> Result<Self, AybError> {
        Ok(reqwest::get(url.to_string()).await?.json().await?)
    }

    fn from_local(config: &AybConfig) -> Self {
        WebFrontendDetails {
            base_url: Url::parse(&public_base_url(config)).unwrap(),
            endpoints: WebFrontendEndpoints {
                profile: TemplateString {
                    string: "{entity}".into(),
                },
                confirmation: TemplateString {
                    string: "confirm/{token}".into(),
                },
            },
        }
    }
    pub fn profile(&self, entity: &str) -> String {
        let relative = self.endpoints.profile.execute(vec![("entity", entity)]);
        let absolute = self
            .base_url
            .join(&relative)
            .expect("invalid profile template string provided by the web frontend");
        absolute.to_string()
    }

    pub fn confirmation(&self, token: &str) -> String {
        let relative = self
            .endpoints
            .confirmation
            .execute(vec![("token", &urlencoding::encode(token))]);
        let absolute = self
            .base_url
            .join(&relative)
            .expect("invalid confirmation template string provided by the web frontend");
        absolute.to_string()
    }

    pub async fn load(config: AybConfig) -> Result<Option<Self>, AybError> {
        if let Some(ref web_conf) = config.web {
            Ok(Some(match web_conf.hosting_method {
                WebHostingMethod::Remote => {
                    if let Some(base_url) = &web_conf.base_url {
                        Self::from_url(base_url).await?
                    } else {
                        return Err(AybError::ConfigurationError {
                            message: "Remote web hosting method requires a base_url".to_string(),
                        });
                    }
                }
                WebHostingMethod::Local => Self::from_local(&config),
            }))
        } else {
            Ok(None)
        }
    }
}
