use crate::error::OrmError;
use crate::repository::read_repository::ReadRepository;
use crate::traits::repository::Repository;

#[async_trait::async_trait]
pub trait UpdateRepository where Self: Repository + ReadRepository {
    async fn update_by_id(entity: Self) -> Result<Self, OrmError>;
    async fn bulk_update_by_id(entities: Vec<Self>) -> Result<Vec<Self>, OrmError>;
    async fn bulk_update_by_example(example: Self, update: Self) -> Result<Vec<Self>, OrmError>;
}

#[async_trait::async_trait]
impl <T> UpdateRepository for T where T: Repository {
    async fn update_by_id(entity: Self) -> Result<Self, OrmError> {
        let pool = T::get_pool().await?;
        Ok(entity.update(&pool).await?)
    }

    async fn bulk_update_by_id(entities: Vec<Self>) -> Result<Vec<Self>, OrmError> {
        let pool = T::get_pool().await?;
        Ok(T::bulk_update(entities, &pool).await?)
    }
    
    async fn bulk_update_by_example(example: Self, update: Self) -> Result<Vec<Self>, OrmError> {
        let pool = T::get_pool().await?;
        let result = T::update_by_example(example, update, &pool).await?;
        Ok(result)
    }
}

#[rglw::codegen::test(name="Test Update Repository")]
mod tests {
    use crate::repository::Repository;
    use crate::{error::OrmError, get_connection, traits::entity::Entity};
    use crate::{JoinType, get_pool, pagination::Pagination, query::{Query, SelectQuery, InsertQuery, UpdateQuery, DropQuery, DeleteQuery, WhereStatement, WhereType, WhereType::And, WhereType::Or}};
    use rglw_orm_codegen::{Entity};
    use sqlx::{Any, Pool};
    use std::path::Path;
    use std::{env, fs};
    use crate::repository::update_repository::UpdateRepository;
    use crate::pagination::{calculate_pagination, Page};
    use crate::repository::read_repository::ReadRepository;
    use crate::types::IdentityType;
    use crate::entity::ForeignKey;
    
    #[derive(Default, Debug, Clone, sqlx::FromRow, Entity)]
    #[connection="update_repository"]
    struct User {
        #[primary_key]
        pub id: i64,
        pub username: String,
        pub email: String,
    }

    #[rglw_codegen::before_all]
    pub async fn before_all() {
        env::set_var("GLWAPI_CONFIGURATION_LOCATION", "../database.toml");
        let result = User::execute(
            "CREATE TABLE IF NOT EXISTS User(\
                id INTEGER PRIMARY KEY AUTOINCREMENT,\
                username TEXT NOT NULL,
                email TEXT NOT NULL,
                CONSTRAINT Unique_email UNIQUE (email)
            )".into()).await;
        assert!(result.is_ok());
        let result = User::execute(
            "\
            INSERT INTO User(username,email) VALUES (
                'Jonathan',
                'email@rustyglw.org'
            );
            INSERT INTO User(username,email) VALUES (
                'User 1',
                'user1@rustyglw.org'
            );
            INSERT INTO User(username,email) VALUES (
                'User 2',
                'user2@rustyglw.org'
            );
            INSERT INTO User(username,email) VALUES (
                'User 3',
                'user3@rustyglw.org'
            );
            INSERT INTO User(username,email) VALUES (
                'User',
                'user4@rustyglw.org'
            );
            INSERT INTO User(username,email) VALUES (
                'User',
                'user5@rustyglw.org'
            );
        ".into()).await;
        assert!(result.is_ok());
    }

    #[rglw_codegen::after_all]
    pub async fn after_all() {
        fs::remove_file(Path::new("./update_repository.sqlite")).unwrap();
    }

    pub async fn test_update_by_id() {
        let existent_user = User::find_by_id(1).await;
        assert!(existent_user.is_ok());
        let mut user = existent_user.unwrap();
        user.username = "Updated Username".to_string();
        let updated_user = User::update_by_id(user).await;
        assert!(updated_user.is_ok());
        let updated_user = updated_user.unwrap();
        assert_eq!(updated_user.id, 1);
        assert_eq!(updated_user.username, "Updated Username");
    }

    pub async fn test_bulk_update_by_id() {
        let user1 = User::find_by_id(1).await;
        assert!(user1.is_ok());
        let mut user1 = user1.unwrap();
        user1.username = "Updated Username 1".to_string();
        let user2 = User::find_by_id(2).await;
        assert!(user2.is_ok());
        let mut user2 = user2.unwrap();
        user2.username = "Updated Username 2".to_string();
        let updated_users = User::bulk_update_by_id(vec![user1, user2]).await;
        assert!(updated_users.is_ok());
        let updated_users = updated_users.unwrap();
        assert_eq!(updated_users.first().unwrap().id, 1);
        assert_eq!(updated_users.first().unwrap().username, "Updated Username 1");
        assert_eq!(updated_users.last().unwrap().id, 2);
        assert_eq!(updated_users.last().unwrap().username, "Updated Username 2");
    }

    pub async fn test_bulk_update_by_example() {
        let updated_users = User::bulk_update_by_example(User {
            username: "User".to_string(),
            ..Default::default()
        }, User {
            username: "Username Modified".to_string(),
            ..Default::default()
        }).await;
        assert!(updated_users.is_ok());
        let updated_users = updated_users.unwrap();
        assert_eq!(updated_users.first().unwrap().id, 5);
        assert_eq!(updated_users.first().unwrap().username, "Username Modified");
        assert_eq!(updated_users.last().unwrap().id, 6);
        assert_eq!(updated_users.last().unwrap().username, "Username Modified");
    }
}