//use async_trait::async_trait;
use async_session::{Session, SessionStore,MemoryStore};
use async_sqlx_session::SqliteSessionStore;

// use axum::{
//     http::{
//         StatusCode,
//         request::Parts,
//     },
//     extract::FromRequestParts,
// };
// use tower_cookies::Cookies;
use std::sync::{Arc,Mutex};
use rand_chacha::ChaCha8Rng;
pub type Random = Arc<Mutex<ChaCha8Rng>>;

use crate::{
//    SESSION_COOKIE_NAME,
    defs::{
        FileStore,
        AppDBs,
        SESSION_ID,
        USER_DATA,
    },
    users::{
        User,
        UserStatus,
    },
};

#[derive(Clone)]
pub enum SessionStoreDB {
    Files(FileStore),
    SqlLite(SqliteSessionStore),
    Memory(MemoryStore),
    None,
}
/// In `SessionStoreDB` creation match the corresponding store to return it as item argument 
impl SessionStoreDB {
    pub fn connect_file_store(store: FileStore) -> Self{
        SessionStoreDB::Files(store)
    }
    pub fn connect_memory_store() -> Self{
        SessionStoreDB::Memory(MemoryStore::new())
    } 
    pub fn connect_sqlite_store(store: SqliteSessionStore) -> Self{
        SessionStoreDB::SqlLite(store)
    } 
    pub async fn store_session_data(id: &str, user_data: &str, expire: u64, app_dbs: &AppDBs) -> String {
        // dbg!("user_data: {}",&user_data);
        let mut session = Session::new();
        if ! id.is_empty() {
            session.insert(SESSION_ID, id).unwrap_or_else(|e|{
                println!("Error insert session {}",e);
            });
        }
        if ! user_data.is_empty() {
            session.insert(USER_DATA, user_data).unwrap_or_else(|e|{
                println!("Error insert session {}",e);
            });
        }
        if expire > 0 {
            session.expire_in(std::time::Duration::from_secs(expire));
        } else if app_dbs.config.session_expire > 0 {
            session.expire_in(std::time::Duration::from_secs(app_dbs.config.session_expire));
        }
        //session.into_cookie_value().unwrap_or_default()
        match app_dbs.sessions_store_db.to_owned() {
            SessionStoreDB::Files(store) => {
                store.store_session(session).await.unwrap_or_else(|e|{
                    println!("Error store session {}",e);
                    None
                }).unwrap_or_default()
            },
            SessionStoreDB::SqlLite(store) => {
               store.store_session(session).await.unwrap_or_else(|e|{
                    println!("Error store session {}",e);
                    None
                }).unwrap_or_default() 
            },
            SessionStoreDB::Memory(store) => {
                store.store_session(session).await.unwrap_or_else(|e|{
                    println!("Error store session {}",e);
                    None
                }).unwrap_or_default()
            },
            SessionStoreDB::None => {
                String::from("")
            },
        }
    }
    pub async fn update_session_data(session: Session, app_dbs: &AppDBs) -> String {
        //session.into_cookie_value().unwrap_or_default()
        let cur_session = session.clone();
        match app_dbs.sessions_store_db.to_owned() {
            SessionStoreDB::Files(store) => {
                store.store_session(session).await.unwrap_or_else(|e|{
                    println!("Error store session {}",e);
                    None
                }).unwrap_or_default()
            },
            SessionStoreDB::SqlLite(store) => {
                let _ = store.destroy_session(session).await;
                store.store_session(cur_session).await.unwrap_or_else(|e|{
                    println!("Error store session {}",e);
                    None
                }).unwrap_or_default() 
            },
            SessionStoreDB::Memory(store) => {
                store.store_session(session).await.unwrap_or_else(|e|{
                    println!("Error store session {}",e);
                    None
                }).unwrap_or_default()
            },
            SessionStoreDB::None => {
                String::from("")
            },
        }
    }
    pub async fn cleanup_data(app_dbs: &AppDBs) {
        //session.into_cookie_value().unwrap_or_default()
        match app_dbs.sessions_store_db.to_owned() {
            SessionStoreDB::Files(store) => {
                let _ = store.cleanup().await;
            },
            SessionStoreDB::SqlLite(store) => {
                let _ = store.cleanup().await;
            },
            SessionStoreDB::Memory(store) => {
                let _ = store.cleanup().await;
            },
            SessionStoreDB::None => {
            },
        }
    }
}

