use once_cell::sync::Lazy;
use serde::Deserialize;
use std::env;
use std::fs;

#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
pub struct ServerConfig {
    pub url: String,
    pub port: u16,
    /// host to listen on
    pub bind: Option<String>,
    pub disable_hsts: Option<bool>,

    pub db: DbConfig,
    pub redis: RedisConfig,
    pub cache_server: Option<RedisConfig>,

    pub proxy: Option<String>,
    pub proxy_smtp: Option<String>,
    pub proxy_bypass_hosts: Option<Vec<String>>,

    pub allowed_private_networks: Option<Vec<String>>,
    /// `NapiValue` is not implemented for `u64`
    pub max_file_size: Option<i64>,
    pub access_log: Option<String>,
    pub cluster_limits: Option<WorkerConfig>,
    pub cuid: Option<IdConfig>,
    pub outgoing_address: Option<String>,

    pub deliver_job_concurrency: Option<u32>,
    pub inbox_job_concurrency: Option<u32>,
    pub deliver_job_per_sec: Option<u32>,
    pub inbox_job_per_sec: Option<u32>,
    pub deliver_job_max_attempts: Option<u32>,
    pub inbox_job_max_attempts: Option<u32>,

    pub log_level: Option<Vec<String>>,

    pub syslog: Option<SysLogConfig>,

    pub proxy_remote_files: Option<bool>,
    pub media_proxy: Option<String>,
    pub summaly_proxy_url: Option<String>,

    pub reserved_usernames: Option<Vec<String>>,

    pub max_user_signups: Option<u32>,
    pub is_managed_hosting: Option<bool>,
    pub max_note_length: Option<u32>,
    pub max_caption_length: Option<u32>,

    pub deepl: Option<DeepLConfig>,
    pub libre_translate: Option<LibreTranslateConfig>,
    pub email: Option<EmailConfig>,
    pub object_storage: Option<ObjectStorageConfig>,
}

#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
pub struct DbConfig {
    pub host: String,
    pub port: u16,
    pub db: String,
    pub user: String,
    pub pass: String,
    pub disable_cache: Option<bool>,
    pub extra: Option<serde_json::Value>,
}

#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
pub struct RedisConfig {
    pub host: String,
    pub port: u16,
    pub family: Option<u8>,
    pub user: Option<String>,
    pub pass: Option<String>,
    pub tls: Option<TlsConfig>,
    #[serde(default)]
    pub db: u32,
    #[serde(default)]
    pub prefix: String,
}

#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
pub struct TlsConfig {
    pub host: String,
    pub reject_unauthorized: bool,
}

#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
pub struct WorkerConfig {
    pub web: Option<u32>,
    pub queue: Option<u32>,
}

#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
pub struct IdConfig {
    pub length: Option<u8>,
    pub fingerprint: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
pub struct SysLogConfig {
    pub host: String,
    pub port: u16,
}

#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
pub struct DeepLConfig {
    pub managed: Option<bool>,
    pub auth_key: Option<String>,
    pub is_pro: Option<bool>,
}

#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
pub struct LibreTranslateConfig {
    pub managed: Option<bool>,
    pub api_url: Option<String>,
    pub api_key: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
pub struct EmailConfig {
    pub managed: Option<bool>,
    pub address: Option<String>,
    pub host: Option<String>,
    pub port: Option<u16>,
    pub user: Option<String>,
    pub pass: Option<String>,
    pub use_implicit_ssl_tls: Option<bool>,
}

#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
pub struct ObjectStorageConfig {
    pub managed: Option<bool>,
    pub base_url: Option<String>,
    pub bucket: Option<String>,
    pub prefix: Option<String>,
    pub endpoint: Option<String>,
    pub region: Option<String>,
    pub access_key: Option<String>,
    pub secret_key: Option<String>,
    pub use_ssl: Option<bool>,
    pub connnect_over_proxy: Option<bool>,
    pub set_public_read_on_upload: Option<bool>,
    pub s3_force_path_style: Option<bool>,
}

#[crate::export]
pub fn read_server_config() -> ServerConfig {
    let cwd = env::current_dir().unwrap();
    let yml = fs::File::open(cwd.join("../../.config/default.yml"))
        .expect("Failed to open '.config/default.yml'");
    let mut data: ServerConfig = serde_yaml::from_reader(yml).expect("Failed to parse yaml");
    data.url = url::Url::parse(&data.url)
        .expect("Config url is invalid")
        .origin()
        .ascii_serialization();
    data
}

pub static SERVER_CONFIG: Lazy<ServerConfig> = Lazy::new(read_server_config);
