use serde::{Deserialize,Serialize}; 
use sqlx::Row;
use futures::TryStreamExt;
use anyhow::{anyhow,Context, Result};
use std::fmt;
use std::num::ParseIntError;
use crate::{
    users::{
        entries::{Entries,Entry},
        UserStore,
    },
    OPENID_USERS_TABLENAME,
    tools::str_date_from_timestamp,
};
const DISPLAY_SEPARATOR: &str = "=";

#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize,Default)]
pub struct OpenidUser {
    pub id: i64,
    pub application: String,
    pub appkey: String,
    pub name: String,
    pub userid: i64,
    pub description: String,
    pub created:  String,
    pub lastaccess: String,
}

fn default_empty() -> String {
    "".to_string()
}
#[derive(Default,Deserialize,Serialize,Debug,Clone)]
pub struct OpenidUserData {
    #[serde(default = "default_empty")]
    pub id: String,
    #[serde(default = "default_empty")]
    pub name: String,
    #[serde(default = "default_empty")]
    pub application: String,
    #[serde(default = "default_empty")]
    pub appkey: String,
    #[serde(default = "default_empty")]
    pub userid: String,
    #[serde(default = "default_empty")]
    pub description: String,
}

impl fmt::Display for OpenidUser {
    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{} {} 
Application{} {}
AppKey {} {}
UserId{} {}
Description{} {}
Created{} {}
Last access{} {}",
            sep, self.id,
            sep, self.name,
            sep, self.application,
            sep, self.appkey,
            sep, self.userid,
            sep, self.description,
            sep, self.created,
            sep, self.lastaccess,
        );
        write!(f, "{}", content)
    }
}

