use actix_web::web::Data;
use actix_web::Scope;
use actix_web_httpauth::middleware::HttpAuthentication;
use serde::{Deserialize, Serialize};

use crate::{api, auth, AppState};

#[macro_export]
#[cfg(test)]
macro_rules! call_endpoint {
    ($req:ident, $state:ident) => {{
        // let subscriber = tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt::with(
        //     tracing_subscriber::registry(),
        //     tracing_subscriber::Layer::with_filter(
        //         tracing_subscriber::fmt::Layer::new()
        //             .pretty()
        //             .with_writer(std::io::stdout)
        //             .with_ansi(true),
        //         tracing_subscriber::filter::LevelFilter::DEBUG,
        //     ),
        // );
        // tracing::subscriber::set_global_default(subscriber)
        //     .expect("Unable to set a global collector");

        let jwt_secret = crate::auth::get_secret(&$state.db).await?;
        let token = jsonwebtoken::encode(
            &jsonwebtoken::Header::default(),
            &crate::auth::AuthClaims {
                exp: 10_000_000_000,
            },
            &jsonwebtoken::EncodingKey::from_secret(&jwt_secret),
        )
        .map_err(|e| {
            crate::error::Error::new(
                crate::error::ErrorCode::Internal,
                &format!("Token error: {} ", e),
            )
        })?;

        $req.headers_mut().append(
            actix_web::http::header::AUTHORIZATION,
            actix_web::http::header::HeaderValue::from_str(&format!("Bearer {}", token)).unwrap(),
        );

        let a = App::new()
            .wrap(tracing_actix_web::TracingLogger::default())
            .app_data($state.clone())
            .service(routes());
        let app = actix_web::test::init_service(a).await;
        let resp = actix_web::test::call_service(&app, $req).await;
        resp
    }};
}

#[cfg(test)]
macro_rules! get_response {
    ($resp: ident, $type:ty) => {{
        let body = test::read_body($resp).await.to_vec();
        serde_json::from_slice::<$type>(&body).unwrap()
    }};
}

pub mod application_categories;
pub mod applications;
pub mod authorization;
pub mod bookmark_categories;
pub mod bookmarks;
pub mod settings;

mod api_prelude {
    pub use super::ListObjects;
    pub use crate::entity::prelude::*;
    pub use crate::entity::*;
    pub use crate::AppState;
    pub use actix_web::{delete, get, post, put, web, Error, HttpResponse, Scope};
    pub use sea_orm::prelude::*;
    pub use sea_orm::{NotSet, Set};
    pub use serde::{Deserialize, Serialize};
}

#[cfg(test)]
pub mod test_prelude {
    use std::collections::HashMap;

    pub use super::ListObjects;
    use crate::auth;
    pub use crate::entity::*;
    pub use crate::AppState;

    pub use super::routes;
    pub use crate::error::Result;
    pub use actix_web::dev::ServiceResponse;
    pub use actix_web::{test, web, App};
    pub use sea_orm::{
        entity::prelude::*, entity::*, tests_cfg::*, DatabaseBackend, MockDatabase, MockExecResult,
        Transaction,
    };
    use tokio::sync::Mutex;

    /// Sets up a testing state with an in-memory database and creates the scheme.
    pub async fn setup_state() -> Result<actix_web::web::Data<AppState>> {
        let pool = sqlx::SqlitePool::connect("sqlite::memory:").await?;
        sqlx::migrate!("./migrations").run(&pool).await?;

        let db = sea_orm::SqlxSqliteConnector::from_sqlx_sqlite_pool(pool);

        auth::generate_secret(&db).await?;

        Ok(actix_web::web::Data::new(AppState {
            db,
            healthcheck_status: Mutex::new(HashMap::new()),
            data_path: ".".to_string(),
        }))
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListObjects<T>
where
    T: Serialize,
{
    items: Vec<T>,
    total: usize,
}

impl<T: Serialize> ListObjects<T> {
    pub fn new(items: Vec<T>, total: usize) -> Self {
        Self { items, total }
    }
}

use actix_multipart::Multipart;
use actix_web::{post, web, HttpResponse};
use futures::StreamExt;
use std::io::Write;
use std::path::Path;

#[post("/upload/bg")]
async fn bg_upload(
    mut payload: Multipart,
    state: Data<AppState>,
) -> crate::error::Result<HttpResponse> {
    if let Some(Ok(mut field)) = payload.next().await {
        let file_path = Path::new(&state.data_path).join("bg.jpg");
        let mut f = web::block(|| std::fs::File::create(file_path)).await??;
        while let Some(chunk) = field.next().await {
            let data = chunk.unwrap();
            f = web::block(move || f.write_all(&data).map(|_| f)).await??;
        }
    }

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

pub fn routes() -> Scope {
    let auth_handler = HttpAuthentication::bearer(auth::validator);
    let protected_routes = Scope::new("")
        .wrap(auth_handler)
        .service(api::applications::routes())
        .service(api::application_categories::routes())
        .service(api::bookmarks::routes())
        .service(api::bookmark_categories::routes())
        .service(api::settings::routes())
        .service(api::authorization::update_password)
        .service(bg_upload);

    Scope::new("api")
        .service(api::authorization::authorize)
        .service(api::authorization::initial_setup)
        .service(protected_routes)
}
