use json::{object, JsonValue};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs;
use std::path::PathBuf;

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Config {
    pub default: String,
    pub connections: BTreeMap<String, Connection>,
}

impl Default for Config {
    fn default() -> Self {
        Self::new()
    }
}

impl Config {
    pub fn new() -> Config {
        let mut connections = BTreeMap::new();
        connections.insert("sqlite".to_string(), Connection::new("sqlite"));
        connections.insert("mysql".to_string(), Connection::new("mysql"));
        connections.insert("pgsql".to_string(), Connection::new("pgsql"));
        connections.insert("mssql".to_string(), Connection::new("mssql"));
        Self {
            default: "sqlite".to_string(),
            connections,
        }
    }
    pub fn from(data: JsonValue) -> Config {
        let default = data["default"].to_string();
        let mut connections = BTreeMap::new();
        for (key, value) in data["connections"].entries() {
            let connection = Connection::from(value.clone()).clone();
            connections.insert(key.to_string(), connection.clone());
        }

        Self {
            default,
            connections,
        }
    }
    pub fn create(config_file: PathBuf, pkg_name: bool) -> Config {
        #[derive(Clone, Debug, Deserialize, Serialize)]
        pub struct ConfigPkg {
            pub br_db: Config,
        }
        impl ConfigPkg {
            pub fn new() -> ConfigPkg {
                let mut connections = BTreeMap::new();
                connections.insert("sqlite".to_string(), Connection::new("sqlite"));
                connections.insert("mysql".to_string(), Connection::new("mysql"));
                connections.insert("pgsql".to_string(), Connection::new("pgsql"));
                connections.insert("mssql".to_string(), Connection::new("mssql"));
                Self {
                    br_db: Config {
                        default: "sqlite".to_string(),
                        connections,
                    },
                }
            }
        }
        match fs::read_to_string(config_file.clone()) {
            Ok(e) => {
                if pkg_name {
                    toml::from_str::<ConfigPkg>(&e)
                        .map(|c| c.br_db)
                        .unwrap_or_else(|_| Config::new())
                } else {
                    toml::from_str::<Config>(&e).unwrap_or_else(|_| Config::new())
                }
            }
            Err(_) => {
                if pkg_name {
                    let data = ConfigPkg::new();
                    if let Some(parent) = config_file.parent() {
                        let _ = fs::create_dir_all(parent);
                    }
                    if let Ok(toml) = toml::to_string(&data) {
                        if let Some(path) = config_file.to_str() {
                            let _ = fs::write(path, toml);
                        }
                    }
                    data.br_db
                } else {
                    let data = Config::new();
                    if let Some(parent) = config_file.parent() {
                        let _ = fs::create_dir_all(parent);
                    }
                    if let Ok(toml) = toml::to_string(&data) {
                        if let Some(path) = config_file.to_str() {
                            let _ = fs::write(path, toml);
                        }
                    }
                    data
                }
            }
        }
    }
    /// 设置新地连接配置
    pub fn set_connection(&mut self, name: &str, connection: JsonValue) {
        let connection = Connection::from(connection);
        self.connections.insert(name.to_string(), connection);
    }
    /// 设置当前运行配置
    pub fn set_default(&mut self, name: &str) {
        self.default = name.to_string();
    }
}
/// 数据库模式
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum Mode {
    Mysql,
    Mssql,
    Sqlite,
    Pgsql,
    None,
}

impl Mode {
    pub fn str(&mut self) -> String {
        match self {
            Mode::Mysql => "mysql",
            Mode::Sqlite => "sqlite",
            Mode::Mssql => "mssql",
            Mode::Pgsql => "pgsql",
            Mode::None => "",
        }
        .to_string()
    }
    pub fn from(name: &str) -> Self {
        match name.to_lowercase().as_str() {
            "mysql" => Mode::Mysql,
            "sqlite" => Mode::Sqlite,
            "mssql" => Mode::Mssql,
            "pgsql" => Mode::Pgsql,
            _ => Mode::None,
        }
    }
}

/// 数据库连接池配置
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PoolConfig {
    pub min_connections: u32,
    pub max_connections: u32,
    pub connect_timeout_secs: u64,
    pub read_timeout_secs: u64,
    pub write_timeout_secs: u64,
    pub keepalive_ms: u64,
}

impl Default for PoolConfig {
    fn default() -> Self {
        Self {
            min_connections: 0,
            max_connections: 400,
            connect_timeout_secs: 5,
            read_timeout_secs: 15,
            write_timeout_secs: 20,
            keepalive_ms: 5000,
        }
    }
}