impl Entry for OpenidUser {
    fn from_line(line: &str) -> Result<OpenidUser, ParseIntError> {
        let parts: Vec<&str> = line.split(":").map(|part| part.trim()).collect();
        Ok(OpenidUser {
            id: parts[0].to_string().parse::<i64>().unwrap_or_default(),
            name: parts[1].to_string(),
            application: parts[2].to_string(),
            appkey: parts[3].to_string(),
            description: parts[4].to_string(),
            userid: parts[5].to_string().parse::<i64>().unwrap_or_default(),
            created: parts[6].to_string(),
            lastaccess: parts[7].to_string(),
        })
    }
}
impl OpenidUser {
    pub async fn add(self, store: &UserStore) -> Result<i64> {

        match store {
            UserStore::Sql(pool) => {
                let query_result = sqlx::query(
                    format!("INSERT INTO {} ( 
                    name, application, appkey,
                    userid,
                    description,
                    created, lastaccess
                    ) VALUES ( ?, ?, ?, ?, ?, ?, ?)", OPENID_USERS_TABLENAME).as_str()
                )
                .bind(self.name)
                .bind(self.application)
                .bind(self.appkey)
                .bind(self.userid)
                .bind(self.description)
                .bind(self.created)
                .bind(self.lastaccess)
                .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: OpenidUser|{
                //         user.line_format()
                //     }).collect(),
                //     vec![ self.line_format()]
                // ]);
                // Entries::<OpenidUser>::write(Path::new(&file_path), &entries);
                let all: Vec<OpenidUser> = 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<OpenidUser> = 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("OpenidUser add")
                    }
                }
            },
        }
    }
    #[allow(dead_code)]
    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 '{}' = ?", OPENID_USERS_TABLENAME, field);
                dbg!(&query_str);
                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 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")?,
                        application: row.try_get("application")?,
                        appkey: row.try_get("appkey")?,
                        userid: row.try_get("userid")?,
                        description: row.try_get("description")?,
                        created,
                        lastaccess,
                    })
                } else {
                    Err(anyhow!("No data found")).context("OpenidUser select")
                }
            },
            UserStore::File(file_path) => {
                if let Some(user) =
                    Entries::<OpenidUser>::new(&file_path).find(|it|
                        match field {
                            "id" => it.id == value.parse::<i64>().unwrap_or_default(),
                            "name" => it.name == value,
                            "application" => it.application == value,
                            "appkey" => it.appkey == value,
                            "userid" => it.userid == value.parse::<i64>().unwrap_or_default(),
                            _ => false,
                        }
                    )
                {
                    Ok(user)
                } else {
                    Err(anyhow!("No data found")).context("OpenidUser select")
                }
            },
        }
    }
    pub async fn app_select(app: &str , field: &str, value: &str, human: bool, store: &UserStore) -> Result<Self> {
        match store {
            UserStore::Sql(pool) => {
                let query_str =  format!("SELECT * FROM {} WHERE application = '{}' AND {} = ?", OPENID_USERS_TABLENAME, app, 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 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")?,
                        application: row.try_get("application")?,
                        appkey: row.try_get("appkey")?,
                        userid: row.try_get("userid")?,
                        description: row.try_get("description")?,
                        created,
                        lastaccess,
                    })
                } else {
                    Err(anyhow!("No data found")).context("OpenidUser select")
                }
            },
            UserStore::File(file_path) => {
                if let Some(user) = 
                    Entries::<OpenidUser>::new(&file_path).find(|it|
                        match field {
                            "id" => it.id == value.parse::<i64>().unwrap_or_default(),
                            "name" => it.name == value,
                            "application" => it.application == app,
                            "appkey" => it.application == value,
                            "userid" => it.userid == value.parse::<i64>().unwrap_or_default(),
                            _ => false,
                        }
                    )
                {
                    Ok(user)
                } else {
                    Err(anyhow!("No data found")).context("OpenidUser select")
                }
            },
        }
    }

    pub async fn delete(id: i64, store: &UserStore) -> Result<bool> {
        match store {
            UserStore::Sql(pool) => {
                let query_str = format!("DELETE FROM {} WHERE id = ?", OPENID_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::<OpenidUser>::new(&file_path).filter(|it| it.id != id).map(|user|
                       user.line_format() 
                    ).collect();
                let entries = Entries::<OpenidUser>::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("OpenidUser delete"))
                    }
                }
            },
        }
    }
    pub async fn update(self, store: &UserStore) -> anyhow::Result<bool> {
        match store {
            UserStore::Sql(pool) => {
                let query_str = format!("UPDATE {} SET name = ?,
                    application = ?,
                    appkey = ?,
                    userid = ?,
                    description = ?,
                    created = ?,
                    lastaccess = ?
                 WHERE id = ? ",
                    OPENID_USERS_TABLENAME
                );
                let query_result = sqlx::query(
                        &query_str
                    ) 
                    .bind(self.name)
                    .bind(self.application)
                    .bind(self.appkey)
                    .bind(self.userid)
                    .bind(self.description)
                    .bind(self.created)
                    .bind(self.lastaccess)
                    .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: OpenidUser|{
                    if user.id == self.id {
                        self.to_owned().line_format()
                    } else {
                        user.line_format()
                    }
                }).collect();
                let entries = Entries::<OpenidUser>::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("OpenidUser update"))
                    }
                }
            },
        }
    }
    pub fn show(&self, sep: &str) {
        let content = if sep.is_empty() {
            format!( "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
                self.id,
                self.name,
                self.application,
                self.appkey,
                self.userid,
                self.description,
                self.created,
                self.lastaccess,
            )
        } else {
            format!("{}",self).replace(DISPLAY_SEPARATOR, sep)
        };
        println!("{}\n_____________________________",content);
    }
    pub async fn users_list( query_str: &str, store: &UserStore, human: bool, show: bool, sep: &str) -> anyhow::Result<Vec<OpenidUser>> {
        let mut usrs: Vec<OpenidUser> = Vec::new();
        match store {
            UserStore::Sql(pool) => {
                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 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")?,
                        application: row.try_get("application")?,
                        appkey: row.try_get("appkey")?,
                        userid: row.try_get("userid")?,
                        description: row.try_get("description")?,
                        created,
                        lastaccess,
                    };
                    if show { user.show(sep); }
                    usrs.push(user);
                }
                Ok(usrs)
            },
            UserStore::File(file_path) => {
                let all: Vec<OpenidUser> = 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("OpenidUsers list"),
        }
    }
    #[allow(dead_code)]
    pub async fn list(store: &UserStore, human: bool, show: bool, sep: &str) -> anyhow::Result<Vec<OpenidUser>> {
        OpenidUser::users_list(
            &format!("SELECT * FROM {}", OPENID_USERS_TABLENAME),
            store, human, show, sep
        ).await
    }
    pub async fn list_selection(field: &str, value: &str, store: &UserStore, human: bool, show: bool, sep: &str) -> anyhow::Result<Vec<OpenidUser>> {
        OpenidUser::users_list(
           &format!("SELECT * FROM {} WHERE {} = '{}'", OPENID_USERS_TABLENAME, &field, &value),
            store, human, show, sep
        ).await
    }
    #[allow(dead_code)]
    pub async fn count(store: &UserStore) -> anyhow::Result<i64> {
        match store {
            UserStore::Sql(pool) => {
                let query_str =  format!("SELECT count(*) as total FROM {}", OPENID_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<OpenidUser> = Entries::new(&file_path).collect();
                Ok(all.len() as i64)
            },
        }
    }
    fn line_format(self) -> String {
        format!( "{}:{}:{}:{}:{}:{}:{}:{}",
            self.id,
            self.name,
            self.application,
            self.appkey,
            self.description,
            self.userid,
            self.created,
            self.lastaccess,
        )
    }
    /*
    pub fn from_data(&mut self, user_data: OpenidUserData) {
        if !user_data.name.is_empty() {
            self.name = user_data.name.to_owned();
        }
        if !user_data.application.is_empty() {
            self.application = user_data.application.to_owned();
        }
        if !user_data.description.is_empty() {
            self.description = user_data.description.to_owned();
        }
    }
    pub fn from_user(&mut self, new_user: OpenidUser) {
        if !new_user.name.is_empty() {
            self.name = new_user.name.to_owned();
        }
        if !new_user.application.is_empty() {
            self.application = new_user.application.to_owned();
        }
        if !new_user.description.is_empty() {
            self.description = new_user.description.to_owned();
        }
    }
    pub fn session_data(&self) -> String {
        format!("{}|{}|{}|{}|{}",
            &self.id,
            &self.name,
            &self.application,
            &self.appkey,
            &self.userid
        )
    }
    */
    pub async fn sync_ids(userid: &str, open_ids: &str, store: &UserStore) {
        let openid_sel = OpenidUser::list_selection(
            "userid", userid, store, true,false, "|"
        ).await.unwrap_or_else(|e| {
            println!("Error list selection {}: {}", &userid, e);
            Vec::new()
        });
        let new_open_ids: Vec<String> = open_ids.split(",").map(|s| s.to_string()).collect();
        for itm in openid_sel {
            if new_open_ids.len() == 0  || ! new_open_ids.contains(&itm.appkey) {
                let _res = OpenidUser::delete(itm.id, &store).await.unwrap_or_else(|e| {
                    println!("Error delete {}: {}",&itm.appkey,e);
                    false
                });
            }
        }
    }
}