use std::{convert::TryFrom, env::VarError, fs, net::AddrParseError, net::SocketAddr, num::ParseIntError, path::Path, str::FromStr};
use config::{Config, File, FileFormat};
use regex::Regex;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct GloryConfig {
    pub output_name: String,
    #[serde(default = "default_site_root")]
    pub site_root: String,
    #[serde(default = "default_site_pkg_dir")]
    pub site_pkg_dir: String,
    #[serde(default = "default_site_addr")]
    pub site_addr: SocketAddr,
    #[serde(default = "default_reload_port")]
    pub reload_port: u32,
    #[serde(default)]
    pub reload_external_port: Option<u32>,
    #[serde(default)]
    pub reload_ws_protocol: ReloadWSProtocol,
    #[serde(default = "default_not_found_path")]
    pub not_found_path: String,
}
impl Default for GloryConfig {
    fn default() -> Self {
        Self::new()
    }
}
impl GloryConfig {
    pub fn new() -> Self {
        Self::default()
    }
    pub async fn load(path: impl Into<Option<&str>>) -> Result<Self, GloryConfigError> {
        if let Some(path) = path.into() {
            Self::load_from_file(&path).await
        } else {
            Self::load_from_env()
        }
    }
    pub async fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, GloryConfigError> {
        let text = fs::read_to_string(path).map_err(|_| GloryConfigError::ConfigNotFound)?;
        Self::load_from_str(&text)
    }
    pub fn load_from_env() -> Result<Self, GloryConfigError> {
        Self::try_load_from_env()
    }
    fn try_load_from_env() -> Result<Self, GloryConfigError> {
        let output_name = env_with_default("GLORY_OUTPUT_NAME", std::option_env!("GLORY_OUTPUT_NAME",).unwrap_or_default())?;
        if output_name.is_empty() {
            eprintln!(
                "It looks like you're trying to compile Glory without the \
                 GLORY_OUTPUT_NAME environment variable being set. There are \
                 two options\n 1. glory-cli is not being used, but \
                 get_configuration() is being passed None. This needs to be \
                 changed to Some(\"Cargo.toml\")\n 2. You are compiling \
                 Glory without GLORY_OUTPUT_NAME being set with \
                 glory-cli. This shouldn't be possible!"
            );
        }
        Ok(GloryConfig {
            output_name,
            site_root: env_with_default("GLORY_SITE_ROOT", "target/site")?,
            site_pkg_dir: env_with_default("GLORY_SITE_PKG_DIR", "pkg")?,
            site_addr: env_with_default("GLORY_SITE_ADDR", "127.0.0.1:8000")?.parse()?,
            reload_port: env_with_default("GLORY_RELOAD_PORT", "3001")?.parse()?,
            reload_external_port: match env_without_default("GLORY_RELOAD_EXTERNAL_PORT")? {
                Some(val) => Some(val.parse()?),
                None => None,
            },
            reload_ws_protocol: ws_from_str(env_with_default("GLORY_RELOAD_WS_PROTOCOL", "ws")?.as_str())?,
            not_found_path: env_with_default("GLORY_NOT_FOUND_PATH", "/404")?,
        })
    }
    pub fn load_from_str(text: &str) -> Result<Self, GloryConfigError> {
        let re: Regex = Regex::new(r"(?m)^\[package.metadata.glory\]").unwrap();
        let re_workspace: Regex = Regex::new(r"(?m)^\[\[workspace.metadata.glory\]\]").unwrap();
        let metadata_name;
        let start;
        match re.find(text) {
            Some(found) => {
                metadata_name = "[package.metadata.glory]";
                start = found.start();
            }
            None => match re_workspace.find(text) {
                Some(found) => {
                    metadata_name = "[[workspace.metadata.glory]]";
                    start = found.start();
                }
                None => return Err(GloryConfigError::ConfigSectionNotFound),
            },
        };
        let newlines = text[..start].matches('\n').count();
        let input = "\n".repeat(newlines) + &text[start..];
        let toml = input.replace(metadata_name, "[glory-web-config]");
        let settings = Config::builder()
            .add_source(File::from_str(&toml, FileFormat::Toml))
            .add_source(config::Environment::with_prefix("GLORY").separator("_"))
            .build()?;
        settings.try_deserialize().map_err(|e| GloryConfigError::ConfigError(e.to_string()))
    }
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub enum ReloadWSProtocol {
    WS,
    WSS,
}
impl Default for ReloadWSProtocol {
    fn default() -> Self {
        Self::WS
    }
}
fn ws_from_str(input: &str) -> Result<ReloadWSProtocol, GloryConfigError> {
    let sanitized = input.to_lowercase();
    match sanitized.as_ref() {
        "ws" | "WS" => Ok(ReloadWSProtocol::WS),
        "wss" | "WSS" => Ok(ReloadWSProtocol::WSS),
        _ => Err(GloryConfigError::EnvVarError(format!(
            "{input} is not a supported websocket protocol. Use only `ws` or \
             `wss`.",
        ))),
    }
}
impl FromStr for ReloadWSProtocol {
    type Err = ();
    fn from_str(input: &str) -> Result<Self, Self::Err> {
        ws_from_str(input).or_else(|_| Ok(Self::default()))
    }
}
impl From<&str> for ReloadWSProtocol {
    fn from(str: &str) -> Self {
        ws_from_str(str).unwrap_or_else(|err| panic!("{}", err))
    }
}
impl From<&Result<String, VarError>> for ReloadWSProtocol {
    fn from(input: &Result<String, VarError>) -> Self {
        match input {
            Ok(str) => ws_from_str(str).unwrap_or_else(|err| panic!("{}", err)),
            Err(_) => Self::default(),
        }
    }
}
impl TryFrom<String> for ReloadWSProtocol {
    type Error = GloryConfigError;
    fn try_from(s: String) -> Result<Self, Self::Error> {
        ws_from_str(s.as_str())
    }
}
#[derive(Debug, Error, Clone)]
pub enum GloryConfigError {
    #[error("Cargo.toml not found in package root")]
    ConfigNotFound,
    #[error("package.metadata.glory section missing from Cargo.toml")]
    ConfigSectionNotFound,
    #[error("Failed to get Glory Environment. Did you set GLORY_ENV?")]
    EnvError,
    #[error("Config Error: {0}")]
    ConfigError(String),
    #[error("Config Error: {0}")]
    EnvVarError(String),
}
impl From<config::ConfigError> for GloryConfigError {
    fn from(e: config::ConfigError) -> Self {
        Self::ConfigError(e.to_string())
    }
}
impl From<ParseIntError> for GloryConfigError {
    fn from(e: ParseIntError) -> Self {
        Self::ConfigError(e.to_string())
    }
}
impl From<AddrParseError> for GloryConfigError {
    fn from(e: AddrParseError) -> Self {
        Self::ConfigError(e.to_string())
    }
}
fn default_site_root() -> String {
    ".".to_string()
}
fn default_site_pkg_dir() -> String {
    "pkg".to_string()
}
fn default_site_addr() -> SocketAddr {
    SocketAddr::from(([127, 0, 0, 1], 8000))
}
fn default_reload_port() -> u32 {
    8001
}
fn default_not_found_path() -> String {
    "/404".to_string()
}
fn env_with_default(key: &str, default: &str) -> Result<String, GloryConfigError> {
    match std::env::var(key) {
        Ok(val) => Ok(val),
        Err(VarError::NotPresent) => Ok(default.to_string()),
        Err(e) => Err(GloryConfigError::EnvVarError(format!("{key}: {e}"))),
    }
}
fn env_without_default(key: &str) -> Result<Option<String>, GloryConfigError> {
    match std::env::var(key) {
        Ok(val) => Ok(Some(val)),
        Err(VarError::NotPresent) => Ok(None),
        Err(e) => Err(GloryConfigError::EnvVarError(format!("{key}: {e}"))),
    }
}