use crate::biz::collab::access_control::CollabAccessControlImpl;
use crate::biz::collab::storage::CollabPostgresDBStorage;
use crate::biz::pg_listener::PgListeners;
use crate::biz::workspace::access_control::WorkspaceAccessControlImpl;
use crate::component::auth::LoggedUser;
use crate::config::config::Config;
use chrono::{DateTime, Utc};
use database::file::bucket_s3_impl::S3BucketStorage;
use snowflake::Snowflake;
use sqlx::PgPool;
use std::collections::BTreeMap;
use std::sync::Arc;
use tokio::sync::RwLock;

#[derive(Clone)]
pub struct AppState {
  pub pg_pool: PgPool,
  pub config: Arc<Config>,
  pub user: Arc<RwLock<UserCache>>,
  pub id_gen: Arc<RwLock<Snowflake>>,
  pub gotrue_client: gotrue::api::Client,
  pub redis_client: redis::aio::ConnectionManager,
  pub collab_storage: Arc<CollabPostgresDBStorage>,
  pub collab_access_control: Arc<CollabAccessControlImpl>,
  pub workspace_access_control: Arc<WorkspaceAccessControlImpl>,
  pub bucket_storage: Arc<S3BucketStorage>,
  pub pg_listeners: Arc<PgListeners>,
}

impl AppState {
  pub async fn load_users(_pool: &PgPool) {
    todo!()
  }

  pub async fn next_user_id(&self) -> i64 {
    self.id_gen.write().await.next_id()
  }
}

#[derive(Clone, Debug, Copy)]
enum AuthStatus {
  Authorized(DateTime<Utc>),
  NotAuthorized,
}

pub const EXPIRED_DURATION_DAYS: i64 = 30;

#[derive(Debug, Default)]
pub struct UserCache {
  // Keep track the user authentication state
  user: BTreeMap<i64, AuthStatus>,
}

impl UserCache {
  pub fn new() -> Self {
    UserCache::default()
  }

  pub fn is_authorized(&self, user: &LoggedUser) -> bool {
    match self.user.get(user.expose_secret()) {
      None => {
        tracing::debug!("user not login yet or server was reboot");
        false
      },
      Some(status) => match *status {
        AuthStatus::Authorized(last_time) => {
          let current_time = Utc::now();
          let days = (current_time - last_time).num_days();
          days < EXPIRED_DURATION_DAYS
        },
        AuthStatus::NotAuthorized => {
          tracing::debug!("user logout already");
          false
        },
      },
    }
  }

  pub fn authorized(&mut self, user: LoggedUser) {
    self.user.insert(
      user.expose_secret().to_owned(),
      AuthStatus::Authorized(Utc::now()),
    );
  }

  pub fn unauthorized(&mut self, user: LoggedUser) {
    self
      .user
      .insert(user.expose_secret().to_owned(), AuthStatus::NotAuthorized);
  }
}
