use actix_web::web::Data;  
use chrono::{Datelike, DateTime, Utc};  
use futures::executor::block_on;  
use sqlx::{Arguments, Pool, Row, Sqlite};  
use sqlx::sqlite::{SqliteArguments, SqliteRow};  
  
use common::models::entry::Entry;  
use common::models::production::Production;  
  
use crate::traits::database::{Database, DatabaseSub};  
use crate::traits::query::QuerySub;  
use crate::traits::standard::{Standard, StandardSub};  
  
impl Standard for Entry {  
    fn table_name() -> String {  
        "entry".to_string()  
    }  
}  
  
impl StandardSub for Entry {  
    fn parent_table() -> String {  
        "variant".to_string()  
    }  
}  
  
impl QuerySub<Entry> for Entry {  
    fn from_sqlite_row(row: SqliteRow, _parent_id: i64, pool: &Data<Pool<Sqlite>>) -> Entry {  
        let production: Option<Production> = match Production::get_by_id(row.get(7), pool).first() {  
            Some(prod) => {  
                Some(prod.clone())  
            }  
            None => {  
                None  
            }  
        };  
  
        Entry {  
            id: row.get(0),  
            charge: row.get(1),  
            amount_created: row.get(2),  
            amount_left: row.get(3),  
            date: row.get(4),  
            variant_id: row.get(5),  
            origin: row.get(6),  
            production,  
        }  
    }  
  
    fn from_sqlite_row_small(row: SqliteRow, _parent_id: i64, pool: &Data<Pool<Sqlite>>) -> Entry {  
        Entry {  
            id: row.get(0),  
            charge: row.get(1),  
            amount_created: row.get(2),  
            amount_left: row.get(3),  
            date: row.get(4),  
            variant_id: row.get(5),  
            origin: row.get(6),  
            production: None,  
        }  
    }  
}  
  
impl DatabaseSub<Entry> for Entry {  
  
}  
  
impl Database<Entry> for Entry {  
    fn insert(&self, pool: &Data<Pool<Sqlite>>) -> i64 {  
        let production_id = match &self.production {  
            Some(production) => {  
               Some(production.insert(pool))  
            }  
            None => {  
                None  
            }  
        };  
  
        let statement = format!(  
            "INSERT INTO {} (charge, amount_created, amount_left, date, variant, origin_id, production_id) VALUES ($1, $2, $2, $3, $4, $5, $6)",  
            Entry::table_name(),  
        );  
  
        let charge = self.charge.as_str();  
  
        let charge = if charge.eq("000_TO_GENERATE") {  
            let new_charge = next_charge(pool);  
            insert_charge(new_charge.clone(), pool);  
  
            new_charge  
        } else {  
            self.charge.clone()  
        };  
  
        let mut date_time: DateTime<Utc> = Utc::now();  
        date_time = date_time.with_day(21).unwrap();  
        date_time = date_time.with_month(09).unwrap();  
        //date_time = date_time.with_hour(10).unwrap();  
  
        let mut args = SqliteArguments::default();  
        args.add(charge);  
        args.add(&self.amount_created);  
        args.add(date_time);  
        args.add(&self.variant_id);  
        args.add(&self.origin);  
        args.add(production_id);  
  
        let mut conn = block_on(pool.acquire()).unwrap();  
        let temp = block_on(  
            sqlx::query_with(  
                statement.as_str(),  
                args)  
                .execute(&mut *conn)  
        );  
  
        println!("Entry result:\n {:#?}", temp);  
        temp.unwrap().last_insert_rowid()  
    }  
  
    fn update(&self, pool: &Data<Pool<Sqlite>>) {  
        let statement = format!(  
            "UPDATE {} SET amount_left = $2 WHERE id = $1",  
            Entry::table_name(),  
        );  
  
        let mut args = SqliteArguments::default();  
        args.add(&self.id);  
        args.add(&self.amount_left);  
  
        println!("Updating");  
  
        let mut conn = block_on(pool.acquire()).unwrap();  
        let temp = block_on(  
            sqlx::query_with(  
                statement.as_str(),  
                args)  
                .execute(&mut *conn)  
        );  
  
        println!("Result:\n {:#?}", temp);  
    }  
  
    fn delete(&self, pool: &Data<Pool<Sqlite>>) {  
        let mut conn = block_on(pool.acquire()).unwrap();  
        let temp = block_on(  
            sqlx::query(  
                format!("DELETE FROM {} WHERE id = {}",  
                        Entry::table_name(),  
                        self.id  
                ).as_str()  
            ).execute(&mut *conn)  
        );  
  
        println!("Result:\n {:#?}", temp);  
    }  
  
    fn get_all(pool: &Data<Pool<Sqlite>>) -> Vec<Entry> {  
        let mut conn = block_on(pool.acquire()).unwrap();  
        let res = block_on(  
            sqlx::query(format!("SELECT * FROM {}", Entry::table_name()).as_str())  
                .fetch_all(&mut *conn)  
        );  
  
        let mut entry: Vec<Entry> = Vec::new();  
  
        match res {  
            Ok(records) => {  
                for record in records {  
                    entry.push(  
                        // TODO: Parent_id  
                        Entry::from_sqlite_row_small(record, -1, pool)  
                    );  
                }  
            }  
            Err(err) => {  
                println!("Error while loading all entry entries:");  
                println!("{:#?}", err);  
            }  
        }  
  
        entry  
    }  
  
