use tracing::instrument;

use crate::api::api_prelude::*;
use crate::error::{Error, Result};

#[instrument]
#[get("")]
pub async fn list_application_categories(state: web::Data<AppState>) -> Result<HttpResponse> {
    let cats: Vec<application_category::Model> =
        ApplicationCategory::find().all(&state.db).await.unwrap();
    let count = cats.len();
    Ok(HttpResponse::Ok().json(ListObjects::new(cats, count)))
}

#[instrument]
#[post("")]
pub async fn new_application_category(
    state: web::Data<AppState>,
    data: web::Json<application_category::Model>,
) -> Result<HttpResponse> {
    let model = application_category::ActiveModel {
        id: NotSet,
        glyph: Set(data.0.glyph),
        category_name: Set(data.0.category_name),
        active: Set(data.0.active),
    };
    let rec = model.insert(&state.db).await?;
    Ok(HttpResponse::Ok().json(rec))
}

#[instrument]
#[get("{id}")]
pub async fn get_application_category(
    state: web::Data<AppState>,
    id: web::Path<i32>,
) -> Result<HttpResponse> {
    let id = id.into_inner();
    let res: Option<application_category::Model> =
        ApplicationCategory::find_by_id(id).one(&state.db).await?;
    match res {
        Some(rec) => Ok(HttpResponse::Ok().json(rec)),
        None => Err(Error::not_found()),
    }
}

#[instrument]
#[put("{id}")]
pub async fn update_application_category(
    state: web::Data<AppState>,

    data: web::Json<application_category::Model>,
    id: web::Path<i32>,
) -> Result<HttpResponse> {
    let id = id.into_inner();
    let res: Option<application_category::Model> =
        ApplicationCategory::find_by_id(id).one(&state.db).await?;
    match res {
        Some(_rec) => {
            let data = data.into_inner();
            let ret = application_category::ActiveModel {
                id: Set(id),
                glyph: Set(data.glyph),
                active: Set(data.active),
                category_name: Set(data.category_name),
            };
            let model = ret.update(&state.db).await?;
            Ok(HttpResponse::Ok().json(model))
        }
        None => Err(Error::not_found()),
    }
}

#[instrument]
#[delete("{id}")]
pub async fn delete_application_category(
    state: web::Data<AppState>,
    id: web::Path<i32>,
) -> Result<HttpResponse> {
    let cat_id = id.into_inner();
    let recs: Vec<application::Model> = Application::find()
        .filter(application::Column::ApplicationCategoryId.eq(cat_id))
        .all(&state.db)
        .await
        .unwrap_or_default();

    for rec in recs {
        application::ActiveModel {
            id: Set(rec.id),
            application_category_id: Set(None),
            ..Default::default()
        }
        .save(&state.db)
        .await?;
    }

    ApplicationCategory::delete_many()
        .filter(application_category::Column::Id.eq(cat_id))
        .exec(&state.db)
        .await?;

    Ok(HttpResponse::Ok().body(""))
}

#[instrument]
#[get("{id}/applications")]
pub async fn application_category_applications(
    state: web::Data<AppState>,
    id: web::Path<i32>,
) -> Result<HttpResponse> {
    let recs: Vec<application::Model> = Application::find()
        .filter(application::Column::ApplicationCategoryId.eq(id.into_inner()))
        .all(&state.db)
        .await
        .unwrap();

    let count = recs.len();
    Ok(HttpResponse::Ok().json(ListObjects::new(recs, count)))
}

/// Routes for the application endpoints. This binds up a scope with all endpoints for applications, to make it easier to add them to the server.
pub fn routes() -> Scope {
    web::scope("/application_categories")
        .service(application_category_applications)
        .service(list_application_categories)
        .service(update_application_category)
        .service(delete_application_category)
        .service(new_application_category)
        .service(get_application_category)
}

#[cfg(test)]
mod tests {

    use crate::api::test_prelude::*;
    use actix_web::http::Method;

    #[actix_rt::test]
    async fn test_list_application_categories() -> Result<()> {
        let state = setup_state().await?;
        application_category::ActiveModel {
            category_name: Set("Application 1".into()),
            active: Set(true),
            ..Default::default()
        }
        .insert(&state.db)
        .await?;
        application_category::ActiveModel {
            category_name: Set("Application 2".into()),
            active: Set(true),
            ..Default::default()
        }
        .insert(&state.db)
        .await?;

        let mut req = actix_web::test::TestRequest::with_uri("/api/application_categories")
            .method(Method::GET)
            .to_request();
        let resp = call_endpoint!(req, state);
        assert_eq!(resp.status(), 200);
        let data = get_response!(
            resp,
            ListObjects<crate::entity::application_category::Model>
        );
        assert_eq!(2_usize, data.items.len());
        Ok(())
    }

