use serde::{Deserialize,Serialize}; 
use sqlx::Row;
use futures::TryStreamExt;
use anyhow::{anyhow,Context, Result};
use std::{
    fmt,
    collections::HashMap,
};
// use std::{
//     // sync::Arc,
//     fmt::Debug,
//     // io::Write,
//     // fs,
//     // path::{Path, PathBuf},
//     // io::{Error, ErrorKind},
//     collections::HashMap,
// };
// use async_session::{MemoryStore, Session, SessionStore};
//use tiitls_utils::logs::file;
// use uuid::Uuid;
//    SID_UI_FILE,
//    UI_SETTINGS_FILE,
//    SidSettings,
//    Config as SessionsConfig, 
use std::num::ParseIntError;
use crate::{
    users::{
        entries::{Entries,Entry},
        UserData,
        UserStore,
        UserStatus,
    },
    USERS_TABLENAME,
    tools::str_date_from_timestamp,
};

const DISPLAY_SEPARATOR: &str = "=";

fn default_user_status() -> UserStatus {
  UserStatus::default()
}

#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize,Default)]
pub struct User {
    pub id: i64,
    pub name:     String,
    pub fullname: String,
    pub email:    String,
    pub description: String,
    pub password: String,
    pub otp_enabled: bool,
    pub otp_verified: bool,
    pub otp_base32: String,
    pub otp_auth_url: String,
    pub otp_defs: String,
    pub roles: String,
    pub created:  String,
    pub lastaccess: String,
    #[serde(default = "default_user_status")]
    pub status: UserStatus,
    pub items: String,
    pub isadmin: bool,
}

impl fmt::Display for User {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Write strictly the first element into the supplied output
        // stream: `f`. Returns `fmt::Result` which indicates whether the
        // operation succeeded or failed. Note that `write!` uses syntax which
        // is very similar to `println!`.
        let sep = DISPLAY_SEPARATOR;
        let content = format!(
            "ID{} {}
Name{} {} 
FullName{} {}
Description{} {}
Email{} {}
Password{} {}
otp_enabled{} {}
otp_verified{} {}
otp_base32{} {}
otp_auth_url{} {}
otp_defs{} {}
Roles{} {}
Created{} {}
Last access{} {}
Status{} {}
Items{} {}
IsAdmin{} {}",
            sep, self.id,
            sep, self.name, sep, self.fullname,
            sep, self.description,
            sep, self.email,
            sep, self.password,
            sep, self.otp_enabled,
            sep, self.otp_verified,
            sep, self.otp_base32,
            sep, self.otp_auth_url,
            sep, self.otp_defs,
            sep, self.roles,
            sep, self.created, sep, self.lastaccess,
            sep, self.status,
            sep, self.items,
            sep, self.isadmin
        );
        write!(f, "{}", content)
    }
}

