use axum::{
    extract::{Extension, Path},
    http::{HeaderValue, Method, StatusCode},
    response::IntoResponse,
    routing::{get, get_service, post},
    Json, Router,
};
use booksman_orm::{
    sea_orm::{Database, DatabaseConnection},
    Mutation as MutationCore,
    Query as QueryCore, //    BookAndMeta,
};
use booksman_search;
use booksman_search::BookMeili;
use clap::Parser;
use image;
use meilisearch_sdk::client::Client;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::io;
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
use std::str::FromStr;
use tower::ServiceBuilder;
use tower_http::cors::{Any, CorsLayer};
use tower_http::services::{ServeDir,ServeFile};
use tower_http::trace::TraceLayer;
//use itertools::Itertools;
use ::entity::entities::{
    book, book_author, book_isbn, book_person, book_place, book_subject, book_time, user,
};
use axum_login::{
    axum_sessions::{async_session::MemoryStore as SessionMemoryStore, SessionLayer},
    AuthLayer, RequireAuthorizationLayer,
};
use chrono::Local;
use migration::{Migrator, MigratorTrait};
use rand::Rng;
use std::env;

#[derive(Deserialize, Serialize, Debug, Clone)]
struct BookUI {
    id: i32,
    open_library_key: Option<String>,
    title: String,
    edition_count: Option<i32>,
    first_publish_year: Option<i32>,
    median_page_count: Option<i32>,
    goodread_id: Option<String>,
    description: Option<String>,
    cover: Option<String>,
    location: Option<String>,
    time_added: Option<String>,
    rating: Option<i32>,
    comments: Option<String>,
    author_name: Option<Vec<String>>,
    person: Option<Vec<String>>,
    place: Option<Vec<String>>,
    subject: Option<Vec<String>>,
    time: Option<Vec<String>>,
    isbn: Option<Vec<String>>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
struct Docs {
    key: String,
    title: String,
    edition_count: Option<i32>,
    first_publish_year: Option<i32>,
    number_of_pages_median: Option<i32>,
    id_goodreads: Option<Vec<String>>,
    description: Option<String>,
    isbn: Option<Vec<String>>,
    cover_i: Option<i32>,
    cover_url: Option<String>,
    author_name: Option<Vec<String>>,
    person: Option<Vec<String>>,
    place: Option<Vec<String>>,
    subject: Option<Vec<String>>,
    time: Option<Vec<String>>,
}

#[derive(Deserialize, Serialize, Debug)]
struct Books {
    num_found: u32,
    docs: Vec<Docs>,
}

#[derive(Deserialize, Serialize, Debug)]
struct PaginatedBookUIList {
    num_pages: u32,
    books: Vec<BookUI>,
}

#[derive(Deserialize, Serialize, Debug)]
struct BookPageOL {
    description: Option<DescriptionOL>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
struct DescriptionOLValue {
    value: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
enum DescriptionOL {
    DescriptionValueString(DescriptionOLValue),
    DescriptionString(String),
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BooksManyISBN {
    #[serde(flatten)]
    pub book_items: HashMap<String, BookISBNItem>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BookISBNItem {
    pub key: String,
    pub title: String,
    pub authors: Vec<Subject>,
    pub identifiers: Option<Identifiers>,
    pub publish_date: Option<String>,
    pub subjects: Option<Vec<Subject>>,
    pub subject_places: Option<Vec<Subject>>,
    pub subject_people: Option<Vec<Subject>>,
    pub subject_times: Option<Vec<Subject>>,
    pub cover: Option<Cover>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Identifiers {
    pub goodreads: Option<Vec<String>>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Subject {
    pub name: String,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Cover {
    pub medium: String,
}

impl Books {
    fn set_all_cover_urls(&mut self) {
        for book in self.docs.iter_mut() {
            match book.cover_i {
                Some(cover_i) => {
                    book.cover_url = Some(format!(
                        "https://covers.openlibrary.org/b/id/{}-M.jpg",
                        cover_i
                    ))
                }
                None => (),
            }
        }
    }
}

impl BookUI {
    async fn set_descriptions(&mut self) {
        if self.open_library_key.is_some() {
            if !(self.open_library_key.as_ref().unwrap().is_empty()) {
                let query = format!(
                    "https://openlibrary.org/{}.json",
                    self.open_library_key.as_ref().unwrap().clone()
                );
                let res = reqwest::get(query).await.expect("Unable to request");
                let resjson = res
                    .json::<BookPageOL>()
                    .await
                    .expect("Unable to return value");
                let description = resjson.description;

                if !description.is_none() {
                    if let DescriptionOL::DescriptionString(desc_string) =
                        description.clone().unwrap()
                    {
                        self.description = Some(desc_string);
                    }
                    if let DescriptionOL::DescriptionValueString(desc_val) =
                        description.clone().unwrap()
                    {
                        self.description = Some(desc_val.value);
                    }
                }
            }
        }
    }
}

async fn handle_error(_err: io::Error) -> impl IntoResponse {
    (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong...")
}

// Setup the command line interface with clap.
#[derive(Parser, Debug)]
#[clap(name = "server", about = "A server for our wasm project!")]
struct Opt {
    /// set the log level
    #[clap(short = 'l', long = "log", default_value = "debug")]
    log_level: String,

    /// set the listen addr
    #[clap(short = 'a', long = "addr", default_value = "0.0.0.0")]
    addr: String,

    /// set the listen port
    #[clap(short = 'p', long = "port", default_value = "8081")]
    port: u16,

    /// set the directory where static files are to be found
    #[clap(long = "static-dir", default_value = "../dist")]
    static_dir: String,
}

type AuthContext =
    axum_login::extractors::AuthContext<booksman_orm::AxumUser, booksman_orm::AxumUserStore>;

#[tokio::main]
pub async fn main() {
    let opt = Opt::parse();

    // Setup logging & RUST_LOG from args
    if std::env::var("RUST_LOG").is_err() {
        std::env::set_var("RUST_LOG", format!("{},hyper=info,mio=info", opt.log_level))
    }
    // enable console logging
    tracing_subscriber::fmt::init();

    dotenvy::dotenv().ok();
    let images_dir = env::var("IMAGES_DIR").expect("IMAGES_DIR is not set in .env file");
    let cors_url = env::var("CORS_URL").expect("CORS_URL is not set in .env file");
    let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file");
    let meili_url = env::var("MEILI_URL").expect("MEILI_URL is not set in .env file");
    let meili_key = env::var("MEILI_KEY").expect("MEILI_KEY is not set in .env file");

    let secret = rand::thread_rng().gen::<[u8; 64]>();

    let conn = Database::connect(db_url)
        .await
        .expect("Database connection failed");
    // Apply all pending migrations
    Migrator::up(&conn, None).await.unwrap();

    let session_store = SessionMemoryStore::new();
    let session_layer = SessionLayer::new(session_store, &secret).with_secure(false);

    let user_store = booksman_orm::AxumUserStore::new(&conn);
    let auth_layer: AuthLayer<booksman_orm::AxumUserStore, booksman_orm::AxumUser> =
        AuthLayer::new(user_store, &secret);

    let meili_client = Client::new(meili_url, meili_key);

    let app = Router::new()
        .route("/api/create_by_isbn", get(create_by_isbn))
        .route("/api/create", post(create_book))
        .route("/api/update", post(update_book))
        .route("/api/delete/:id", get(delete_book))
        .route("/api/authentication_check", get(authentication_check))
        .route("/api/refresh_search_index", get(refresh_meili_index))
        .route_layer(RequireAuthorizationLayer::<booksman_orm::AxumUser>::login())
        .route("/api/list", get(list_book))
        .route("/api/list_search", get(list_search_book))
        .route("/api/list_users", get(list_users))
        .route("/api/search_openlibrary", get(search_openlibrary))
        .route("/api/login", post(login_handler))
        .route("/api/register", post(register_handler))
        .route("/api/logout", post(logout_handler))
        .nest_service(
            "/images",
            get_service(ServeDir::new(images_dir)).handle_error(handle_error),
        )
        .nest_service(
            "/assets",
            get_service(ServeDir::new(opt.static_dir.clone())).handle_error(handle_error),
        )
        .fallback_service(
            get_service(ServeFile::new( format!("{}/index.html",opt.static_dir))).handle_error(
                |_| async move { (StatusCode::INTERNAL_SERVER_ERROR, "internal server error") },
            ),
        )
        //        .merge(SpaRouter::new("/assets", opt.static_dir))
        .layer(auth_layer)
        .layer(session_layer)
        .layer(ServiceBuilder::new().layer(TraceLayer::new_for_http()))
        .layer(Extension(conn))
        .layer(Extension(meili_client))
        .layer(
            // see https://docs.rs/tower-http/latest/tower_http/cors/index.html
            // for more details
            //
            // pay attention that for some request types like posting content-type: application/json
            // it is required to add ".allow_headers([http::header::CONTENT_TYPE])"
            // or see this issue https://github.com/tokio-rs/axum/issues/849
            CorsLayer::new()
                .allow_methods([Method::GET, Method::POST])
                .allow_origin(cors_url.parse::<HeaderValue>().unwrap())
                .allow_headers(Any),
        );

    let sock_addr = SocketAddr::from((
        IpAddr::from_str(opt.addr.as_str()).unwrap_or(IpAddr::V6(Ipv6Addr::LOCALHOST)),
        opt.port,
    ));

    log::info!("listening on http://{}", sock_addr);

    axum::Server::bind(&sock_addr)
        .serve(app.into_make_service())
        .await
        .expect("Unable to start server");
}

//#[axum_macros::debug_handler]
async fn register_handler(
    Extension(ref conn): Extension<DatabaseConnection>,
    Json(user_sent): Json<booksman_orm::AxumUser>,
) -> impl IntoResponse {
    // add to db
    let user: user::Model = user::Model {
        id: user_sent.id,
        user_name: Some(user_sent.clone().name),
        password_hash: user_sent.clone().password_hash,
    };
    let _created_user = MutationCore::create_user(conn, user)
        .await
        .expect("Failed to create user");
    return "success";
}

async fn list_users(Extension(ref conn): Extension<DatabaseConnection>) -> impl IntoResponse {
    let usersmodels: Vec<user::Model> =
        QueryCore::list_all_users(&conn).await.unwrap_or(Vec::new());
    let mut users: Vec<booksman_orm::AxumUser> = Vec::new();
    for usermodel in usersmodels.iter() {
        let user = booksman_orm::AxumUser {
            id: usermodel.id,
            name: usermodel.user_name.clone().unwrap().clone(),
            password_hash: "".to_string(),
        };
        users.push(user);
    }
    return Json(users);
}
async fn login_handler(
    mut auth: AuthContext,
    Extension(ref conn): Extension<DatabaseConnection>,
    Json(user_sent): Json<booksman_orm::AxumUser>,
) -> impl IntoResponse {
    let userdb: user::Model = QueryCore::find_userid_by_name(conn, user_sent.name.clone())
        .await
        .unwrap()
        .unwrap();
    let userid = userdb.id;
    let corrected_user = booksman_orm::AxumUser {
        id: userid,
        name: user_sent.name.clone(),
        password_hash: user_sent.password_hash.clone(),
    };
    auth.login(&corrected_user).await.unwrap();
    return "success";
}

//

async fn logout_handler(mut auth: AuthContext) {
    dbg!("Logging out user: {}", &auth.current_user);
    auth.logout().await;
}

//https://openlibrary.org/api/books?bibkeys=ISBN:9780980200447&jscmd=data&format=json
async fn authentication_check(
    Extension(_user): Extension<booksman_orm::AxumUser>,
) -> impl IntoResponse {
    return Json(true);
}

//https://openlibrary.org/api/books?bibkeys=ISBN:9780980200447&jscmd=data&format=json
async fn create_by_isbn(
    Extension(ref conn): Extension<DatabaseConnection>,
    Extension(ref meili_client): Extension<Client>,
    Extension(user): Extension<booksman_orm::AxumUser>,
    axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>,
) -> impl IntoResponse {
    dotenvy::dotenv().ok();
    let images_dir = env::var("IMAGES_DIR").expect("IMAGES_DIR is not set in .env file");
    let userid = user.id;

    print!("Get items with query params: {:?}\n", params);
    let isbns = params.get("isbns").unwrap();
    let splitisbns: Vec<String> = isbns
        .split(",")
        .map(|s| format!("ISBN:{}", s.to_string()))
        .collect();
    let joinedisbns = splitisbns.join(",");
    let query = format!(
        "https://openlibrary.org/api/books?bibkeys={}&jscmd=data&format=json",
        joinedisbns
    );
    let res = reqwest::get(query).await.expect("Unable to request");
    let resjson: BooksManyISBN = res
        .json::<BooksManyISBN>()
        .await
        .expect("Unable to return value");
    for isbnstring in splitisbns.iter() {
        let bookfound = resjson.book_items.get(isbnstring);
        if !bookfound.clone().is_none() {
            let mut cover_id = "".to_string();
            if !bookfound.unwrap().cover.is_none() {
                let coverurl = bookfound
                    .clone()
                    .unwrap()
                    .cover
                    .clone()
                    .unwrap()
                    .medium
                    .clone();
                let img_bytes = reqwest::get(coverurl.clone())
                    .await
                    .unwrap()
                    .bytes()
                    .await
                    .unwrap();
                let image = image::load_from_memory(&img_bytes).unwrap();
                let temp_cover = coverurl.clone();
                let img_id = temp_cover.split("/").last().unwrap();
                image
                    .save(format!("{}/{}", images_dir, img_id))
                    .expect("Failed to save image");
                cover_id = img_id.to_string();
            }
            let mut goodread_id = "".to_string();
            if !bookfound.unwrap().identifiers.is_none() {
                let goodread_identifier = bookfound
                    .clone()
                    .unwrap()
                    .identifiers
                    .clone()
                    .unwrap()
                    .goodreads;
                if !goodread_identifier.is_none() {
                    goodread_id = goodread_identifier.unwrap().get(0).unwrap().to_string()
                }
            }
            //let mut authors_name = Some(vec!["".to_string()]);
            //if !bookfound.unwrap().authors.is_none() {
            let authors_name = Some(
                bookfound
                    .clone()
                    .unwrap()
                    .authors
                    .iter()
                    .map(|s| s.name.clone())
                    .collect(),
            );
            //} else {
            //    authors_name = None
            //}
            let persons: Option<Vec<String>>;
            //= Some(vec!["".to_string()]);
            if !bookfound.unwrap().subject_people.is_none() {
                persons = Some(
                    bookfound
                        .clone()
                        .unwrap()
                        .subject_people
                        .clone()
                        .unwrap()
                        .iter()
                        .map(|s| s.name.clone())
                        .collect(),
                );
            } else {
                persons = None;
            }
            let places: Option<Vec<String>>;
            if !bookfound.unwrap().subject_places.is_none() {
                places = Some(
                    bookfound
                        .clone()
                        .unwrap()
                        .subject_places
                        .clone()
                        .unwrap()
                        .iter()
                        .map(|s| s.name.clone())
                        .collect(),
                );
            } else {
                places = None;
            }
            let subjects: Option<Vec<String>>;
            if !bookfound.unwrap().subjects.is_none() {
                subjects = Some(
                    bookfound
                        .clone()
                        .unwrap()
                        .subjects
                        .clone()
                        .unwrap()
                        .iter()
                        .map(|s| s.name.clone())
                        .collect(),
                );
            } else {
                subjects = None;
            }
            let times: Option<Vec<String>>;
            if !bookfound.unwrap().subject_times.is_none() {
                times = Some(
                    bookfound
                        .clone()
                        .unwrap()
                        .subject_times
                        .clone()
                        .unwrap()
                        .iter()
                        .map(|s| s.name.clone())
                        .collect(),
                );
            } else {
                times = None;
            }

            let mut doc_sent = BookUI {
                id: 1,
                open_library_key: Some(bookfound.clone().unwrap().key.clone()),
                title: bookfound.clone().unwrap().title.clone(),
                edition_count: None,
                first_publish_year: None,
                median_page_count: None,
                goodread_id: Some(goodread_id),
                description: None,
                cover: Some(cover_id.clone()),
                location: None,
                time_added: None,
                rating: None,
                comments: Some("From openlibrary".to_string()),
                author_name: authors_name,
                person: persons,
                place: places,
                subject: subjects,
                time: times,
                isbn: Some(vec![isbnstring.split("ISBN:").last().unwrap().to_string()]),
            };
            doc_sent.set_descriptions().await;
            let book: book::Model = book::Model {
                open_library_key: doc_sent.open_library_key.to_owned(),
                title: (doc_sent.title.to_owned()),
                edition_count: doc_sent.edition_count.to_owned(),
                first_publish_year: (doc_sent.first_publish_year.to_owned()),
                median_page_count: (doc_sent.median_page_count.to_owned()),
                goodread_id: doc_sent.goodread_id.to_owned(),
                description: doc_sent.description.to_owned(),
                comments: doc_sent.comments.to_owned(),
                cover: Some(cover_id.to_owned()),
                rating: doc_sent.rating.to_owned(),
                time_added: doc_sent.time_added.to_owned(),
                id: 1,
                location: doc_sent.location.to_owned(),
                user_id: userid,
            };
            let created_book = MutationCore::create_book(conn, book)
                .await
                .expect("could not create book");

            for author_name in doc_sent.author_name.as_ref().unwrap().iter() {
                let record: book_author::Model = book_author::Model {
                    id: 1,
                    book_id: (created_book.last_insert_id),
                    author_name: author_name.to_owned(),
                };
                MutationCore::create_book_author(conn, record)
                    .await
                    .expect("could not create book");
            }
            for person in doc_sent.person.as_ref().unwrap().iter() {
                let record: book_person::Model = book_person::Model {
                    id: 1,
                    book_id: (created_book.last_insert_id),
                    person: person.to_owned(),
                };
                MutationCore::create_book_person(conn, record)
                    .await
                    .expect("could not create book");
            }
            for place in doc_sent.place.as_ref().unwrap().iter() {
                let record: book_place::Model = book_place::Model {
                    id: 1,
                    book_id: (created_book.last_insert_id),
                    place: place.to_owned(),
                };
                MutationCore::create_book_place(conn, record)
                    .await
                    .expect("could not create book");
            }
            for subject in doc_sent.subject.as_ref().unwrap().iter() {
                let record: book_subject::Model = book_subject::Model {
                    id: 1,
                    book_id: (created_book.last_insert_id),
                    subject: subject.to_owned(),
                };
                MutationCore::create_book_subject(conn, record)
                    .await
                    .expect("could not create book");
            }
            for time in doc_sent.time.as_ref().unwrap().iter() {
                let record: book_time::Model = book_time::Model {
                    id: 1,
                    book_id: (created_book.last_insert_id),
                    time: time.to_owned(),
                };
                MutationCore::create_book_time(conn, record)
                    .await
                    .expect("could not create book");
            }
            for isbn in doc_sent.isbn.as_ref().unwrap().iter() {
                let record: book_isbn::Model = book_isbn::Model {
                    id: 1,
                    book_id: (created_book.last_insert_id),
                    isbn: isbn.to_owned(),
                };
                MutationCore::create_book_isbn(conn, record)
                    .await
                    .expect("could not create book");
            }
            let book_meili = BookMeili {
                id: doc_sent.id,
                open_library_key: doc_sent.open_library_key.unwrap_or("".to_string()),
                title: doc_sent.title,
                edition_count: doc_sent.edition_count.unwrap_or(0),
                first_publish_year: doc_sent.first_publish_year.unwrap_or(0),
                median_page_count: doc_sent.median_page_count.unwrap_or(0),
                goodread_id: doc_sent.goodread_id.unwrap_or("".to_string()),
                description: doc_sent.description.unwrap_or("".to_string()),
                cover: doc_sent.cover.unwrap_or("".to_string()),
                location: doc_sent.location.unwrap_or("".to_string()),
                time_added: doc_sent.time_added.unwrap_or("".to_string()),
                rating: doc_sent.rating.unwrap_or(0),
                comments: doc_sent.comments.unwrap_or("".to_string()),
                author_name: doc_sent.author_name.unwrap_or(vec!["".to_string()]),
                person: doc_sent.person.unwrap_or(vec!["".to_string()]),
                place: doc_sent.place.unwrap_or(vec!["".to_string()]),
                subject: doc_sent.subject.unwrap_or(vec!["".to_string()]),
                time: doc_sent.time.unwrap_or(vec!["".to_string()]),
                isbn: doc_sent.isbn.unwrap_or(vec!["".to_string()]),
            };
            booksman_search::create_or_update_book(book_meili, userid, meili_client).await;
        } else {
            println!("ISBN Not found : {}", isbnstring);
        }
    }
    return "Done";
}

async fn search_openlibrary(
    axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>,
) -> impl IntoResponse {
    print!("Get items with query params: {:?}\n", params);
    let search = params.get("search").unwrap();
    let query = format!("https://openlibrary.org/search.json?q={}", search);
    let res = reqwest::get(query).await.expect("Unable to request");
    let mut resjson = res.json::<Books>().await.expect("Unable to return value");
    resjson.docs.truncate(12);
    resjson.set_all_cover_urls();
    //resjson.set_all_descriptions().await;
    print!("Search token {:?}\n", search);
    let mut vec = Vec::with_capacity(12);
    for i in 0..resjson.docs.len() as usize {
        let doc = resjson.docs[i].clone();
        let mut goodread = "".to_string();
        if doc.id_goodreads.is_some() {
            goodread = doc.id_goodreads.unwrap().get(0).unwrap().to_string();
        }
        vec.push(BookUI {
            id: -((i + 1) as i32),
            open_library_key: Some(doc.key),
            title: doc.title,
            edition_count: doc.edition_count,
            first_publish_year: doc.first_publish_year,
            median_page_count: doc.number_of_pages_median,
            goodread_id: Some(goodread),
            description: doc.description,
            cover: doc.cover_url,
            location: None,
            time_added: None,
            rating: None,
            comments: Some("From openlibrary".to_string()),
            author_name: doc.author_name,
            person: doc.person,
            place: doc.place,
            subject: doc.subject,
            time: doc.time,
            isbn: doc.isbn,
        });
    }
    return Json(vec);
}

async fn delete_book(
    Extension(ref conn): Extension<DatabaseConnection>,
    Extension(ref meili_client): Extension<Client>,
    Extension(user): Extension<booksman_orm::AxumUser>,
    Path(id): Path<i32>,
) -> impl IntoResponse {
    MutationCore::delete_book(conn, id)
        .await
        .expect("could not delete book");
    let userid = user.id;
    booksman_search::delete_book(id, userid, meili_client).await;
    "success"
}


async fn refresh_meili_index(
    Extension(ref conn): Extension<DatabaseConnection>,
    Extension(ref meili_client): Extension<Client>,
    Extension(user): Extension<booksman_orm::AxumUser>,
) -> impl IntoResponse {
    dotenvy::dotenv().ok();
    let userid = user.id;

    let page: usize = 1;
    let sort: String = "desc".to_string();
    let per_page: usize = 12;

    let books = QueryCore::find_books_plus_meta_in_page(conn, page, per_page, userid, sort.clone())
        .await
        .expect("could not list books");

    let num_pages: usize = (books.1 as usize) + 1;
    for pg_idx in 1..num_pages {
        //println!("DEBUGG {} {} {} {}",pg_idx, per_page, userid, sort.clone());
        let books = QueryCore::find_books_plus_meta_in_page(conn, pg_idx, per_page, userid, sort.clone())
        .await
        .expect("could not list books");

        for bookandmeta in books.0.into_iter() {
            let bookui = BookUI {
                id: bookandmeta.clone().book.id,
                title: bookandmeta.clone().book.title,
                open_library_key: bookandmeta.clone().book.open_library_key,
                edition_count: bookandmeta.clone().book.edition_count,
                first_publish_year: bookandmeta.clone().book.first_publish_year,
                median_page_count: bookandmeta.clone().book.median_page_count,
                goodread_id: bookandmeta.clone().book.goodread_id,
                description: bookandmeta.clone().book.description,
                cover: bookandmeta.clone().book.cover,
                location: bookandmeta.clone().book.location,
                time_added: bookandmeta.clone().book.time_added,
                rating: bookandmeta.clone().book.rating,
                comments: bookandmeta.clone().book.comments,
                author_name: Some(
                    bookandmeta
                        .clone()
                        .authors
                        .into_iter()
                        .map(|u| u.author_name)
                        .collect(),
                ),
                person: Some(
                    bookandmeta
                        .clone()
                        .persons
                        .into_iter()
                        .map(|u| u.person)
                        .collect(),
                ),
                place: Some(
                    bookandmeta
                        .clone()
                        .places
                        .into_iter()
                        .map(|u| u.place)
                        .collect(),
                ),
                subject: Some(
                    bookandmeta
                        .clone()
                        .subjects
                        .into_iter()
                        .map(|u| u.subject)
                        .collect(),
                ),
                time: Some(
                    bookandmeta
                        .clone()
                        .times
                        .into_iter()
                        .map(|u| u.time)
                        .collect(),
                ),
                isbn: Some(
                    bookandmeta
                        .clone()
                        .isbns
                        .into_iter()
                        .map(|u| u.isbn)
                        .collect(),
                ),
            };

            let book_meili = BookMeili {
                id: bookui.id,
                open_library_key: bookui.open_library_key.unwrap_or("".to_string()),
                title: bookui.title,
                edition_count: bookui.edition_count.unwrap_or(0),
                first_publish_year: bookui.first_publish_year.unwrap_or(0),
                median_page_count: bookui.median_page_count.unwrap_or(0),
                goodread_id: bookui.goodread_id.unwrap_or("".to_string()),
                description: bookui.description.unwrap_or("".to_string()),
                cover: bookui.cover.unwrap_or("".to_string()),
                location: bookui.location.unwrap_or("".to_string()),
                time_added: bookui.time_added.unwrap_or("".to_string()),
                rating: bookui.rating.unwrap_or(0),
                comments: bookui.comments.unwrap_or("".to_string()),
                author_name: bookui.author_name.unwrap_or(vec!["".to_string()]),
                person: bookui.person.unwrap_or(vec!["".to_string()]),
                place: bookui.place.unwrap_or(vec!["".to_string()]),
                subject: bookui.subject.unwrap_or(vec!["".to_string()]),
                time: bookui.time.unwrap_or(vec!["".to_string()]),
                isbn: bookui.isbn.unwrap_or(vec!["".to_string()]),
            };
            booksman_search::create_or_update_book(book_meili, userid, meili_client).await;
        }
    }

    "success"
}

async fn list_book(
    Extension(ref conn): Extension<DatabaseConnection>,
    axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>,
) -> impl IntoResponse {
    dotenvy::dotenv().ok();
    let backend_url = env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");

    let page: usize = params.get("page").unwrap().parse().unwrap();
    let userid: i32 = params.get("userid").unwrap().parse().unwrap();
    let sort: String = params.get("sort").unwrap().to_string();
    let per_page: usize = 12;

    let books = QueryCore::find_books_plus_meta_in_page(conn, page, per_page, userid, sort)
        .await
        .expect("could not list books");

    let mut resbooks: Vec<BookUI> = Vec::with_capacity(per_page);

    for bookandmeta in books.0.into_iter() {
        let mut cover = bookandmeta.clone().book.cover;
        if !cover.is_none() {
            if cover.clone().unwrap() != "".to_string() {
                cover = Some(format!("{}/images/{}", backend_url, cover.unwrap()));
            } else {
                cover = Some(format!("{}/images/placeholder.jpg", backend_url));
            }
        } else {
            cover = Some(format!("{}/images/placeholder.jpg", backend_url));
        }
        let bookui = BookUI {
            id: bookandmeta.clone().book.id,
            title: bookandmeta.clone().book.title,
            open_library_key: bookandmeta.clone().book.open_library_key,
            edition_count: bookandmeta.clone().book.edition_count,
            first_publish_year: bookandmeta.clone().book.first_publish_year,
            median_page_count: bookandmeta.clone().book.median_page_count,
            goodread_id: bookandmeta.clone().book.goodread_id,
            description: bookandmeta.clone().book.description,
            cover: cover.clone(),
            location: bookandmeta.clone().book.location,
            time_added: bookandmeta.clone().book.time_added,
            rating: bookandmeta.clone().book.rating,
            comments: bookandmeta.clone().book.comments,
            author_name: Some(
                bookandmeta
                    .clone()
                    .authors
                    .into_iter()
                    .map(|u| u.author_name)
                    .collect(),
            ),
            person: Some(
                bookandmeta
                    .clone()
                    .persons
                    .into_iter()
                    .map(|u| u.person)
                    .collect(),
            ),
            place: Some(
                bookandmeta
                    .clone()
                    .places
                    .into_iter()
                    .map(|u| u.place)
                    .collect(),
            ),
            subject: Some(
                bookandmeta
                    .clone()
                    .subjects
                    .into_iter()
                    .map(|u| u.subject)
                    .collect(),
            ),
            time: Some(
                bookandmeta
                    .clone()
                    .times
                    .into_iter()
                    .map(|u| u.time)
                    .collect(),
            ),
            isbn: Some(
                bookandmeta
                    .clone()
                    .isbns
                    .into_iter()
                    .map(|u| u.isbn)
                    .collect(),
            ),
        };
        resbooks.push(bookui);
    }

    let res = PaginatedBookUIList {
        num_pages: books.1 as u32,
        books: resbooks,
    };
    return Json(res);
    //  "success"
}

async fn list_search_book(
    Extension(ref meili_client): Extension<Client>,
    axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>,
) -> impl IntoResponse {
    let page: usize = params.get("page").unwrap().parse().unwrap();
    let search = params.get("search").unwrap();
    let userid: i32 = params.get("userid").unwrap().parse().unwrap();

    dotenvy::dotenv().ok();
    let backend_url = env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");

    let books = booksman_search::search_book(search, page, userid, meili_client)
        .await
        .expect("could not list books");

    let mut resbooks: Vec<BookUI> = Vec::with_capacity(12);

    for bookmeili in books.0.into_iter() {
        let mut cover = bookmeili.clone().cover;
        if cover != "".to_string() {
            cover = format!("{}/images/{}", backend_url, cover);
        } else {
            cover = format!("{}/images/placeholder.jpg", backend_url);
        }
        let bookui = BookUI {
            id: bookmeili.id,
            title: bookmeili.title,
            open_library_key: Some(bookmeili.open_library_key),
            edition_count: Some(bookmeili.edition_count),
            first_publish_year: Some(bookmeili.first_publish_year),
            median_page_count: Some(bookmeili.median_page_count),
            goodread_id: Some(bookmeili.goodread_id),
            description: Some(bookmeili.description),
            cover: Some(cover.clone()),
            location: Some(bookmeili.location),
            time_added: Some(bookmeili.time_added),
            rating: Some(bookmeili.rating),
            comments: Some(bookmeili.comments),
            author_name: Some(bookmeili.author_name),
            person: Some(bookmeili.person),
            place: Some(bookmeili.place),
            subject: Some(bookmeili.subject),
            time: Some(bookmeili.time),
            isbn: Some(bookmeili.isbn),
        };
        resbooks.push(bookui);
    }

    let res = PaginatedBookUIList {
        num_pages: (books.1 / 12 + 1) as u32,
        books: resbooks,
    };
    return Json(res);
    //  "success"
}

async fn create_book(
    Extension(ref conn): Extension<DatabaseConnection>,
    Extension(ref meili_client): Extension<Client>,
    Extension(user): Extension<booksman_orm::AxumUser>,
    Json(doc_sent_orig): Json<BookUI>,
) -> impl IntoResponse {
    println!("Creating book");

    dotenvy::dotenv().ok();
    let images_dir = env::var("IMAGES_DIR").expect("IMAGES_DIR is not set in .env file");
    let userid = user.id;
    let mut doc_sent = doc_sent_orig.clone();
    let mut cover = doc_sent.cover.clone();
    if doc_sent_orig.description.is_none() {
        doc_sent.set_descriptions().await;
    } else {
        if doc_sent_orig.description.unwrap() == "".to_string() {
            doc_sent.set_descriptions().await;
        }
    }

    if !doc_sent.cover.is_none() {
        if !doc_sent.cover.clone().unwrap().is_empty() {
            let img_bytes = reqwest::get(cover.unwrap())
                .await
                .unwrap()
                .bytes()
                .await
                .unwrap();
            let image = image::load_from_memory(&img_bytes).unwrap();
            let temp_cover = doc_sent.cover.clone().unwrap();
            let img_id = format!(
                "{}{}",
                Local::now().format("%Y-%m-%d-%H-%M-%S"),
                temp_cover.split("/").last().unwrap()
            );
            image
                .save(format!("{}/{}", images_dir, img_id))
                .expect("Failed to save image");
            cover = Some(img_id.to_string());
        }
    }

    let book: book::Model = book::Model {
        open_library_key: doc_sent.open_library_key.to_owned(),
        title: (doc_sent.title.to_owned()),
        edition_count: doc_sent.edition_count.to_owned(),
        first_publish_year: (doc_sent.first_publish_year.to_owned()),
        median_page_count: (doc_sent.median_page_count.to_owned()),
        goodread_id: doc_sent.goodread_id.to_owned(),
        description: doc_sent.description.to_owned(),
        comments: doc_sent.comments.to_owned(),
        cover: cover.to_owned(),
        rating: doc_sent.rating.to_owned(),
        time_added: doc_sent.time_added.to_owned(),
        id: 1,
        location: doc_sent.location.to_owned(),
        user_id: userid,
    };
    let created_book = MutationCore::create_book(conn, book)
        .await
        .expect("could not create book");

    for author_name in doc_sent
        .author_name
        .as_ref()
        .unwrap_or(&vec!["".to_string()])
        .iter()
    {
        let record: book_author::Model = book_author::Model {
            id: 1,
            book_id: (created_book.last_insert_id),
            author_name: author_name.to_owned(),
        };
        MutationCore::create_book_author(conn, record)
            .await
            .expect("could not create book");
    }
    for person in doc_sent
        .person
        .as_ref()
        .unwrap_or(&vec!["".to_string()])
        .iter()
    {
        let record: book_person::Model = book_person::Model {
            id: 1,
            book_id: (created_book.last_insert_id),
            person: person.to_owned(),
        };
        MutationCore::create_book_person(conn, record)
            .await
            .expect("could not create book");
    }
    for place in doc_sent
        .place
        .as_ref()
        .unwrap_or(&vec!["".to_string()])
        .iter()
    {
        let record: book_place::Model = book_place::Model {
            id: 1,
            book_id: (created_book.last_insert_id),
            place: place.to_owned(),
        };
        MutationCore::create_book_place(conn, record)
            .await
            .expect("could not create book");
    }
    for subject in doc_sent
        .subject
        .as_ref()
        .unwrap_or(&vec!["".to_string()])
        .iter()
    {
        let record: book_subject::Model = book_subject::Model {
            id: 1,
            book_id: (created_book.last_insert_id),
            subject: subject.to_owned(),
        };
        MutationCore::create_book_subject(conn, record)
            .await
            .expect("could not create book");
    }
    for time in doc_sent
        .time
        .as_ref()
        .unwrap_or(&vec!["".to_string()])
        .iter()
    {
        let record: book_time::Model = book_time::Model {
            id: 1,
            book_id: (created_book.last_insert_id),
            time: time.to_owned(),
        };
        MutationCore::create_book_time(conn, record)
            .await
            .expect("could not create book");
    }
    for isbn in doc_sent
        .isbn
        .as_ref()
        .unwrap_or(&vec!["".to_string()])
        .iter()
    {
        let record: book_isbn::Model = book_isbn::Model {
            id: 1,
            book_id: (created_book.last_insert_id),
            isbn: isbn.to_owned(),
        };
        MutationCore::create_book_isbn(conn, record)
            .await
            .expect("could not create book");
    }
    let book_meili = BookMeili {
        id: created_book.last_insert_id,
        open_library_key: doc_sent.open_library_key.unwrap_or("".to_string()),
        title: doc_sent.title,
        edition_count: doc_sent.edition_count.unwrap_or(0),
        first_publish_year: doc_sent.first_publish_year.unwrap_or(0),
        median_page_count: doc_sent.median_page_count.unwrap_or(0),
        goodread_id: doc_sent.goodread_id.unwrap_or("".to_string()),
        description: doc_sent.description.unwrap_or("".to_string()),
        cover: cover.unwrap_or("".to_string()),
        location: doc_sent.location.unwrap_or("".to_string()),
        time_added: doc_sent.time_added.unwrap_or("".to_string()),
        rating: doc_sent.rating.unwrap_or(0),
        comments: doc_sent.comments.unwrap_or("".to_string()),
        author_name: doc_sent.author_name.unwrap_or(vec!["".to_string()]),
        person: doc_sent.person.unwrap_or(vec!["".to_string()]),
        place: doc_sent.place.unwrap_or(vec!["".to_string()]),
        subject: doc_sent.subject.unwrap_or(vec!["".to_string()]),
        time: doc_sent.time.unwrap_or(vec!["".to_string()]),
        isbn: doc_sent.isbn.unwrap_or(vec!["".to_string()]),
    };
    booksman_search::create_or_update_book(book_meili, userid, meili_client).await;

    "success"
}

async fn update_book(
    Extension(ref conn): Extension<DatabaseConnection>,
    Extension(ref meili_client): Extension<Client>,
    Extension(user): Extension<booksman_orm::AxumUser>,
    Json(doc_sent): Json<BookUI>,
) -> impl IntoResponse {
    println!("Updating book");

    dotenvy::dotenv().ok();
    let images_dir = env::var("IMAGES_DIR").expect("IMAGES_DIR is not set in .env file");
    let backend_url = env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
    let userid = user.id.clone();

    let mut cover = doc_sent.cover.clone();
    if !doc_sent.cover.is_none() {
        if !doc_sent.cover.clone().unwrap().is_empty() {
            if doc_sent.cover.clone().unwrap().contains(&backend_url) {
                cover = Some(
                    doc_sent
                        .cover
                        .clone()
                        .unwrap()
                        .split("/")
                        .last()
                        .unwrap()
                        .to_string(),
                );
            } else {
                let img_bytes = reqwest::get(cover.unwrap())
                    .await
                    .unwrap()
                    .bytes()
                    .await
                    .unwrap();
                let image = image::load_from_memory(&img_bytes).unwrap();
                let temp_cover = doc_sent.cover.clone().unwrap();
                let img_id = format!(
                    "{}{}",
                    Local::now().format("%Y-%m-%d-%H-%M-%S"),
                    temp_cover.split("/").last().unwrap()
                );
                image
                    .save(format!("{}/{}", images_dir, img_id))
                    .expect("Failed to save image");
                cover = Some(img_id.to_string());
            }
        }
    }
    let book: book::Model = book::Model {
        open_library_key: doc_sent.open_library_key.to_owned(),
        title: (doc_sent.title.to_owned()),
        edition_count: doc_sent.edition_count.to_owned(),
        first_publish_year: (doc_sent.first_publish_year.to_owned()),
        median_page_count: (doc_sent.median_page_count.to_owned()),
        goodread_id: doc_sent.goodread_id.to_owned(),
        description: doc_sent.description.to_owned(),
        comments: doc_sent.comments.to_owned(),
        cover: cover.to_owned(),
        rating: doc_sent.rating.to_owned(),
        time_added: doc_sent.time_added.to_owned(),
        id: doc_sent.id.to_owned(),
        location: doc_sent.location.to_owned(),
        user_id: userid,
    };
    MutationCore::update_book_by_id(conn, book.id, book)
        .await
        .expect("could not update book");

    MutationCore::delete_book_author(conn, doc_sent.id)
        .await
        .expect("could not delete book authors while updating");
    MutationCore::delete_book_person(conn, doc_sent.id)
        .await
        .expect("could not delete book persons while updating");

    MutationCore::delete_book_place(conn, doc_sent.id)
        .await
        .expect("could not delete book places while updating");

    MutationCore::delete_book_subject(conn, doc_sent.id)
        .await
        .expect("could not delete book subjects while updating");

    MutationCore::delete_book_time(conn, doc_sent.id)
        .await
        .expect("could not delete book times while updating");

    MutationCore::delete_book_isbn(conn, doc_sent.id)
        .await
        .expect("could not delete book isbns while updating");

    for author_name in doc_sent
        .author_name
        .as_ref()
        .unwrap_or(&vec!["".to_string()])
        .iter()
    {
        let record: book_author::Model = book_author::Model {
            id: 1,
            book_id: doc_sent.id,
            author_name: author_name.to_owned(),
        };
        MutationCore::create_book_author(conn, record)
            .await
            .expect("could not create book");
    }
    for person in doc_sent
        .person
        .as_ref()
        .unwrap_or(&vec!["".to_string()])
        .iter()
    {
        let record: book_person::Model = book_person::Model {
            id: 1,
            book_id: doc_sent.id,
            person: person.to_owned(),
        };
        MutationCore::create_book_person(conn, record)
            .await
            .expect("could not create book");
    }
    for place in doc_sent
        .place
        .as_ref()
        .unwrap_or(&vec!["".to_string()])
        .iter()
    {
        let record: book_place::Model = book_place::Model {
            id: 1,
            book_id: doc_sent.id,
            place: place.to_owned(),
        };
        MutationCore::create_book_place(conn, record)
            .await
            .expect("could not create book");
    }
    for subject in doc_sent
        .subject
        .as_ref()
        .unwrap_or(&vec!["".to_string()])
        .iter()
    {
        let record: book_subject::Model = book_subject::Model {
            id: 1,
            book_id: doc_sent.id,
            subject: subject.to_owned(),
        };
        MutationCore::create_book_subject(conn, record)
            .await
            .expect("could not create book");
    }
    for time in doc_sent
        .time
        .as_ref()
        .unwrap_or(&vec!["".to_string()])
        .iter()
    {
        let record: book_time::Model = book_time::Model {
            id: 1,
            book_id: doc_sent.id,
            time: time.to_owned(),
        };
        MutationCore::create_book_time(conn, record)
            .await
            .expect("could not create book");
    }
    for isbn in doc_sent
        .isbn
        .as_ref()
        .unwrap_or(&vec!["".to_string()])
        .iter()
    {
        let record: book_isbn::Model = book_isbn::Model {
            id: 1,
            book_id: doc_sent.id,
            isbn: isbn.to_owned(),
        };
        MutationCore::create_book_isbn(conn, record)
            .await
            .expect("could not create book");
    }

    let book_meili = BookMeili {
        id: doc_sent.id,
        open_library_key: doc_sent.open_library_key.unwrap_or("".to_string()),
        title: doc_sent.title,
        edition_count: doc_sent.edition_count.unwrap_or(0),
        first_publish_year: doc_sent.first_publish_year.unwrap_or(0),
        median_page_count: doc_sent.median_page_count.unwrap_or(0),
        goodread_id: doc_sent.goodread_id.unwrap_or("".to_string()),
        description: doc_sent.description.unwrap_or("".to_string()),
        cover: cover.unwrap_or("".to_string()),
        location: doc_sent.location.unwrap_or("".to_string()),
        time_added: doc_sent.time_added.unwrap_or("".to_string()),
        rating: doc_sent.rating.unwrap_or(0),
        comments: doc_sent.comments.unwrap_or("".to_string()),
        author_name: doc_sent.author_name.unwrap_or(vec!["".to_string()]),
        person: doc_sent.person.unwrap_or(vec!["".to_string()]),
        place: doc_sent.place.unwrap_or(vec!["".to_string()]),
        subject: doc_sent.subject.unwrap_or(vec!["".to_string()]),
        time: doc_sent.time.unwrap_or(vec!["".to_string()]),
        isbn: doc_sent.isbn.unwrap_or(vec!["".to_string()]),
    };
    booksman_search::create_or_update_book(book_meili, userid, meili_client).await;

    "success"
}