    fn get_by_id(id: i64, pool: &Data<Pool<Sqlite>>) -> Vec<Entry> {  
        let statement = format!(  
            "SELECT * FROM {} WHERE id = $1",  
            Entry::table_name(),  
        );  
  
        let mut args = SqliteArguments::default();  
        args.add(id);  
  
        let mut conn = block_on(pool.acquire()).unwrap();  
        let res = block_on(  
            sqlx::query_with(  
                statement.as_str(),  
                args)  
                .fetch_all(&mut *conn)  
        );  
  
        let mut entry: Vec<Entry> = Vec::new();  
  
        match res {  
            Ok(records) => {  
                for record in records {  
                    entry.push(  
                        // TODO: Parent_id  
                        Entry::from_sqlite_row(record, -1, pool)  
                    );  
                }  
            }  
            Err(err) => {  
                println!("Error while loading all entry entries:");  
                println!("{:#?}", err);  
            }  
        }  
  
        entry  
    }  
  
    fn get_by_search(search_term: String, pool: &Data<Pool<Sqlite>>) -> Vec<Entry> {  
        let statement = format!(  
            "SELECT * FROM {} WHERE variant = {}",  
            Entry::table_name(),  
            search_term,  
        );  
  
        let mut conn = block_on(pool.acquire()).unwrap();  
        let res = block_on(  
            sqlx::query(  
                statement.as_str())  
                .fetch_all(&mut *conn)  
        );  
  
        let mut entry: Vec<Entry> = Vec::new();  
  
        match res {  
            Ok(records) => {  
                for record in records {  
                    entry.push(  
                        // TODO: Parent_id  
                        Entry::from_sqlite_row(record, -1, pool)  
                    );  
                }  
            }  
            Err(err) => {  
                println!("Error while loading all entry entries:");  
                println!("{:#?}", err);  
            }  
        }  
  
        entry  
    }  
}  
  
pub fn get_entry_by_variant_and_date(variant_id: i64, date_start: String, date_end: String, pool: &Data<Pool<Sqlite>>) -> Vec<Entry> {  
    let statement = format!(  
        "SELECT entry.*  
         FROM entry         WHERE entry.variant = $1         AND entry.date > date($2) AND entry.date < date($3)",  
    );  
  
    let mut args = SqliteArguments::default();  
    args.add(variant_id);  
    args.add(date_start);  
    args.add(date_end);  
  
    let mut conn = block_on(pool.acquire()).unwrap();  
    let res = block_on(  
        sqlx::query_with(  
            statement.as_str(),  
            args)  
            .fetch_all(&mut *conn)  
    );  
    conn.detach();  
  
    let mut entries: Vec<Entry> = Vec::new();  
  
    match res {  
        Ok(records) => {  
            for record in records {  
                entries.push(  
                    // TODO: Parent_id  
                    Entry::from_sqlite_row_small(record, -1, pool)  
                );  
            }  
        }  
        Err(err) => {  
            println!("Error while loading entries by variant and dates:");  
            println!("{:#?}", err);  
        }  
    }  
  
    entries  
}  
  
pub fn get_entry_with_used_via_production(production_id: i64, pool: &Data<Pool<Sqlite>>) -> Option<Entry> {  
    let statement = format!(  
        "SELECT entry.*  
         FROM entry         WHERE entry.production_id = $1",  
    );  
  
    let mut args = SqliteArguments::default();  
    args.add(production_id);  
  
    let mut conn = block_on(pool.acquire()).unwrap();  
    let res = block_on(  
        sqlx::query_with(  
            statement.as_str(),  
            args)  
            .fetch_one(&mut *conn)  
    );  
    conn.detach();  
  
    let entry: Option<Entry>;  
  
    match res {  
        Ok(record) => {  
            entry = Some(Entry::from_sqlite_row_small(record, -1, pool));  
        }  
        Err(err) => {  
            entry = None;  
            println!("Error while loading parent entry by used:");  
            println!("{:#?}", err);  
        }  
    }  
  
    entry  
}  
  
const CHARGE_TABLE: &str = "used_charge";  
  
fn insert_charge(charge: String, pool: &Data<Pool<Sqlite>>) {  
    let statement = format!(  
        "INSERT INTO {} (charge) VALUES ($1)",  
        CHARGE_TABLE,  
    );  
  
    let mut args = SqliteArguments::default();  
    args.add(charge);  
  
    let mut conn = block_on(pool.acquire()).unwrap();  
    let temp = block_on(  
        sqlx::query_with(  
            statement.as_str(),  
            args)  
            .execute(&mut *conn)  
    );  
    conn.detach();  
  
    println!("Charge result:\n {:#?}", temp);  
}  
  
fn next_charge(pool: &Data<Pool<Sqlite>>) -> String {  
    let statement = format!(  
        "SELECT charge FROM {}",  
        CHARGE_TABLE,  
    );  
  
    let mut conn = block_on(pool.acquire()).unwrap();  
    let temp = block_on(  
        sqlx::query(  
            statement.as_str())  
            .fetch_all(&mut *conn)  
    );  
    conn.detach();  
  
    match temp {  
        Ok(charges) => {  
            if charges.last().is_some() {  
                increment_charge(charges.last().unwrap().get(0))  
            } else {  
                "A".to_string()  
            }  
        },  
        Err(_) => {  
            "A".to_string()  
        }  
    }  
}  
  
fn increment_charge(charge: String) -> String {  
    let mut charge: Vec<char> = charge.chars().collect();  
  
    charge.reverse();  
    let mut over = false;  
  
    for ch in charge.iter_mut() {  
        if *ch == 'Z' {  
            over = true;  
            *ch = 'A';  
        } else if over {  
            *ch = std::char::from_u32(*ch as u32 + 1).unwrap_or(*ch);  
            over = false;  
        } else {  
            *ch = std::char::from_u32(*ch as u32 + 1).unwrap_or(*ch);  
            break;  
        }  
    }  
  
    charge.reverse();  
  
    if over {  
        charge.push('A');  
    }  
  
    charge.iter().collect()  
}