use chrono::Utc;
use diesel::prelude::*;

use diesel::dsl::Select;
use internal::establish_connection;
use models::{farm_entry::FarmEntry, unit::Unit, user::User};
use schema::farm_entry::dsl::date as date_dsl;
use schema::user_farm;

type AllColumns = (
    user_farm::id,
    user_farm::unit_id,
    user_farm::user_id,
    user_farm::gears_goal,
    user_farm::gears_finished,
    user_farm::stars_goal,
    user_farm::stars_finished,
);

pub const ALL_COLUMNS: AllColumns = (
    user_farm::id,
    user_farm::unit_id,
    user_farm::user_id,
    user_farm::gears_goal,
    user_farm::gears_finished,
    user_farm::stars_goal,
    user_farm::stars_finished,
);

type All = Select<user_farm::table, AllColumns>;

#[derive(Identifiable, Queryable, Associations, AsChangeset, PartialEq, Debug, Clone)]
#[belongs_to(Unit)]
#[belongs_to(User)]
#[table_name = "user_farm"]
pub struct UserFarm {
    pub id: i32,
    pub unit_id: i32,
    pub user_id: i32,
    pub gears_goal: Option<u16>,
    pub gears_finished: bool,
    pub stars_goal: Option<u16>,
    pub stars_finished: bool,
}

impl UserFarm {
    pub fn all() -> All {
        user_farm::table.select(ALL_COLUMNS)
    }

    pub fn get_last_entry(&self) -> Option<FarmEntry> {
        let conn = establish_connection();

        FarmEntry::belonging_to(self)
            .order(date_dsl.desc())
            .limit(1)
            .first::<FarmEntry>(&conn)
            .ok()
    }

    pub fn get_remaining_days(&self) -> Option<u16> {
        use schema::farm_entry::dsl::*;

        let conn = establish_connection();

        let first_entry: FarmEntry = FarmEntry::belonging_to(self)
            .order(date.asc())
            .limit(1)
            .first(&conn)
            .unwrap();

        let last_entry: FarmEntry = FarmEntry::belonging_to(self)
            .order(date.desc())
            .limit(1)
            .first(&conn)
            .unwrap();

        if first_entry.id == last_entry.id || first_entry.total_shards() > last_entry.total_shards()
        {
            return None;
        }

        debug!("{:?} {:?}", first_entry, last_entry);

        let passed_days = Utc::now()
            .naive_utc()
            .signed_duration_since(last_entry.date)
            .num_days() as u16;
        let duration = last_entry
            .date
            .signed_duration_since(first_entry.date)
            .num_days() as u16;
        let shards = last_entry.total_shards() - first_entry.total_shards();

        if 0 == shards || 0 == duration || 0 == shards / duration {
            return None;
        }

        Some(
            (last_entry.get_remaining_shards(self.stars_goal.unwrap_or(7)) / (shards / duration))
                .checked_sub(passed_days)
                .unwrap_or(0),
        )
    }

    pub fn print_current_summary(&self) -> String {
        use schema::farm_entry::dsl::*;
        use schema::unit;

        let conn = establish_connection();
        let unit: Unit = unit::dsl::unit.find(self.unit_id).first(&conn).unwrap();

        match FarmEntry::belonging_to(self)
            .order(date.desc())
            .limit(1)
            .first::<FarmEntry>(&conn)
        {
            Ok(last_entry) => format!(
                "```asciidoc\n
#{}
Actual shards    :: {}
Actual stars     :: {}
Remaining shards :: {}
Remaining days   :: {}
Stars goal       :: {}
Comments         :: {}
```",
                unit.name,
                last_entry.current_shards,
                last_entry.current_stars,
                last_entry.get_remaining_shards(self.stars_goal.unwrap_or(7)),
                match self.get_remaining_days() {
                    Some(d) => d.to_string(),
                    None => "Unknown".to_string(),
                },
                self.stars_goal.unwrap_or(0),
                last_entry.comments.unwrap_or_else(|| "".to_string())
            ),
            Err(e) => {
                error!("{}", e);
                format!("No entry found for {}", unit.name)
            }
        }
    }

    pub fn print_user_and_delay(&self) -> String {
        "toto".to_string()
    }
}

#[derive(Insertable)]
#[table_name = "user_farm"]
pub struct NewUserFarm<'a> {
    pub unit_id: &'a i32,
    pub user_id: &'a i32,
    pub gears_goal: Option<&'a u16>,
    pub stars_goal: Option<&'a u16>,
}
