use diesel::{connection::Connection, mysql::MysqlConnection, prelude::*};
use dotenv::dotenv;
use models::user::User;
use serenity::{
    model::{
        guild::Member,
        prelude::{GuildId, RoleId},
        user::User as DiscordUser,
    },
    prelude::Context,
};
use std::{env, fmt};

pub struct CmdError {
    pub message: String,
}

impl fmt::Display for CmdError {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "{}", self.message)
    }
}

impl From<serenity::framework::standard::ArgError<std::string::ParseError>> for CmdError {
    fn from(e: serenity::framework::standard::ArgError<std::string::ParseError>) -> Self {
        CmdError {
            message: e.to_string(),
        }
    }
}

impl From<serenity::framework::standard::ArgError<std::num::ParseIntError>> for CmdError {
    fn from(e: serenity::framework::standard::ArgError<std::num::ParseIntError>) -> Self {
        CmdError {
            message: e.to_string(),
        }
    }
}

impl From<diesel::result::Error> for CmdError {
    fn from(e: diesel::result::Error) -> Self {
        CmdError {
            message: e.to_string(),
        }
    }
}

impl From<std::num::ParseIntError> for CmdError {
    fn from(e: std::num::ParseIntError) -> Self {
        CmdError {
            message: e.to_string(),
        }
    }
}

impl From<serenity::Error> for CmdError {
    fn from(e: serenity::Error) -> Self {
        CmdError {
            message: e.to_string(),
        }
    }
}

impl From<chrono::ParseError> for CmdError {
    fn from(e: chrono::ParseError) -> Self {
        CmdError {
            message: format!("{}", e),
        }
    }
}

pub fn establish_connection() -> MysqlConnection {
    dotenv().ok();

    let db_url = if "PROD"
        == env::var("APP_ENV")
            .expect("APP_ENV must be set")
            .to_uppercase()
    {
        debug!("Use prod db");
        env::var("DATABASE_URL").expect("DATABASE_URL must be set")
    } else {
        debug!("Use dev db");
        env::var("DATABASE_URL_DEV").expect("DATABASE_URL_DEV must be set")
    };

    MysqlConnection::establish(&db_url)
        .unwrap_or_else(|e| panic!("Error connecting to {}. '{}'", db_url, e))
}

pub fn get_current_user(asked_discord_id: u64) -> Result<User, CmdError> {
    use schema::user::dsl::*;

    let conn = establish_connection();

    let users: Vec<User> = user
        .filter(discord_id.eq(asked_discord_id))
        .load::<User>(&conn)?;

    if users.is_empty() {
        Err(CmdError {
            message: "Unknown user! please use register before.".to_string(),
        })
    } else {
        Ok(users[0].clone())
    }
}

pub fn get_user_for_roles(
    ctx: &mut Context,
    roles: &[RoleId],
    gid: GuildId,
) -> Result<Vec<User>, CmdError> {
    use schema::user::dsl::{discord_id as discord_id_dsl, user as user_dsl};

    let members: Vec<Member> = gid.members(ctx, Some(1000), None)?;
    debug!("members: {:?}", members);

    let ids: Vec<u64> = members
        .iter()
        .filter(|m| roles.iter().map(|item| m.roles.contains(item)).any(|v| v))
        .map(|m| m.user_id().0)
        .collect();

    let conn = establish_connection();

    Ok(user_dsl
        .filter(discord_id_dsl.eq_any(ids))
        .load::<User>(&conn)?)
}

pub fn get_user_from_discord_user(
    d_user: &DiscordUser,
    conn: &MysqlConnection,
) -> Result<User, diesel::result::Error> {
    use schema::user::dsl::*;

    user.filter(discord_id.eq(d_user.id.0)).first(conn)
}

pub fn stars_to_shards(stars: u16) -> u16 {
    match stars {
        0 => 0,
        1 => 10,
        2 => 25,
        3 => 50,
        4 => 80,
        5 => 145,
        6 => 230,
        _ => 330,
    }
}

#[allow(unused_macros)]
#[macro_use]
pub mod macros {
    macro_rules! catch_error {
        ($e:expr) => {
            $e.map_err(|e| {
                error!("error: {}", e.to_string());
                CommandError(e.to_string)
            })
        };
    }
}
