use diesel::prelude::*;
use diesel::pg::PgConnection;
use diesel::result::Error;
use dotenvy::dotenv;
use std::env;
use serde::{Serialize};
use diesel_derive_enum::DbEnum;
use std::str::FromStr;
use strum_macros::EnumString;
use crate::schema::dids;

#[derive(Debug, PartialEq, DbEnum, Serialize, EnumString)]
#[db_enum(existing_type_path = "crate::schema::sql_types::DidTargetType")]
pub enum DidTargetType {
    #[db_enum(rename ="URL")]
    Url,
    #[db_enum(rename ="MOH")]
    Moh,
    #[db_enum(rename ="EXTERNAL_NUMBER")]
    ExternalNumber,
    #[db_enum(rename ="NO_SERVICE")]
    NoService,
    #[db_enum(rename ="NIGHT_MODE")]
    NightMode,
    #[db_enum(rename ="CUSTOME_MESSAGE")]
    CustomMessage
}

#[derive(Debug, Queryable, Selectable, Serialize, AsChangeset)]
#[diesel(table_name = dids)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Did {
    pub id: i32,
    pub did_number: String,
    pub target_type: DidTargetType,
    pub target: Option<String>,
    pub active: bool,
    pub trunk: String,
}

#[derive(Insertable)]
#[diesel(table_name = dids)]
pub struct NewDid<'a> {
    pub did_number: &'a str,
    pub target_type: DidTargetType,
    pub target: Option<&'a str>,
    pub active: bool,
    pub trunk: &'a str,
}

pub fn connect() -> PgConnection {
    dotenv().ok();

    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    PgConnection::establish(&database_url)
        .unwrap_or_else(|_| panic!("Error connecting to database"))
}

pub fn get_did(a_id :i32) -> Result<Did, Error> {
    use crate::schema::dids::dsl::*;

    let mut conn = connect();
    let result = dids.find(a_id).first(&mut conn)?;

    Ok(result)
}

pub fn get_did_by(did: &str) -> Result<Did, Error> {
    use crate::schema::dids::dsl::*;
    let mut conn = connect();

    let d = dids.filter(did_number.eq(did))
        .first::<Did>(&mut conn)?;

    Ok(d)
}

pub fn delete_did(i: i32) -> Result<(), Error> {
    use crate::schema::dids;
    use crate::schema::dids::columns::id;

    let mut conn = connect();
    diesel::delete(dids::table)
        .filter(id.eq(i))
        .execute(&mut conn)?;

    Ok(())
}

pub fn update_did(d: Did)-> Result<(), Error> {
    use crate::schema::dids;
    use crate::schema::dids::dsl::*;

    let mut conn = connect();

    diesel::update(dids::table)
        .filter(id.eq(d.id))
        .set(d)
        .execute(&mut conn)?;

    Ok(())
}

pub fn add_did(did_number: &str, target_type: &str, target: &str, active: bool, trunk: &str)
               -> Result<(), Error> {
    let mut conn = connect();
    let new_did = NewDid {
        did_number: did_number,
        target_type: DidTargetType::from_str(target_type).unwrap(),
        target: Some(target),
        active: active,
        trunk: trunk,
    };

    diesel::insert_into(dids::table)
        .values(&new_did)
        .execute(&mut conn)?;

    Ok(())
}

pub fn list_did() ->Result<Vec<Did>, Error> {
    use crate::schema::dids::dsl::*;
    let mut conn = connect();

    let res = dids
        .order_by(id.asc())
        .load(&mut conn)?;

    Ok(res)
}

pub fn from_pbx(ipaddr: &str) -> Result<bool, Error> {
    use crate::schema::dids::dsl::*;

    let pattern = format!("%{}%", ipaddr);
    let mut conn = connect();
    let count =  dids
        .filter(target.is_not_null().and(target.ilike(&pattern)))
        .count()
        .get_result::<i64>(&mut conn)?;

    Ok(count > 0)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn validate_from_pbx() {
        assert_eq!(Ok(true), from_pbx("172.16.0.213"));
        assert_eq!(Ok(true), from_pbx("172.16.0.215"));
    }

    #[test]
    fn validate_not_from_pbx() {
        assert_eq!(Ok(false), from_pbx("192.168.1.1"));
    }
}