#[derive(Clone, Default, Debug)]
pub struct AuthState{
    pub session: Option<Session>,
    pub user: Option<User>, 
}
impl AuthState {
    // pub fn new(session: Session, user: User) -> Self {
    //     let sid = session.get("sid").unwrap_or_default();
    //     let user_store = &app_dbs.user_store;
    //     let user = User::select("id", sid, user_store).await.unwrap_or_default();
    //     //let user = User::from_id(sid, app_dbs);
    //     Self {
    //         session,
    //         user
    //     }
    // }
    // pub fn sid(&self) -> String {
    //     if let Some(session) = &self.session {
    //         session.get("sid").unwrap_or_default()
    //     } else {
    //         String::from("")
    //     }
    // }
    pub fn id(&self) -> String {
        if let Some(session) = &self.session {
            session.id().to_owned()
        } else {
            String::from("")
        }
    }
    #[allow(dead_code)]
    pub fn ses_expired(&self) -> bool {
        if let Some(session) = &self.session {
            session.is_expired()
        } else {
            true
        }
    }
    pub fn ses_destroyed(&self) -> bool {
        if let Some(session) = &self.session {
            session.is_destroyed()
        } else {
            true
        }
    }
    pub fn destroy(&self) -> bool {
        if let Some(mut session) = self.session.to_owned() {
            session.destroy();
            self.ses_destroyed()
        } else {
            true
        }
    }
    #[allow(dead_code)]
    pub fn ses_validate(&self) -> bool {
        if let Some(session) = &self.session {
            session.clone().validate().is_some()
        } else {
            false
        }
    }
    pub fn user_data(&self) -> Vec<String> {
        if let Some(session) = &self.session {
            let user_data: String = session.get(USER_DATA).unwrap_or_default();
            user_data.split("|").map(|s| s.to_string()).collect()
        } else {
            Vec::new()
        }
    }
    pub fn user_id(&self) -> String {
        let user_data = self.user_data();
        if user_data.len() > 0 && user_data[0] != "0" {
            user_data[0].to_owned()
        } else {
            String::from("")
        }
    }
    pub fn user_name(&self) -> String {
        let user_data = self.user_data();
        if user_data.len() > 1 && user_data[1] != "" {
            user_data[1].to_owned()
        } else {
            String::from("")
        }
    }
    pub fn user_email(&self) -> String {
        let user_data = self.user_data();
        if user_data.len() > 2 && user_data[2] != "" {
            user_data[2].to_owned()
        } else {
            String::from("")
        }
    }
    pub fn user_roles(&self) -> String {
        let user_data = self.user_data();
        if user_data.len() > 3 && user_data[3] != "" {
            user_data[3].to_owned()
        } else {
            String::from("")
        }
    } 
    pub fn user_items(&self) -> String {
        let user_data = self.user_data();
        if user_data.len() > 4 && user_data[4] != "" {
            user_data[4].to_owned()
        } else {
            String::from("")
        }
    } 
    pub fn is_admin(&self) -> bool {
        let user_data = self.user_data();
        if user_data.len() > 5 && user_data[5] != "" {
            user_data[5] == "true"  || user_data[5] == "1"
        } else {
            false
        }
    } 
    #[allow(dead_code)]
    pub fn user_status(&self) -> UserStatus {
        let user_data = self.user_data();
        if user_data.len() > 6 && user_data[6] != "" {
            UserStatus::from_str(&user_data[6])
        } else {
            UserStatus::Unknown
        }
    } 
    pub fn has_auth_role(&self, auth_roles: &Vec<String>) -> bool {
        let roles = self.user_roles();
        for role in auth_roles.to_owned() {
            if roles.contains(&role) {
                return true;
            }
        }
        return false;
    }
    pub async fn expire_in(&mut self, expire_secs: u64, app_dbs: &AppDBs ) -> Self {
        if let Some(mut session) = self.session.to_owned() {
            session.expire_in(std::time::Duration::from_secs(expire_secs));
            let _ = SessionStoreDB::update_session_data(session.to_owned(),&app_dbs).await;
            Self {
                session: Some(session),
                user: self.user.to_owned(),
            }
        } else {
            self.to_owned()
        }
    } 
    #[allow(dead_code)]
    pub async fn from_data(&self, app_dbs: &AppDBs) -> Self {
        let user_id = self.user_id();
        let user = if !user_id.is_empty() {
            // dbg!(&user_id);
            match User::select("id", &user_id, false, &app_dbs.user_store).await {
                Ok(user) => Some(user),
                Err(e) => {
                    println!("Error user {}: {:#}",&user_id,e);
                    None
                }
            }
        } else {
            None
        };
        // dbg!(&user);
        Self {
            session: self.session.to_owned(),
            user,
        }
    }
    pub async fn from_cookie(session_cookie: String, app_dbs: &AppDBs) -> Self {
        let result_session = match app_dbs.sessions_store_db.to_owned() {
            SessionStoreDB::Files(store) => 
                store
                    .load_session(session_cookie)
                    .await
            ,
            SessionStoreDB::SqlLite(store) =>
                store
                    .load_session(session_cookie)
                    .await
            ,
            SessionStoreDB::Memory(store) => 
                store
                    .load_session(session_cookie.to_string())
                    .await
            ,
            SessionStoreDB::None => {
                dbg!("No store");
                return AuthState::default();
            },
        };
        // dbg!(&result_session);
        match result_session {
            Ok(op_session) => if let Some(session) = op_session {
                if let Some(sid) = session.get::<String>("sid") {
                    // dbg!(
                    //     "UserDataFromSession: session decoded success, user_data={}",
                    //     &session
                    // );
                    let user_store = &app_dbs.user_store;
                    let user = User::select("id", &sid, false, user_store).await.unwrap_or_default();
                    return AuthState {
                        session: Some(session),
                        user: Some(user)
                    };
                } else {
                    println!("No `sid` found in session");
                }
            },
            Err(e) => {
                println!("Error session {}", e);
            }
        }  
        AuthState::default()
    }
}

