use crate::error::OrmError;
use crate::pagination::{calculate_pagination, Page, Pagination};
use crate::query::WhereType;
use crate::traits::repository::Repository;
use crate::WhereStatement;
use sqlx::{Encode, Type};

#[async_trait::async_trait]
pub trait ReadRepository where Self: Repository {
    async fn example(example: Self, pagination: Option<Pagination>) -> Result<Page<Self>, OrmError>;
    async fn find_by_id<ID>(id: ID) -> Result<Self, OrmError> where ID: for <'r> Encode<'r, sqlx::Any> + Type<sqlx::Any> + Send + Sync;
    async fn find_all(pagination: Option<Pagination>) -> Result<Page<Self>, OrmError>;
}

#[async_trait::async_trait]
impl <T> ReadRepository for T where T: Repository {
    async fn example(example: Self, pagination: Option<Pagination>) -> Result<Page<Self>, OrmError> {
        let result = example.query_example(pagination, &Self::get_pool().await?).await;
        if result.is_err() {
            let error = result.err().unwrap();
            return Err(OrmError {
                message: format!("Error trying to find by example: {}", error),
            });
        }
        Ok(result?)
    }

    async fn find_by_id<ID>(id: ID) -> Result<Self, OrmError> where ID: for <'r> Encode<'r, sqlx::Any> + Type<sqlx::Any> + Send + Sync {
        let mut query = Self::get_plain_query();
        query._where(WhereStatement {
            statement: format!("{} = $1", Self::get_primary_key_column()),
            where_type: WhereType::And,
        });
        let result = sqlx::query_as::<_, Self>(query.build().as_str())
            .bind(id)
            .fetch_one(&Self::get_pool().await?).await;
        if result.is_err() {
            let error = result.err().unwrap();
            return Err(OrmError {
                message: format!("Error trying to find by id: {}", error),
            });
        }
        Ok(result?)
    }

    async fn find_all(pagination: Option<Pagination>) -> Result<Page<Self>, OrmError> {
        let query = Self::get_plain_query();
        let mut total_rows = 0;
        let mut total_pages = 1;
        let mut page_number = 1;
        if pagination.is_some() {
            let pagination = pagination.clone().unwrap();
            let total_pagination = calculate_pagination(query.clone(), pagination.clone(), &Self::get_pool().await?).await?;
            total_rows = total_pagination.rows;
            total_pages = ((total_pagination.rows / pagination.page_size) as f64).ceil() as i64;
            page_number = pagination.page_number;
        }
        let data = sqlx::query_as::<_, Self>(query.build().as_str()).fetch_all(&Self::get_pool().await?).await?;
        if pagination.is_none() {
            total_rows = data.len() as i64;
        }
        Ok(Page {
            data,
            page_number,
            total_rows,
            total_pages,
        })
    }
}

#[rglw::codegen::test(name="Test Reading Repository")]
mod tests {

    use crate::repository::read_repository::{ReadRepository};
    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::pagination::Page;
    use crate::pagination::calculate_pagination;
    use crate::types::IdentityType;
    use crate::entity::ForeignKey;
    
    #[derive(Default, Debug, Clone, sqlx::FromRow, Entity)]
    #[connection="read_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
            )".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 4',
                'user4@rustyglw.org'
            );
            INSERT INTO User(username,email) VALUES (
                'User 5',
                'user5@rustyglw.org'
            );
        ".into()).await;
        assert!(result.is_ok());
    }

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

    pub async fn test_find_by_id() {
        let result = User::find_by_id(5).await;
        assert!(result.is_ok());
        let result = result.unwrap();
        assert_eq!(result.email, "user4@rustyglw.org");
    }

    pub async fn test_find_all() {
        let result = User::find_all(None).await;
        assert!(result.is_ok());
        let result = result.unwrap();
        assert_eq!(result.total_rows, 6);
    }

    pub async fn test_example() {
        let result = User::example(User {
            username: "Jonathan".to_string(),
            ..Default::default()
        }, None).await;
        assert!(result.is_ok());
        let result = result.unwrap();
        assert_eq!(result.total_rows, 1);
        assert_eq!(result.data.first().unwrap().email, "email@rustyglw.org");
    }
}