use serde::{Serialize,Deserialize};
use std::collections::{HashMap};
use anyhow::{anyhow,Result};
use datastores::defs::DataStore;

use crate::redis::RedisPool;
use crate::mysql::MysqlPool;
use crate::postgres::PostgresPool;
use crate::sqlite::SqlitePool;
//use crate::tikv::TikvPool;

use async_trait::async_trait;

//use tkdr::crypt_lib::decrypt; // FIXME
fn decrypt(data: &str, _key: &str) -> String {
	data.to_owned()
}

#[async_trait]
pub trait PoolHandle {
	async fn connect(&self) -> Self;
}

#[derive(Debug)]
pub enum DataPool {
	Redis(RedisPool),
  Mysql(MysqlPool),
  Postgres(PostgresPool),
  Sqlite(SqlitePool),
  //Tikv(TikvPool),
	File(String),
  Slab(String), 
  NoPool,
}

impl PartialEq for DataPool {
	fn eq(&self, other: &Self) -> bool {
		match self  {
			DataPool::Redis(source) => 
			  match other {
					DataPool::Redis(target) => source.id == target.id,
					_ => false,
				},
			DataPool::Mysql(source) => 
			  match other {
					DataPool::Mysql(target) => source.id == target.id,
					_ => false,
				},
			DataPool::Postgres(source) => 
			  match other {
					DataPool::Postgres(target) => source.id == target.id,
					_ => false,
				},
			DataPool::Sqlite(source) => 
			  match other {
					DataPool::Sqlite(target) => source.id == target.id,
					_ => false,
				},
	    DataPool::File(source) => 
			  match other {
					DataPool::File(target) => source == target,
					_ => false,
				},
  		DataPool::Slab(source) =>
			  match other {
					DataPool::Slab(target) => source == target,
					_ => false,
				},
  		DataPool::NoPool => 
				match other {
					DataPool::NoPool => true,
					_ => false,
				},
		}
	}
}
impl Default for DataPool {
  fn default() -> Self {
    DataPool::NoPool
  }
}

#[derive(Debug, Default)]
pub struct AppDataConn {
	name: String,
	datastores: HashMap<String,DataPool>,
}

impl AppDataConn {
	pub async fn new(name: String, stores_settings: Vec<StoreSettings>, key: &str) -> Self {
		let mut datastores: HashMap<String,DataPool> = HashMap::new(); 
		for settings in stores_settings.iter() {
			match settings.datastore {
				DataStore::Redis => {
					datastores.insert(
						settings.id.to_owned(), 
						DataPool::Redis(RedisPool::new(settings.to_owned()).connect_pool().await)
					);
				},
				DataStore::Mysql => {
					datastores.insert(
						settings.id.to_owned(), 
						DataPool::Mysql(MysqlPool::new(settings.to_owned(),key).await.connect_pool().await)
					);
				},
				DataStore::Postgres => {
					datastores.insert(
						settings.id.to_owned(), 
						DataPool::Postgres(PostgresPool::new(settings.to_owned(),key).await.connect_pool().await)
					);
				},
				DataStore::Sqlite => {
					datastores.insert(
						settings.id.to_owned(), 
						DataPool::Sqlite(SqlitePool::new(settings.to_owned()).connect_pool().await)
					);
				},
				_ => continue,
			};
		}
		Self {
			name,
			datastores,
		}
	}
	pub fn get_conn(&self,key: &str) -> &DataPool {
	  self.datastores.get(key).unwrap_or(&DataPool::NoPool)
	}
	pub async fn get_redis(&self,key: &str) -> Result<&redis::aio::Connection> {
	  match self.datastores.get(key).unwrap_or(&DataPool::NoPool) {
		  DataPool::Redis(redis_conn) => 
				if let Some(pool) = &redis_conn.conn {
					Ok(pool)
				} else {
					Err(anyhow!("Redis pool not available"))
				},
			_ => Err(anyhow!("{} is not a Redis connection",key)),
		}
	}
 	pub async fn get_mysql(&self,key: &str) -> Result<&sqlx::pool::PoolConnection<sqlx::MySql>> {
	  match self.datastores.get(key).unwrap_or(&DataPool::NoPool) {
		  DataPool::Mysql(mysql_conn) => 
				if let Some(pool) = &mysql_conn.conn {
					Ok(pool)
				} else {
					Err(anyhow!("Mysql pool not available"))
				},
			_ => Err(anyhow!("{} is not a Mysql connection",key)),
		}
	}
 	pub async fn get_postgres(&self,key: &str) -> Result<&sqlx::pool::PoolConnection<sqlx::Postgres>> {
	  match self.datastores.get(key).unwrap_or(&DataPool::NoPool) {
		  DataPool::Postgres(postgres_conn) => 
				if let Some(pool) = &postgres_conn.conn {
					Ok(pool)
				} else {
					Err(anyhow!("Postgres pool not available"))
				},
			_ => Err(anyhow!("{} is not a Postgres connection",key)),
		}
	}
 	pub async fn get_sqlite(&self,key: &str) -> Result<&sqlx::Pool<sqlx::Sqlite>> {
	  match self.datastores.get(key).unwrap_or(&DataPool::NoPool) {
		  DataPool::Sqlite(sqlite_conn) => 
				if let Some(pool) = &sqlite_conn.conn {
					Ok(pool)
				} else {
					Err(anyhow!("Sqlite pool not available"))
				},
			_ => Err(anyhow!("{} is not a Sqlite connection",key)),
		}
	}
	pub async fn check_connections(&self, datastores_settings: Vec<StoreSettings>) -> bool {
		let debug = envmnt::get_isize("DEBUG",0);
		let mut status = false;
		for con in &datastores_settings {
			match con.datastore {
				DataStore::Redis => {
					status = match self.get_redis(&con.id).await {
						Ok(_pool) => {
							if debug > 0 {
								println!("app_data_conn found redis pool");
							}
							true
						},
						Err(e) => {
							if StoreSettings::check_required_id(datastores_settings.to_owned(),&con.id) {
								panic!("Error app_data_conn required: {}",e);
							} else {
								println!("Error app_data_conn: {}",e);
							}
							false
						},
					}
				}			  
				_ => {
					continue;
				}
			};
		}
		status
	}
}

