use self::{list::*, nothing::*, shards::*};
use chrono::{prelude::*, Duration};
use diesel::{
    prelude::*,
    result::{DatabaseErrorKind, Error as DieselError},
};
use internal::*;
use models::{
    unit::Unit,
    user::User,
    user_farm::{NewUserFarm, UserFarm},
};
use numerals::roman::Roman;
use serenity::{
    framework::standard::{
        macros::{command, group},
        ArgError as SerenityError, Args, CommandError, CommandResult,
    },
    model::channel::Message,
    prelude::*,
};

mod list;
mod nothing;
mod shards;

#[group]
#[prefix = "farm"]
#[description = "Farming related commands"]
#[commands(list, nothing, shards)]
struct Farm;

#[allow(dead_code)]
enum FarmType {
    Shard,
    Gear,
}

#[derive(Debug)]
struct AddParams {
    unit_name: String,
    comments: Option<String>,
    current_shards: u16,
    current_stars: u16,
    goal_shards: u16,
    goal_stars: Option<u16>,
    goal_gears: Option<u16>,
}

impl AddParams {
    pub fn parse(mut args: Args, farm_type: FarmType) -> Result<AddParams, CmdError> {
        let mut unit_name = args.single::<String>()?;

        let mut shards: String = args.single()?;
        while !shards.contains('/') {
            unit_name += &format!(" {}", &shards);
            shards = args.single()?;
        }

        let splitted: Vec<&str> = shards.split('/').collect();

        let current_shards = splitted[0].parse()?;
        let goal_shards = splitted[1].parse()?;
        let current_stars = AddParams::get_current_star_from_shards_count(goal_shards);

        let goal_gears;
        let goal_stars;
        match farm_type {
            FarmType::Shard => {
                goal_gears = None;
                goal_stars = args.single().ok();
            }
            FarmType::Gear => {
                goal_gears = args
                    .single::<String>()
                    .and_then(|s| match s.parse::<u16>() {
                        Ok(v) => Ok(v),
                        Err(_) => match Roman::parse(s.as_str()) {
                            None => Err(SerenityError::Eos),
                            Some(r) => Ok(r.value() as u16),
                        },
                    })
                    .ok();
                goal_stars = None;
            }
        }

        let c = args.rest().to_string();
        let comments = if c.is_empty() { None } else { Some(c) };

        Ok(AddParams {
            unit_name,
            comments,
            current_shards,
            current_stars,
            goal_shards,
            goal_stars,
            goal_gears,
        })
    }

    fn get_current_star_from_shards_count(shards_needed: u16) -> u16 {
        match shards_needed {
            10 => 0,
            15 => 1,
            25 => 2,
            30 => 3,
            65 => 4,
            85 => 5,
            100 => 6,
            _ => 0,
        }
    }
}

fn handle_diesel_error(e: DieselError, unit: &Unit) -> String {
    debug!("{:?}", e);
    match e {
        DieselError::DatabaseError(kind, info) => match kind {
            DatabaseErrorKind::UniqueViolation => {
                let message = if info.message().contains("user_farm_user_id_unit_id_uindex") {
                    format!(
                        "Toon '{}' is already farmed by you! Use modify action instead.",
                        unit.name
                    )
                } else {
                    format!("{:?}", info)
                };

                error!("{}", message);

                message
            }
            _ => "<@409375902586372108> Internal error code 001, Unhandled kind".to_string(),
        },
        _ => "<@409375902586372108> Internal error code 002, Unhandled error".to_string(),
    }
}

fn get_farmed_unit(user: &User, unit: &Unit) -> Option<UserFarm> {
    use schema::user_farm::dsl::*;

    let conn = establish_connection();

    match UserFarm::belonging_to(user)
        .filter(unit_id.eq(unit.id))
        .first(&conn)
    {
        Ok(uft) => Some(uft),
        _ => None,
    }
}

fn get_from_date(mut args: Args) -> Result<DateTime<Utc>, CommandError> {
    let date = if args.message().contains("-f") {
        let mut date = Utc::now();
        let mut next = false;
        for arg in args.iter::<String>() {
            let arg = arg.unwrap_or_else(|_| "".to_string());
            let str_date = arg.clone() + " 00:00:00";
            if next {
                date = Utc.datetime_from_str(str_date.as_str(), "%Y-%m-%d %H:%M:%S")?;
            }

            next = "-f" == arg;
        }
        date
    } else {
        match Utc::today().checked_sub_signed(Duration::days(i64::from(
            Utc::today().weekday().num_days_from_monday() + 3,
        ))) {
            Some(d) => d.and_hms(0, 0, 0),
            None => {
                return Err(CommandError("Internal error".to_string()));
            }
        }
    };

    Ok(date)
}

fn insert_farmed_unit(
    user: &User,
    unit: &Unit,
    params: &AddParams,
    conn: &MysqlConnection,
    farm_type: FarmType,
) -> Result<UserFarm, CmdError> {
    use schema::user_farm::dsl::user_farm;
    use schema::user_farm::*;

    let goals = match farm_type {
        FarmType::Shard => (Some(params.goal_stars.unwrap_or(7)), None),
        FarmType::Gear => (None, Some(params.goal_gears.unwrap_or(12))),
    };

    let nuf = NewUserFarm {
        user_id: &user.id,
        unit_id: &unit.id,
        stars_goal: goals.0.as_ref(),
        gears_goal: goals.1.as_ref(),
    };

    conn.transaction::<UserFarm, CmdError, _>(|| {
        let inserted_count = diesel::insert_into(user_farm).values(&nuf).execute(conn)?;

        Ok(user_farm
            .order(id.desc())
            .limit(inserted_count as i64)
            .load(conn)?
            .into_iter()
            .rev()
            .collect::<Vec<UserFarm>>()[0]
            .clone())
    })
}
