use headers::HeaderMap;
use serde::Deserialize;
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::path::Path;
use std::{collections::BTreeSet, path::PathBuf};
#[cfg(feature = "directory-listing")]
use crate::directory_listing::DirListFmt;
use crate::{helpers, Context, Result};
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub enum LogLevel {
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}
impl LogLevel {
    pub fn name(&self) -> &'static str {
        match self {
            LogLevel::Error => "error",
            LogLevel::Warn => "warn",
            LogLevel::Info => "info",
            LogLevel::Debug => "debug",
            LogLevel::Trace => "trace",
        }
    }
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Headers {
    pub source: String,
    #[serde(rename(deserialize = "headers"), with = "http_serde::header_map")]
    pub headers: HeaderMap,
}
#[derive(Debug, Serialize_repr, Deserialize_repr, Clone)]
#[repr(u16)]
pub enum RedirectsKind {
    Permanent = 301,
    Temporary = 302,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Redirects {
    pub source: String,
    pub destination: String,
    pub kind: RedirectsKind,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Rewrites {
    pub source: String,
    pub destination: String,
    pub redirect: Option<RedirectsKind>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct VirtualHosts {
    pub host: String,
    pub root: Option<PathBuf>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Advanced {
    pub headers: Option<Vec<Headers>>,
    pub rewrites: Option<Vec<Rewrites>>,
    pub redirects: Option<Vec<Redirects>>,
    pub virtual_hosts: Option<Vec<VirtualHosts>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct General {
    pub host: Option<String>,
    pub port: Option<u16>,
    pub root: Option<PathBuf>,
    pub log_level: Option<LogLevel>,
    pub cache_control_headers: Option<bool>,
    #[cfg(feature = "compression")]
    #[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
    pub compression: Option<bool>,
    #[cfg(feature = "compression")]
    #[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
    pub compression_static: Option<bool>,
    pub page404: Option<PathBuf>,
    pub page50x: Option<PathBuf>,
    #[cfg(feature = "http2")]
    #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
    pub http2: Option<bool>,
    #[cfg(feature = "http2")]
    #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
    pub http2_tls_cert: Option<PathBuf>,
    #[cfg(feature = "http2")]
    #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
    pub http2_tls_key: Option<PathBuf>,
    #[cfg(feature = "http2")]
    #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
    pub https_redirect: Option<bool>,
    #[cfg(feature = "http2")]
    #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
    pub https_redirect_host: Option<String>,
    #[cfg(feature = "http2")]
    #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
    pub https_redirect_from_port: Option<u16>,
    #[cfg(feature = "http2")]
    #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
    pub https_redirect_from_hosts: Option<String>,
    pub security_headers: Option<bool>,
    pub cors_allow_origins: Option<String>,
    pub cors_allow_headers: Option<String>,
    pub cors_expose_headers: Option<String>,
    #[cfg(feature = "directory-listing")]
    #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
    pub directory_listing: Option<bool>,
    #[cfg(feature = "directory-listing")]
    #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
    pub directory_listing_order: Option<u8>,
    #[cfg(feature = "directory-listing")]
    #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
    pub directory_listing_format: Option<DirListFmt>,
    #[cfg(feature = "basic-auth")]
    #[cfg_attr(docsrs, doc(cfg(feature = "basic-auth")))]
    pub basic_auth: Option<String>,
    pub fd: Option<usize>,
    pub threads_multiplier: Option<usize>,
    pub max_blocking_threads: Option<usize>,
    pub grace_period: Option<u8>,
    #[cfg(feature = "fallback-page")]
    #[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))]
    pub page_fallback: Option<PathBuf>,
    pub log_remote_address: Option<bool>,
    pub redirect_trailing_slash: Option<bool>,
    pub ignore_hidden_files: Option<bool>,
    pub health: Option<bool>,
    #[cfg(windows)]
    pub windows_service: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Settings {
    pub general: Option<General>,
    pub advanced: Option<Advanced>,
}
impl Settings {
    pub fn read(config_file: &Path) -> Result<Settings> {
        let ext = config_file.extension();
        if ext.is_none() || ext.unwrap().is_empty() || ext.unwrap().ne("toml") {
            bail!("configuration file should be in toml format. E.g `config.toml`");
        }
        let toml =
            read_toml_file(config_file).with_context(|| "error reading toml configuration file")?;
        let mut unused = BTreeSet::new();
        let manifest: Settings = serde_ignored::deserialize(toml, |path| {
            let mut key = String::new();
            helpers::stringify(&mut key, &path);
            unused.insert(key);
        })
        .with_context(|| "error during toml configuration file deserialization")?;
        for key in unused {
            println!("Warning: unused configuration manifest key \"{key}\" or unsupported");
        }
        Ok(manifest)
    }
}
fn read_toml_file(path: &Path) -> Result<toml::Value> {
    let toml_str = helpers::read_file(path).with_context(|| {
        format!(
            "error trying to deserialize toml configuration file at \"{}\"",
            path.display()
        )
    })?;
    toml_str
        .parse()
        .map_err(|e| anyhow::Error::from(e).context("could not parse input as TOML"))
}