    #[actix_rt::test]
    async fn test_get_application_categories() -> Result<()> {
        let state = setup_state().await?;
        let model = application_category::ActiveModel {
            category_name: Set("Application 1".into()),
            active: Set(true),
            ..Default::default()
        }
        .insert(&state.db)
        .await?;

        let mut req = actix_web::test::TestRequest::with_uri(&format!(
            "/api/application_categories/{}",
            model.id
        ))
        .method(Method::GET)
        .to_request();
        let resp = call_endpoint!(req, state);
        let status = resp.status();
        let mut data = get_response!(resp, crate::entity::application_category::Model);
        data.id = model.id;
        assert_eq!(model, data);
        assert_eq!(status, 200);
        Ok(())
    }

    #[actix_rt::test]
    async fn test_new_application_category() -> Result<()> {
        let model = application_category::Model {
            id: 0,
            category_name: "Some name".into(),
            glyph: None,
            active: true,
        };

        let state = setup_state().await?;

        let mut req = actix_web::test::TestRequest::with_uri("/api/application_categories")
            .method(Method::POST)
            .set_json(model.clone())
            .to_request();

        let resp = call_endpoint!(req, state);
        assert_eq!(resp.status(), 200);
        let data = get_response!(resp, crate::entity::application_category::Model);
        assert_eq!(model, data);
        assert_eq!(
            application_category::Entity::find()
                .count(&state.db)
                .await?,
            1
        );
        Ok(())
    }

    #[actix_rt::test]
    async fn test_update_application_category() -> Result<()> {
        let state = setup_state().await?;
        application_category::ActiveModel {
            category_name: Set("Some name".into()),
            active: Set(true),
            ..Default::default()
        }
        .insert(&state.db)
        .await?;

        let mut model = application_category::ActiveModel {
            category_name: Set("Some name".into()),
            active: Set(true),
            ..Default::default()
        }
        .insert(&state.db)
        .await?;

        model.category_name = "Another name".into();

        let mut req = actix_web::test::TestRequest::with_uri(&format!(
            "/api/application_categories/{}",
            model.id
        ))
        .method(Method::PUT)
        .set_json(model.clone())
        .to_request();
        let resp = call_endpoint!(req, state);
        assert_eq!(resp.status(), 200);
        let mut data = get_response!(resp, crate::entity::application_category::Model);
        data.id = model.id;
        assert_eq!(model, data, "Check API");

        let db_model = application_category::Entity::find_by_id(model.id)
            .one(&state.db)
            .await?
            .unwrap();

        assert_eq!(db_model, model, "Check DB");

        Ok(())
    }

    #[actix_rt::test]
    async fn test_delete_application_category() -> Result<()> {
        let state = setup_state().await?;
        let model = application_category::ActiveModel {
            category_name: Set("Some name".into()),
            active: Set(true),
            ..Default::default()
        }
        .insert(&state.db)
        .await?;

        let app = application::ActiveModel {
            app_name: Set("Application 1".into()),
            url: Set("http://somewhere/".into()),
            active: Set(true),
            application_category_id: Set(Some(model.id)),
            ..Default::default()
        }
        .insert(&state.db)
        .await?;

        let mut req = actix_web::test::TestRequest::with_uri(&format!(
            "/api/application_categories/{}",
            model.id
        ))
        .method(Method::DELETE)
        .to_request();
        let resp = call_endpoint!(req, state);
        let status = resp.status();
        let body = test::read_body(resp).await;
        assert_eq!(body, "");
        assert_eq!(status, 200);
        assert_eq!(
            application_category::Entity::find()
                .count(&state.db)
                .await?,
            0
        );

        let app = application::Entity::find_by_id(app.id)
            .one(&state.db)
            .await?
            .unwrap();
        assert_eq!(app.application_category_id, None);

        Ok(())
    }

    #[actix_rt::test]
    async fn test_application_categories_applications() -> Result<()> {
        let state = setup_state().await?;
        let category = application_category::ActiveModel {
            category_name: Set("Application 1".into()),
            active: Set(true),
            ..Default::default()
        }
        .insert(&state.db)
        .await?;
        application::ActiveModel {
            app_name: Set("Application 1".into()),
            url: Set("http://somewhere/".into()),
            active: Set(true),
            application_category_id: Set(Some(category.id)),
            ..Default::default()
        }
        .insert(&state.db)
        .await?;
        application::ActiveModel {
            app_name: Set("Application 2".into()),
            url: Set("http://somewhere/".into()),
            application_category_id: Set(Some(category.id)),
            active: Set(true),
            ..Default::default()
        }
        .insert(&state.db)
        .await?;
        application::ActiveModel {
            app_name: Set("Application 2".into()),
            url: Set("http://somewhere/".into()),
            active: Set(true),
            ..Default::default()
        }
        .insert(&state.db)
        .await?;

        let mut req = actix_web::test::TestRequest::with_uri(&format!(
            "/api/application_categories/{}/applications",
            category.id
        ))
        .method(Method::GET)
        .to_request();
        let resp = call_endpoint!(req, state);
        assert_eq!(resp.status(), 200);
        let data = get_response!(resp, ListObjects<crate::entity::application::Model>);
        assert_eq!(2_usize, data.items.len());
        Ok(())
    }
}
