pub use super::_entities::o_auth2_sessions::{ActiveModel, Column, Entity, Model};
use crate::models::{o_auth2_sessions, users};
use async_trait::async_trait;
use chrono::Utc;
use loco_oauth2::base_oauth2::{basic::BasicTokenResponse, TokenResponse};
use loco_oauth2::models::oauth2_sessions::OAuth2SessionsTrait;
use loco_rs::prelude::*;
use sea_orm::entity::prelude::*;

pub type OAuth2Sessions = Entity;

#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
    async fn before_save<C>(self, _db: &C, insert: bool) -> std::result::Result<Self, DbErr>
    where
        C: ConnectionTrait,
    {
        if !insert && self.updated_at.is_unchanged() {
            let mut this = self;
            this.updated_at = sea_orm::ActiveValue::Set(Utc::now().into());
            Ok(this)
        } else {
            Ok(self)
        }
    }
}

#[async_trait]
impl OAuth2SessionsTrait<users::Model> for Model {
    /// Whether the session identified by `session_id` has expired.
    async fn is_expired(db: &DatabaseConnection, session_id: &str) -> ModelResult<bool> {
        let session = o_auth2_sessions::Entity::find()
            .filter(o_auth2_sessions::Column::SessionId.eq(session_id))
            .one(db)
            .await?
            .ok_or_else(|| ModelError::EntityNotFound)?;
        Ok(session.expires_at < Utc::now().fixed_offset())
    }

    /// Create or refresh the session row for `user` from the provider token.
    async fn upsert_with_oauth2(
        db: &DatabaseConnection,
        token: &BasicTokenResponse,
        user: &users::Model,
    ) -> ModelResult<Self> {
        let txn = db.begin().await?;
        let session_id = token.access_token().secret().clone();
        let expires_at = Utc::now()
            + token
                .expires_in()
                .unwrap_or(std::time::Duration::from_secs(3600));

        let session = match o_auth2_sessions::Entity::find()
            .filter(o_auth2_sessions::Column::UserId.eq(user.id))
            .one(&txn)
            .await?
        {
            Some(session) => {
                let mut session: o_auth2_sessions::ActiveModel = session.into();
                session.session_id = ActiveValue::set(session_id);
                session.expires_at = ActiveValue::set(expires_at.into());
                session.updated_at = ActiveValue::set(Utc::now().into());
                session.update(&txn).await?
            }
            None => {
                o_auth2_sessions::ActiveModel {
                    session_id: ActiveValue::set(session_id),
                    expires_at: ActiveValue::set(expires_at.into()),
                    user_id: ActiveValue::set(user.id),
                    ..Default::default()
                }
                .insert(&txn)
                .await?
            }
        };
        txn.commit().await?;
        Ok(session)
    }
}