impl Entry for User {
    fn from_line(line: &str) -> Result<User, ParseIntError> {
        let parts: Vec<&str> = line.split(":").map(|part| part.trim()).collect();
        Ok(User {
            id: parts[0].to_string().parse::<i64>().unwrap_or_default(),
            name: parts[1].to_string(),
            fullname: parts[2].to_string(),
            description: parts[3].to_string(),
            email: parts[4].to_string(),
            password: parts[5].to_string(),
            otp_enabled: if parts[6] == "TRUE" { true } else { false},
            otp_verified: if parts[7] == "TRUE" { true } else { false},
            otp_base32: parts[8].to_string(),
            otp_auth_url: parts[9].to_string(),
            otp_defs: parts[10].to_string(),
            roles: parts[11].to_string(),
            created: parts[12].to_string(),
            lastaccess: parts[13].to_string(),
            status: UserStatus::from_str(&parts[14].to_string()),
            items: parts[15].to_string(),
            isadmin: if parts[16] == "TRUE" { true } else { false},
        })
    }
}
impl User {
    pub async fn add(self, store: &UserStore) -> Result<i64> {

        match store {
            UserStore::Sql(pool) => {
                let query_result = sqlx::query(
                    format!("INSERT INTO {} ( 
                    name, fullname, email, description, password, otp_enabled, otp_verified,
                    otp_base32,
                    otp_auth_url,
                    otp_defs,
                    roles, created, lastaccess, status, items, isadmin
                    ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", USERS_TABLENAME).as_str()
                )
                .bind(self.name)
                .bind(self.fullname)
                .bind(self.email)
                .bind(self.description)
                .bind(self.password)
                .bind(self.otp_enabled)
                .bind(self.otp_verified)
                .bind(self.otp_base32)
                .bind(self.otp_auth_url)
                .bind(self.otp_defs)
                .bind(self.roles)
                .bind(self.created)
                .bind(self.lastaccess)
                .bind(format!("{}",self.status))
                .bind(self.items)
                .bind(self.isadmin)
                .execute(pool).await?;
                Ok(query_result.last_insert_id().unwrap_or_default())
            },
            UserStore::File(file_path) => {
                // use itertools::Itertools;
                // let entries: Vec<String> = concat(vec![
                //     Entries::new(Path::new(&file_path)).map(|user: User|{ 
                //         user.line_format()
                //     }).collect(),
                //     vec![ self.line_format()]
                // ]);
                // Entries::<User>::write(Path::new(&file_path), &entries); 
                let all: Vec<User> = Entries::new(&file_path).collect();
                let id = if all.len() > 0 {
                    all[all.len()-1].id + 1
                } else {
                    1
                };
                let mut new_user = self.to_owned();
                new_user.id = id;
                let entries: Entries<User> = Entries::new(&file_path);
                match entries.append(new_user.line_format()) {
                    Ok(_) => Ok(id),
                    Err(e) => {
                        println!("Error add item to file: {}",e);
                        Err(anyhow!("No data added")).context("User add")
                    }
                }
            },
        }
    }
    pub async fn select(field: &str, value: &str, human: bool, store: &UserStore) -> Result<Self> {
        match store {
            UserStore::Sql(pool) => {
                let query_str =  format!("SELECT * FROM {} WHERE {} = ? ", USERS_TABLENAME, field);
                let mut stream = sqlx::query(
                    &query_str
                )
                .bind(value)
                //.map(|row: PgRow| {
                //    map the row into a user-defined domain type
                //})
                .fetch(pool);
                if let Some(row) = stream.try_next().await? {
                    let str_status: String = row.try_get("status")?;
                    let status = UserStatus::from_str(&str_status);
                    let created = if human {
                        let created: String = row.try_get("created")?;
                        str_date_from_timestamp(&created)
                    } else {
                        row.try_get("created")?
                    };
                    let lastaccess = if human {
                        let lastaccess: String = row.try_get("lastaccess")?;
                        str_date_from_timestamp(&lastaccess)
                    } else {
                        row.try_get("lastaccess")?
                    };
                    Ok(Self{
                        id: row.try_get("id")?,
                        name: row.try_get("name")?,
                        fullname: row.try_get("fullname")?,
                        email: row.try_get("email")?,
                        description: row.try_get("description")?,
                        password: row.try_get("password")?,
                        otp_enabled: row.try_get("otp_enabled")?,
                        otp_verified: row.try_get("otp_verified")?,
                        otp_base32: row.try_get("otp_base32")?,
                        otp_auth_url: row.try_get("otp_auth_url")?,
                        otp_defs: row.try_get("otp_defs")?,
                        roles: row.try_get("roles")?,
                        created,
                        lastaccess,
                        status,
                        items: row.try_get("items")?,
                        isadmin: row.try_get("isadmin")?,
                    })
                } else {
                    Err(anyhow!("No data found")).context("User select")
                }
            },
            UserStore::File(file_path) => {
                if let Some(user) = 
                    Entries::<User>::new(&file_path).find(|it| 
                        match field {
                            "id" => it.id == value.parse::<i64>().unwrap_or_default(),
                            "name" => it.name == value,
                            "fullname" => it.fullname == value,
                            "email" => it.email == value,
                            "description" => it.description == value,
                            "otp_base32" => it.otp_base32 == value,
                            "roles" => it.roles == value,
                            "items" => it.items == value,
                            "isadmin" => match value { 
                                "TRUE" => it.isadmin,
                                _ => !it.isadmin,
                            },
                            _ => false,
                        }
                    )
                {
                    Ok(user)
                } else {
                    Err(anyhow!("No data found")).context("User select")
                }
            },
        }
    }

    pub async fn delete(id: i64, store: &UserStore) -> Result<bool> {
        match store {
            UserStore::Sql(pool) => {
                let query_str = format!("DELETE FROM {} WHERE id = ?", USERS_TABLENAME);
                let query_result = sqlx::query(
                        &query_str
                    ) 
                    .bind(id)
                    .execute(pool).await?;
                 Ok(query_result.rows_affected() > 0)
            },
            UserStore::File(file_path) => {
                let new_entries: Vec<String> = 
                    Entries::<User>::new(&file_path).filter(|it| it.id != id).map(|user|
                       user.line_format() 
                    ).collect();
                let entries = Entries::<User>::new(&file_path);
                match entries.write(&new_entries) {
                    Ok(_) => Ok(true),
                    Err(e) => {
                        println!("Error data delete '{}': {}", id, e);
                        Err(anyhow!("No data delete").context("User delete"))
                    }
                }
            },
        }
    }
    pub async fn update(self, store: &UserStore) -> anyhow::Result<bool> {
        match store {
            UserStore::Sql(pool) => {
                let query_str = format!("UPDATE {} SET name = ?, fullname = ?, 
                    email = ?, description = ?, password = ?,
                    otp_enabled = ?, otp_verified = ?,
                    otp_base32 = ?, otp_auth_url = ?,
                    otp_defs = ?,
                    roles = ?,
                    created = ?, lastaccess = ?,
                    status = ?,
                    items = ?,
                    isadmin = ? 
                 WHERE id = ? ",
                    USERS_TABLENAME
                );
                let query_result = sqlx::query(
                        &query_str
                    ) 
                    .bind(self.name)
                    .bind(self.fullname)
                    .bind(self.email)
                    .bind(self.description)
                    .bind(self.password)
                    .bind(self.otp_enabled)
                    .bind(self.otp_verified)
                    .bind(self.otp_base32)
                    .bind(self.otp_auth_url)
                    .bind(self.otp_defs)
                    .bind(self.roles)
                    .bind(self.created)
                    .bind(self.lastaccess)
                    .bind(format!("{}",self.status))
                    .bind(self.items)
                    .bind(self.isadmin)
                    .bind(self.id)
                    .execute(pool).await?;
                 Ok(query_result.rows_affected() > 0)
            },
            UserStore::File(file_path) => {
                let new_entries: Vec<String> = Entries::new(&file_path).map(|user: User|{ 
                    if user.id == self.id {
                        self.to_owned().line_format()
                    } else {
                        user.line_format()
                    }
                }).collect();
                let entries = Entries::<User>::new(&file_path);
                match entries.write(&new_entries) {
                    Ok(_) => Ok(true),
                    Err(e) => {
                        println!("Error data update '{}': {}", self.id, e);
                        Err(anyhow!("No data updated").context("User update"))
                    }
                }
            },
        }
    }
    pub fn show(&self, sep: &str) {
        let content = if sep.is_empty() {
            format!( "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
                self.id,
                self.name, self.fullname,
                self.description,
                self.email,
                self.password,
                self.otp_enabled,
                self.otp_verified,
                self.otp_base32,
                self.otp_auth_url,
                self.otp_defs,
                self.roles,
                self.created,
                self.lastaccess,
                self.status,
                self.items,
                self.isadmin
            )
        } else {
            format!("{}",&self).replace(DISPLAY_SEPARATOR, sep)
        };
        println!("{}\n_____________________________",content);
    }
    pub async fn list(store: &UserStore, human: bool, show: bool, sep: &str) -> anyhow::Result<Vec<User>> {
        let mut usrs: Vec<User> = Vec::new();
        match store {
            UserStore::Sql(pool) => {
                let query_str =  format!("SELECT * FROM {}", USERS_TABLENAME);
                let mut stream = sqlx::query(
                    &query_str
                )
                //.map(|row: PgRow| {
                //  map the row into a user-defined domain type
                //})
                .fetch(pool);
                while let Some(row) = stream.try_next().await? {
                    let str_status: String = row.try_get("status")?;
                    let status = UserStatus::from_str(&str_status);
                    let created = if human {
                        let created: String = row.try_get("created")?;
                        str_date_from_timestamp(&created)
                    } else {
                        row.try_get("created")?
                    };
                    let lastaccess = if human {
                        let lastaccess: String = row.try_get("lastaccess")?;
                        str_date_from_timestamp(&lastaccess)
                    } else {
                        row.try_get("lastaccess")?
                    };
                    let user = Self{
                        id: row.try_get("id")?,
                        name: row.try_get("name")?,
                        fullname: row.try_get("fullname")?,
                        email: row.try_get("email")?,
                        description: row.try_get("description")?,
                        password: row.try_get("password")?,
                        otp_enabled: row.try_get("otp_enabled")?,
                        otp_verified: row.try_get("otp_verified")?,
                        otp_base32: row.try_get("otp_base32")?,
                        otp_auth_url: row.try_get("otp_auth_url")?,
                        otp_defs: row.try_get("otp_defs")?,
                        roles: row.try_get("roles")?,
                        created,
                        lastaccess,
                        status,
                        items: row.try_get("items")?,
                        isadmin: row.try_get("isadmin")?,
                    };
                    if show { user.show(sep); }
                    usrs.push(user);
                }
                Ok(usrs)
            },
            UserStore::File(file_path) => {
                let all: Vec<User> = Entries::new(&file_path).collect();
                if show {
                    for user in all.to_owned() { if show { user.show(sep); } }
                }
                Ok(all)
            },
            // UserStore::None => Err(anyhow!("No store set")).context("Users list"),
        }
    }       
    pub async fn count(store: &UserStore) -> anyhow::Result<i64> {
        match store {
            UserStore::Sql(pool) => {

                let query_str =  format!("SELECT count(*) as total FROM {}", USERS_TABLENAME);
                let row = sqlx::query(
                    &query_str
                )
                .fetch_one(pool).await?;

                let total: i64 = row.try_get("total")?;
                Ok(total)
            },
            UserStore::File(file_path) => {
                let all: Vec<User> = Entries::new(&file_path).collect();
                Ok(all.len() as i64)
            },
        }
    }
    fn line_format(self) -> String {
        format!( "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
            self.id,
            self.name, self.fullname,
            self.description,
            self.email,
            self.password,
            self.otp_enabled,
            self.otp_verified,
            self.otp_base32,
            self.otp_auth_url,
            self.otp_defs,
            self.roles,
            self.created, self.lastaccess,
            self.status,
            self.items,
            self.isadmin
        )
    }
    pub fn hash_items(items: &str) -> HashMap<String,String> {
        if items.is_empty() || ! items.starts_with("{") {
            HashMap::new()
        } else {
            serde_json::from_str(items).unwrap_or_else(|e|{
                println!("Error to pars string to user items: {}",e);
                HashMap::new()
            })
        }
    }
    pub fn items(&self) -> HashMap<String,String> {
        Self::hash_items(&self.items)
    }
    pub fn json_items(items: HashMap<String,String>) -> String {
        serde_json::to_string(&items).unwrap_or_else(|e|{
            println!("Error to convert user items to string: {}",e);
            String::from("")
        })
    }
    pub fn from_data(&mut self, user_data: UserData) {
        if !user_data.name.is_empty() {
            self.name = user_data.name.to_owned();
        }
        if !user_data.fullname.is_empty() {
            self.fullname = user_data.fullname.to_owned();
        }
        if !user_data.description.is_empty() {
            self.description = user_data.description.to_owned();
        }
        if !user_data.email.is_empty() {
            self.email = user_data.email.to_owned();
        }
        if !user_data.otp_code.is_empty() {
            self.otp_base32 = user_data.otp_code.to_owned();
        }
        if !user_data.otp_url.is_empty() {
            self.otp_auth_url = user_data.otp_url.to_owned();
        }
        if !user_data.roles.is_empty() {
            self.roles = user_data.roles.to_owned();
        }
        if !user_data.items.is_empty() {
            let mut items_hash = self.items();
            for (key,val) in user_data.items {
                items_hash.insert(key,val);
            }
            self.items = Self::json_items(items_hash);
        }
    }
    pub fn disable_totp(&mut self) {
        self.otp_base32 = String::from("");
        self.otp_auth_url = String::from("");
        self.otp_defs = String::from("");
        self.otp_verified =  false;
        self.otp_enabled =  false;
    }
    pub fn from_user(&mut self, new_user: User) {
        if !new_user.name.is_empty() {
            self.name = new_user.name.to_owned();
        }
        if !new_user.fullname.is_empty() {
            self.fullname = new_user.fullname.to_owned();
        }
        if !new_user.description.is_empty() {
            self.description = new_user.description.to_owned();
        }
        if !new_user.email.is_empty() {
            self.email = new_user.email.to_owned();
        }
        if !new_user.password.is_empty() {
            self.password = new_user.password.to_owned();
        }
        if new_user.otp_enabled  {
            self.disable_totp();
        } else {
            if !new_user.otp_base32.is_empty() {
                self.otp_base32 = new_user.otp_base32.to_owned();
            }
            if !new_user.otp_auth_url.is_empty() {
                self.otp_auth_url = new_user.otp_auth_url.to_owned();
            }
            if !new_user.otp_defs.is_empty() {
                self.otp_defs = new_user.otp_defs.to_owned();
            }
            self.otp_verified =  new_user.otp_verified;
        }
        if !new_user.roles.is_empty() {
            self.roles = new_user.roles.to_owned();
        }
        if !new_user.items.is_empty() {
            self.items = new_user.items.to_owned();
        }
        if new_user.status != self.status {
            self.status = new_user.status.to_owned();
        }
    }
    pub fn session_data(&self) -> String {
        format!("{}|{}|{}|{}|{}|{}|{}",
            &self.id,
            &self.name,
            &self.email,
            &self.roles,
            &self.items,
            &self.isadmin,
            &self.status
        )
    }
    pub fn estimate_password(word: &str) -> String  {
         match zxcvbn::zxcvbn(word, &[]) {
            Ok(estimate) => {
                if let Some(feedback) = estimate.feedback() {
                    let arr_suggestions: Vec<String> = feedback.suggestions().iter().map(|s| format!("{}",s)).collect();
                    let suggestions = arr_suggestions.join("\n");
                    if let Some(warning) = feedback.warning() {
                        let warning = format!("{}", warning);
                        format!("{}|{}|{}",estimate.score(),suggestions,warning)
                    } else {
                        format!("{}|{}|",estimate.score(),suggestions)
                    }
                } else {
                    format!("{}||",estimate.score())
                }
            },
            Err(e) => {
                println!("Error password strength estimator: {}", e);
                String::from("-1|||")
            }
        }
    }
    pub fn password_score(word: &str) -> u8 {
         match zxcvbn::zxcvbn(word, &[]) {
            Ok(estimate) => {
                estimate.score()
            },
            Err(e) => {
                println!("Error password strength estimator: {}", e);
                0
            }
        }
    }
    #[allow(dead_code)]
    pub fn has_auth_role(&self, auth_roles: Vec<String>) -> bool {
        for role in auth_roles {
            if self.roles.contains(&role) {
                return true;
            }
        }
        return false;
    }
}