impl PoolConfig {
    pub fn from(data: &JsonValue) -> Self {
        Self {
            min_connections: data["min_connections"].as_u32().unwrap_or(0),
            max_connections: data["max_connections"].as_u32().unwrap_or(400),
            connect_timeout_secs: data["connect_timeout_secs"].as_u64().unwrap_or(5),
            read_timeout_secs: data["read_timeout_secs"].as_u64().unwrap_or(15),
            write_timeout_secs: data["write_timeout_secs"].as_u64().unwrap_or(20),
            keepalive_ms: data["keepalive_ms"].as_u64().unwrap_or(5000),
        }
    }
}

/// 数据库连接
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Connection {
    pub mode: Mode,
    pub hostname: String,
    pub hostport: String,
    pub database: String,
    pub username: String,
    pub userpass: String,
    pub params: Vec<String>,
    pub charset: Charset,
    pub prefix: String,
    pub debug: bool,
    #[serde(default)]
    pub pool: PoolConfig,
}

impl Default for Connection {
    fn default() -> Self {
        Self::new("sqlite")
    }
}

impl Connection {
    pub fn new(mode: &str) -> Connection {
        let mut that = Self {
            mode: Mode::from(mode),
            hostname: "".to_string(),
            hostport: "".to_string(),
            database: "".to_string(),
            username: "".to_string(),
            userpass: "".to_string(),
            params: vec![],
            charset: Charset::Utf8mb4,
            prefix: "".to_string(),
            debug: false,
            pool: PoolConfig::default(),
        };
        match Mode::from(mode) {
            Mode::Mysql => {
                that.hostname = "127.0.0.1".to_string();
                that.hostport = "3306".to_string();
                that.database = "test".to_string();
                that.username = "test".to_string();
                that.userpass = "test".to_string();
            }
            Mode::Mssql => {}
            Mode::Sqlite => {
                that.database = "db/app.db".to_string();
            }
            Mode::Pgsql => {
                that.hostname = "127.0.0.1".to_string();
                that.hostport = "5432".to_string();
                that.username = "test".to_string();
                that.userpass = "test".to_string();
            }
            Mode::None => {}
        }

        that
    }
    pub fn json(&mut self) -> JsonValue {
        object! {
            mode: self.mode.str(),
            hostname: self.hostname.clone(),
            hostport: self.hostport.clone(),
            database: self.database.clone(),
            username: self.username.clone(),
            userpass:self.userpass.clone(),
            params: self.params.clone(),
            charset: self.charset.str(),
            prefix: self.prefix.clone(),
            debug: self.debug
        }
    }
    pub fn from(data: JsonValue) -> Connection {
        Self {
            mode: Mode::from(data["mode"].as_str().unwrap_or("none")),
            hostname: data["hostname"].to_string(),
            hostport: data["hostport"].to_string(),
            database: data["database"].to_string(),
            username: data["username"].to_string(),
            userpass: data["userpass"].to_string(),
            params: data["params"].members().map(|x| x.to_string()).collect(),
            charset: Charset::from(data["charset"].as_str().unwrap_or("utf8mb4")),
            prefix: data["prefix"].as_str().unwrap_or("").to_string(),
            debug: data["debug"].to_string().parse::<bool>().unwrap_or(false),
            pool: PoolConfig::from(&data["pool"]),
        }
    }
    pub fn get_dsn(self) -> String {
        match self.mode {
            Mode::Mysql => {
                format!(
                    "mysql://{}:{}@{}:{}/{}",
                    self.username, self.userpass, self.hostname, self.hostport, self.database
                )
            }
            Mode::Sqlite => {
                let db_path = self.database.as_str();
                let path_buf = PathBuf::from(db_path);
                if !path_buf.is_file() {
                    if let Some(file_name) = path_buf.file_name() {
                        if let Some(file_name_str) = file_name.to_str() {
                            let dir_path = db_path.trim_end_matches(file_name_str);
                            let _ = fs::create_dir_all(dir_path);
                        }
                    }
                }
                path_buf.to_str().unwrap_or(db_path).to_string()
            }
            Mode::Mssql => format!(
                "sqlsrv://{}:{}@{}:{}/{}",
                self.username, self.userpass, self.hostname, self.hostport, self.database
            ),
            Mode::Pgsql => format!(
                "host={} user={} password={} dbname={}",
                self.hostname, self.username, self.userpass, self.database
            ),
            Mode::None => "".to_string(),
        }
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum Charset {
    Utf8mb4,
    Utf8,
    None,
}

impl Charset {
    pub fn from(str: &str) -> Charset {
        match str.to_lowercase().as_str() {
            "utf8" => Charset::Utf8,
            "utf8mb4" => Charset::Utf8mb4,
            _ => Charset::None,
        }
    }
    pub fn str(&self) -> String {
        match self {
            Charset::Utf8 => "utf8",
            Charset::Utf8mb4 => "utf8mb4",
            Charset::None => "",
        }
        .to_string()
    }
}