#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct StoreSettings {
	pub id: String,
  pub host: String,
  pub port: u32,
  pub user: String,
  pub pass: String,
	pub datastore: DataStore,
  pub database: String,
  pub prefix: String,
  pub max_conn: u32,
	pub required: bool,
}
impl StoreSettings {
	pub fn get_credentials(&self,key: &str) -> (String,String) {
    let user: String;
    let pass: String;
    if key.is_empty() {
      user = self.user.to_owned();
      pass = self.pass.to_owned();
    } else {
      user = decrypt(&self.user, key);
      pass = decrypt(&self.pass, key);
    }
		(user,pass)
	}
  pub fn url_db(&self, key: &str) -> String {
		let store = self.get_store();
    let (user,pass) = self.get_credentials(key);
		let user_pass: String;
		if ! user.is_empty() || ! pass.is_empty() {
      user_pass = format!("{}:{}", &user, &pass);
    } else {
			user_pass = String::from("");
		}
    let host = &self.host;
    let port = &self.port;
    let database = &self.database;
    format!(
      "{}://{}@{}:{}/{}",
      store, &user_pass, &host, &port, &database
    )
  }
  pub fn url_keyval(&self) -> String {
		let store = self.get_store();
    let host = &self.host;
    let port = &self.port;
    format!("{}://{}:{}", store, &host, &port)
	}
  pub fn url_local(&self) -> String {
		let store = self.get_store();
    format!("{}.{}", store, &self.database)
	}
	pub fn get_store(&self) -> String {
		match self.datastore {
				DataStore::File => String::from("file"),
				DataStore::Mysql => String::from("mysql"),
				DataStore::Postgres => String::from("postgres"),
				DataStore::Sqlite => String::from("sqlite"),
				DataStore::Redis => String::from("redis"),
				// DataStore::Tikv => String::from("tikv"),
				DataStore::Slab => String::from("slab"),
				DataStore::Unknown => String::from("Unknown"),
		}
	}
	pub fn find_storesetting_id(cfg_store_settings: Vec<StoreSettings>, id: &str) -> Result<Vec<StoreSettings>> {
		let datastore_found: Vec<StoreSettings> = cfg_store_settings.iter().filter(|itm| itm.id == id).cloned().collect();
		if datastore_found.len() > 0 {
			Ok(datastore_found)
		} else {
			Err(anyhow!("No DataStore Settings found for: {}",&id))
		}
	}
	pub fn check_required_id(cfg_store_settings: Vec<StoreSettings>, id: &str) -> bool {
		match StoreSettings::find_storesetting_id(cfg_store_settings.to_owned(),id) {
			Ok(ds_found) => 	
				if let Some(item) = ds_found.get(0) {
					item.required
				} else {
					false
				},
			Err(e) => {
				eprintln!("Error check required: {}",e);
				false
			},
		}
	}
}

#[derive(Default)]
pub struct DataStorePool<T> 
  where T : PoolHandle + Default
{
	pub pool: T,
	pub typ: DataStore,
}