/*
#[async_trait]
impl<S> FromRequestParts<S> for AuthState
where
    S: Send + Sync,
{
    type Rejection = (StatusCode, &'static str);

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        // let h = parts.headers.to_owned();
        // dbg!(&h);
/*
        if let Some(app_dbs) = parts.extensions.get::<Arc<AppDBs>>()  {
            if let Some(cookies) = parts.extensions.get::<Cookies>()  {
                if let Some(s_cookie) = cookies.get(SESSION_COOKIE_NAME) {
                    let session_cookie = s_cookie.to_string().replace(&format!("{}=",SESSION_COOKIE_NAME),"");
                    println!("From request parts cookie: {}",session_cookie);
                    // continue to decode the session cookie
                    // return Ok(
                    //     AuthState::from_cookie(session_cookie, app_dbs).await
                    // );           
                } 
            }
        }
        */
        Ok(AuthState::default())
        // Err((
        //     StatusCode::INTERNAL_SERVER_ERROR,
        //     "No `user_data` found in session",
        // ))
        // let cookie = Option::<Cookie>::g(parts, state);
        // let cookie = Cookies::get(parts, state);
        // if let Some(_user_agent) = parts.headers.get(USER_AGENT) {
        //     Ok(AuthState(None))
        // } else {
        //     Err((StatusCode::BAD_REQUEST, "`User-Agent` header is missing"))
        // }
//         let cookie = Option::<TypedHeader<Cookie>>::from_request_parts(req, state)
        //let cookies = Cookies::from(pa, state).await?;

//         let info: String = cookies
//             .get(SESSION_COOKIE_NAME)
// //            .and_then(|c| c.value().parse().ok())
//             .unwrap_or_default()
//             ;
//         dbg!(&info);
//         //cookies.add(Cookie::new(COOKIE_NAME, visited.to_string()));
     }
}
*/