use std::{collections::HashMap, vec};

use actix_web::{
    delete, get, post, put,
    web::{self, Json},
    HttpRequest, HttpResponse, Responder,
};
use sqlx::QueryBuilder;

use dotenvy;

use crate::{db, AppState};

pub(crate) mod schemas;

fn verify_auth(token: Option<&str>, data: &AppState) -> schemas::AuthReturn {
    let mut auth_return = schemas::AuthReturn {
        moderator: false,
        admin: false,
    };

    if let Some(token) = token {
        if token == data.auth_tokens.admin {
            auth_return.admin = true;
            auth_return.moderator = true;
        } else if token == data.auth_tokens.moderator {
            auth_return.moderator = true;
        }
    }

    return auth_return;
}

fn get_auth_header(req: &HttpRequest) -> Option<&str> {
    req.headers().get("x-api-key")?.to_str().ok()
}

#[utoipa::path(
    responses(
        (status = 200, description = "OK", body = AuthReturn),
    ),
    security(
        ("api_key" = [])
    ),
)]
#[get("/api/v3/auth")]
pub(crate) async fn auth(data: web::Data<AppState>, req: HttpRequest) -> impl Responder {
    let auth_token = get_auth_header(&req);

    let auth_return = verify_auth(auth_token, &data);

    Json(auth_return)
}

#[utoipa::path(
    responses(
        (status = 200, description = "OK", body = FullViewData),
    ),
    security(
        ("api_key" = [])
    ),
)]
#[get("/api/v3/full_view_data")]
pub(crate) async fn full_view_data(data: web::Data<AppState>) -> impl Responder {
    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do

    let db_games: Vec<db::schemas::Game> = sqlx::query_as("SELECT * FROM public.games ORDER BY id")
        .fetch_all(&data.db)
        .await
        .expect("Error Fetching Data from DB");

    let db_groups: Vec<db::schemas::GameGroup> =
        sqlx::query_as("SELECT * FROM public.game_groups ORDER BY id")
            .fetch_all(&data.db)
            .await
            .expect("Error Fetching Data from DB");

    let db_empires: Vec<db::schemas::Empire> =
        sqlx::query_as("SELECT * FROM public.empires ORDER BY id")
            .fetch_all(&data.db)
            .await
            .expect("Error Fetching Data from DB");

    let db_ethics: Vec<db::schemas::Ethic> =
        sqlx::query_as("SELECT * FROM public.ethics ORDER BY id")
            .fetch_all(&data.db)
            .await
            .expect("Error Fetching Data from DB");

    let db_empire_ethics: Vec<db::schemas::EmpireEthic> =
        sqlx::query_as("SELECT * FROM public.empire_ethics ORDER BY empire_id")
            .fetch_all(&data.db)
            .await
            .expect("Error Fetching Data from DB");

    let db_portrait_groups: Vec<db::schemas::PortraitGroup> =
        sqlx::query_as("SELECT * FROM public.portrait_groups ORDER BY id")
            .fetch_all(&data.db)
            .await
            .expect("Error Fetching Data from DB");

    let db_portraits: Vec<db::schemas::Portrait> =
        sqlx::query_as("SELECT * FROM public.portraits ORDER BY id")
            .fetch_all(&data.db)
            .await
            .expect("Error Fetching Data from DB");

    let mut parsed_data: schemas::FullViewData = schemas::FullViewData {
        games: HashMap::new(),
        ethics: HashMap::new(),
        phenotypes: HashMap::new(),
    };

    // Data processing
    // Species Vector
    db_portrait_groups.iter().for_each(|species| {
        let new_data = schemas::Phenotype {
            id: species.id,
            display: species.name.clone(),
            species: HashMap::new(),
        };

        parsed_data
            .phenotypes
            .entry(species.id)
            .and_modify(|d| *d = new_data.clone())
            .or_insert(new_data);
    });

    db_portraits.iter().for_each(|portrait| {
        let new_data = schemas::Species {
            id: portrait.id,
            hires: portrait.hires.clone(),
            lores: portrait.lores.clone(),
        };

        parsed_data
            .phenotypes
            .get_mut(&portrait.group_id)
            .unwrap()
            .species
            .entry(portrait.id)
            .and_modify(|d| *d = new_data.clone())
            .or_insert(new_data);
    });

    // Games Vector and Children
    db_games.iter().for_each(|game| {
        let new_data = schemas::ChellarisGameLegacy {
            id: game.id,
            name: game.name.clone(),
            empires: HashMap::new(),
            groups: HashMap::new(),
        };

        parsed_data
            .games
            .entry(game.id)
            .and_modify(|d| *d = new_data.clone())
            .or_insert(new_data);
    });

    db_groups.iter().for_each(|group| {
        let new_data = schemas::ChellarisGameGroupLegacy {
            id: group.id,
            name: group.name.clone(),
        };

        parsed_data
            .games
            .get_mut(&group.game_id)
            .unwrap()
            .groups
            .entry(group.id)
            .and_modify(|d| *d = new_data.clone())
            .or_insert(new_data);
    });

    db_empires.iter().for_each(|empire| {
        let new_data = schemas::ChellarisEmpireLegacy {
            id: empire.id,
            gestalt: empire.gestalt,
            machine: empire.portrait_group_id.to_string() == dotenvy::var("MACHINE_GROUP_ID").unwrap_or("12".to_string()),
            group: empire.group_id,
            portrait_id: empire.portrait_id,
            portrait_group_id: empire.portrait_group_id,
            discord_user: None,
            ethics: HashMap::new(),
        };

        parsed_data
            .games
            .get_mut(&empire.game_id)
            .unwrap()
            .empires
            .entry(empire.id)
            .and_modify(|d| *d = new_data.clone())
            .or_insert(new_data);
    });

    // Ethics Vector
    db_ethics.iter().for_each(|ethic| {
        let new_data = schemas::Ethic {
            id: ethic.id,
            display: ethic.name.clone(),
            gestalt: ethic.gestalt_ethic,
        };

        parsed_data
            .ethics
            .entry(ethic.id)
            .and_modify(|d| *d = new_data.clone())
            .or_insert(new_data);
    });

    db_empire_ethics.iter().for_each(|empire_ethic| {
        let game_id = empire_ethic.empire_game_id;
        let id = empire_ethic.empire_id;

        let new_data = schemas::EmpireEthicLegacy {
            ethic_id: empire_ethic.ethics_id,
            display: parsed_data.ethics[&empire_ethic.ethics_id].display.clone(),
            gestalt: parsed_data.ethics[&empire_ethic.ethics_id].gestalt,
            fanatic: empire_ethic.fanatic,
        };

        if new_data.gestalt {
            parsed_data
                .games
                .get_mut(&game_id)
                .unwrap()
                .empires
                .get_mut(&id)
                .unwrap()
                .gestalt = true;
        }

        parsed_data
            .games
            .get_mut(&game_id)
            .unwrap()
            .empires
            .get_mut(&id)
            .unwrap()
            .ethics
            .entry(empire_ethic.ethics_id)
            .and_modify(|d| *d = new_data.clone())
            .or_insert(new_data);
    });

    Json(parsed_data)
}

// Data Fetching Endpoints for Admin/Moderator Menu

// Game Endpoints

#[utoipa::path(
    params(),
    responses(
        (status = 200, description = "OK", body = HashMap<i32, ChellarisGameFlat>),
    ),
    security(
        ("api_key" = [])
    ),
)]
#[get("/api/v3/games")]
pub(crate) async fn list_games(data: web::Data<AppState>) -> impl Responder {
    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do

    let db_games: Vec<db::schemas::Game> = sqlx::query_as("SELECT * FROM public.games ORDER BY id")
        .fetch_all(&data.db)
        .await
        .expect("Error Fetching Data from DB");

    let mut parsed_data: HashMap<i32, schemas::ChellarisGameFlat> = HashMap::new();

    // Data processing
    // Games Vector
    db_games.iter().for_each(|game| {
        let new_data = schemas::ChellarisGameFlat {
            id: game.id,
            name: game.name.clone(),
        };

        parsed_data
            .entry(game.id)
            .and_modify(|d| *d = new_data.clone())
            .or_insert(new_data);
    });

    Json(parsed_data)
}

#[utoipa::path(
    params(
        schemas::GetGameParam
    ),
    responses(
        (status = 200, description = "OK", body = ChellarisGame),
    ),
    security(
        ("api_key" = [])
    ),
)]
#[get("/api/v3/game")]
pub(crate) async fn get_game_data(
    data: web::Data<AppState>,
    path_param: web::Query<schemas::GetGameParam>,
    req: HttpRequest,
) -> impl Responder {
    let auth_token = get_auth_header(&req);
    let param = path_param.into_inner();

    let user_auth: schemas::AuthReturn = verify_auth(auth_token.as_deref(), &data);

    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do
    let db_game: db::schemas::Game = match sqlx::query_as!(
        db::schemas::Game,
        "SELECT * FROM public.games WHERE id = $1",
        param.game_id
    )
    .fetch_one(&data.db)
    .await
    {
        Ok(data) => data,
        Err(_) => return HttpResponse::UnprocessableEntity().finish(),
    };

    let db_empires: Vec<db::schemas::Empire> = sqlx::query_as!(
        db::schemas::Empire,
        "SELECT * FROM public.empires WHERE game_id = $1",
        param.game_id
    )
    .fetch_all(&data.db)
    .await
    .unwrap_or(vec![]);

    let db_groups: Vec<db::schemas::GameGroup> = sqlx::query_as!(
        db::schemas::GameGroup,
        "SELECT * FROM public.game_groups WHERE game_id = $1",
        param.game_id
    )
    .fetch_all(&data.db)
    .await
    .unwrap_or(vec![]);

    // Data Processing
    let mut parsed_data: schemas::ChellarisGame = schemas::ChellarisGame {
        id: db_game.id,
        name: db_game.name,
        empires: HashMap::new(),
        groups: HashMap::new(),
    };

    db_empires.iter().for_each(|empire| {
        let new_empire = schemas::ChellarisEmpireFlat {
            id: empire.id,
            group: empire.group_id,
            game: empire.game_id,
            name: if user_auth.moderator || user_auth.admin {
                empire.name.clone()
            } else {
                "[REDACTED]".to_string()
            },
            discord_user: if user_auth.moderator || user_auth.admin {
                Some("deprecated".to_string())
            } else {
                None
            },
            gestalt: empire.gestalt,
            portrait_id: empire.portrait_id,
            portrait_group_id: empire.portrait_group_id,
        };

        parsed_data
            .empires
            .entry(empire.id)
            .and_modify(|d| *d = new_empire.clone())
            .or_insert(new_empire);
    });

    db_groups.iter().for_each(|group| {
        let new_group = schemas::ChellarisGroupFlat {
            id: group.id,
            name: group.name.clone(),
        };

        parsed_data
            .groups
            .entry(group.id)
            .and_modify(|d| *d = new_group.clone())
            .or_insert(new_group);
    });

    return HttpResponse::Ok().json(parsed_data);
}

#[utoipa::path(
    request_body = PostGameParams,
    responses(
        (status = 200, description = "OK", body = ChellarisGameLite),
        (status = 422, description = "Missing Game Name"),
        (status = 401, description = "Auth Token Invalid"),
    ),
    security(
        ("api_key" = [])
    ),
)]
#[post("/api/v3/game")]
pub(crate) async fn create_game(
    data: web::Data<AppState>,
    params: web::Json<schemas::PostGameParams>,
    req: HttpRequest,
) -> impl Responder {
    let auth_token = get_auth_header(&req);
    let params = params.into_inner();

    let user_auth: schemas::AuthReturn = verify_auth(auth_token.as_deref(), &data);

    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do
    if user_auth.admin || user_auth.moderator {
        if params.game_name != "" {
            let db_game: db::schemas::Game;

            db_game = match sqlx::query_as!(
                db::schemas::Game,
                "INSERT INTO public.games(name) VALUES ($1) RETURNING * ",
                params.game_name
            )
            .fetch_one(&data.db)
            .await
            {
                Ok(data) => data,
                Err(_) => return HttpResponse::UnprocessableEntity().finish(),
            };

            match sqlx::query!(
                "INSERT INTO public.game_groups(name, game_id) VALUES ($1, $2)",
                "N/A",
                db_game.id
            )
            .execute(&data.db)
            .await
            {
                Ok(_) => {}
                Err(_) => return HttpResponse::UnprocessableEntity().finish(),
            };

            let parsed_game: schemas::ChellarisGameFlat = schemas::ChellarisGameFlat {
                id: db_game.id,
                name: db_game.name,
            };

            return HttpResponse::Ok().json(parsed_game);
        } else {
            return HttpResponse::UnprocessableEntity().finish();
        }
    } else {
        return HttpResponse::Unauthorized().finish();
    }
}

#[utoipa::path(
    request_body = UpdateGameParams,
    responses(
        (status = 200, description = "OK", body = ChellarisGameLite),
        (status = 422, description = "Missing Game Name"),
        (status = 401, description = "Auth Token Invalid"),
    ),
    security(
        ("api_key" = [])
    ),
)]
#[put("/api/v3/game")]
pub(crate) async fn edit_game(
    data: web::Data<AppState>,
    params: web::Json<schemas::UpdateGameParams>,
    req: HttpRequest,
) -> impl Responder {
    let auth_token = get_auth_header(&req);
    let params = params.into_inner();

    let user_auth: schemas::AuthReturn = verify_auth(auth_token.as_deref(), &data);

    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do
    if user_auth.admin || user_auth.moderator {
        if params.game_name != "" {
            let db_game: db::schemas::Game;

            db_game = match sqlx::query_as!(
                db::schemas::Game,
                "UPDATE public.games SET name = $1 WHERE id = $2 RETURNING * ;",
                params.game_name,
                params.game_id
            )
            .fetch_one(&data.db)
            .await
            {
                Ok(data) => data,
                Err(_) => return HttpResponse::UnprocessableEntity().finish(),
            };

            let parsed_game: schemas::ChellarisGameFlat = schemas::ChellarisGameFlat {
                id: db_game.id,
                name: db_game.name,
            };

            return HttpResponse::Ok().json(parsed_game);
        } else {
            return HttpResponse::UnprocessableEntity().finish();
        }
    } else {
        return HttpResponse::Unauthorized().finish();
    }
}

#[utoipa::path(
    params(
        schemas::DeleteGameParam,
    ),
    responses(
        (status = 200, description = "OK"),
        (status = 422, description = "Missing Game ID"),
        (status = 401, description = "Auth Token Invalid"),
    ),
    security(
        ("api_key" = [])
    ),
)]
#[delete("/api/v3/game")]
pub(crate) async fn delete_game(
    data: web::Data<AppState>,
    req: HttpRequest,
    param: web::Query<schemas::DeleteGameParam>,
) -> impl Responder {
    let auth_token = get_auth_header(&req);
    let param = param.into_inner();

    let user_auth: schemas::AuthReturn = verify_auth(auth_token, &data);

    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do
    if user_auth.admin || user_auth.moderator {
        match sqlx::query!("DELETE FROM public.games WHERE id = $1", param.game_id)
            .execute(&data.db)
            .await
        {
            Ok(_) => {}
            Err(e) => {
                println!("{:#?}", e);
                return HttpResponse::UnprocessableEntity().finish();
            }
        };

        return HttpResponse::Ok().into();
    } else {
        return HttpResponse::Unauthorized().finish();
    }
}

// Group Endpoints

#[utoipa::path(
    request_body = PostGroupParams,
    responses(
        (status = 200, description = "OK", body = ChellarisGroupFlat),
        (status = 422, description = "Missing Game Name"),
        (status = 401, description = "Auth Token Invalid"),
    ),
    security(
        ("api_key" = [])
    ),
)]
#[post("/api/v3/group")]
pub(crate) async fn create_group(
    data: web::Data<AppState>,
    params: web::Json<schemas::PostGroupParams>,
    req: HttpRequest,
) -> impl Responder {
    let auth_token = get_auth_header(&req);
    let params = params.into_inner();

    let user_auth: schemas::AuthReturn = verify_auth(auth_token.as_deref(), &data);

    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do
    if user_auth.admin || user_auth.moderator {
        if params.group_name != "" {
            let db_group: db::schemas::GameGroup;

            db_group = match sqlx::query_as!(
                db::schemas::GameGroup,
                "INSERT INTO public.game_groups(name, game_id) VALUES ($1, $2) RETURNING * ",
                params.group_name,
                params.game_id
            )
            .fetch_one(&data.db)
            .await
            {
                Ok(data) => data,
                Err(_) => return HttpResponse::UnprocessableEntity().finish(),
            };

            let parsed_group: schemas::ChellarisGroupFlat = schemas::ChellarisGroupFlat {
                id: db_group.id,
                name: db_group.name,
            };

            return HttpResponse::Ok().json(parsed_group);
        } else {
            return HttpResponse::UnprocessableEntity().finish();
        }
    } else {
        return HttpResponse::Unauthorized().finish();
    }
}

#[utoipa::path(
    request_body = UpdateGroupParams,
    responses(
        (status = 200, description = "OK", body = ChellarisGroupFlat),
        (status = 422, description = "Missing Game Name"),
        (status = 401, description = "Auth Token Invalid"),
    ),
    security(
        ("api_key" = [])
    ),
)]
#[put("/api/v3/group")]
pub(crate) async fn edit_group(
    data: web::Data<AppState>,
    params: web::Json<schemas::UpdateGroupParams>,
    req: HttpRequest,
) -> impl Responder {
    let auth_token = get_auth_header(&req);
    let params = params.into_inner();

    let user_auth: schemas::AuthReturn = verify_auth(auth_token.as_deref(), &data);

    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do
    if user_auth.admin || user_auth.moderator {
        if params.group_name != "" {
            let db_group: db::schemas::GameGroup;

            db_group = match sqlx::query_as!(
                db::schemas::GameGroup,
                "UPDATE public.game_groups SET name = $1 WHERE id = $2 AND game_id = $3 RETURNING * ;",
                params.group_name, params.group_id, params.game_id
            )
            .fetch_one(&data.db)
            .await
            {
                Ok(data) => data,
                Err(_) => return HttpResponse::UnprocessableEntity().finish(),
            };

            let parsed_group: schemas::ChellarisGroupFlat = schemas::ChellarisGroupFlat {
                id: db_group.id,
                name: db_group.name,
            };

            return HttpResponse::Ok().json(parsed_group);
        } else {
            return HttpResponse::UnprocessableEntity().finish();
        }
    } else {
        return HttpResponse::Unauthorized().finish();
    }
}

#[utoipa::path(
    params(
        schemas::DeleteGroupParams,
    ),
    responses(
        (status = 200, description = "OK"),
        (status = 422, description = "Missing Game ID"),
        (status = 401, description = "Auth Token Invalid"),
    ),
    security(
        ("api_key" = [])
    ),
)]
#[delete("/api/v3/group")]
pub(crate) async fn delete_group(
    data: web::Data<AppState>,
    req: HttpRequest,
    param: web::Query<schemas::DeleteGroupParams>,
) -> impl Responder {
    let auth_token = get_auth_header(&req);
    let param = param.into_inner();

    let user_auth: schemas::AuthReturn = verify_auth(auth_token, &data);

    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do
    if user_auth.admin || user_auth.moderator {
        match sqlx::query!(
            "DELETE FROM public.game_groups WHERE id = $1 AND game_id = $2 AND name != 'N/A'",
            param.group_id,
            param.game_id
        )
        .execute(&data.db)
        .await
        {
            Ok(_) => {}
            Err(_) => return HttpResponse::UnprocessableEntity().finish(),
        };

        return HttpResponse::Ok().into();
    } else {
        return HttpResponse::Unauthorized().finish();
    }
}

// Empire Endpoints

#[utoipa::path(
    params(
        schemas::GetEmpireParams
    ),
    responses(
        (status = 200, description = "OK", body = ChellarisEmpire),
    ),
    security(
        ("api_key" = [])
    ),
)]
#[get("/api/v3/empire")]
pub(crate) async fn get_empire(
    data: web::Data<AppState>,
    params: web::Query<schemas::GetEmpireParams>,
    req: HttpRequest,
) -> impl Responder {
    let auth_token = get_auth_header(&req);
    let params = params.into_inner();

    let user_auth: schemas::AuthReturn = verify_auth(auth_token.as_deref(), &data);

    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do
    if user_auth.admin || user_auth.moderator {
        let db_empire: db::schemas::Empire;

        let mut db_empire_query =
            QueryBuilder::<sqlx::Postgres>::new("SELECT * FROM public.empires WHERE");

        db_empire_query.push(" id = ").push_bind(params.empire_id);
        db_empire_query
            .push(" AND game_id = ")
            .push_bind(params.game_id);

        db_empire = match db_empire_query
            .build_query_as::<db::schemas::Empire>()
            .fetch_one(&data.db)
            .await
        {
            Ok(data) => data,
            Err(e) => {
                return HttpResponse::UnprocessableEntity().body(format!("{:#?}", e.to_string()))
            }
        };

        let mut db_ethics: HashMap<i32, schemas::EmpireEthic> = HashMap::new();

        let mut db_ethic_query =
            QueryBuilder::<sqlx::Postgres>::new("SELECT * FROM public.empire_ethics WHERE");

        db_ethic_query
            .push(" empire_id = ")
            .push_bind(params.empire_id);
        db_ethic_query
            .push(" AND empire_game_id = ")
            .push_bind(params.game_id);

        match db_ethic_query
            .build_query_as::<db::schemas::EmpireEthic>()
            .fetch_all(&data.db)
            .await
        {
            Ok(data) => {
                for ethic in data {
                    db_ethics.insert(
                        ethic.ethics_id,
                        schemas::EmpireEthic {
                            ethic_id: ethic.ethics_id,
                            fanatic: ethic.fanatic,
                        },
                    );
                }
            }
            Err(e) => {
                return HttpResponse::UnprocessableEntity().body(format!("{:#?}", e.to_string()))
            }
        };

        // Empire Ethic Creation
        let parsed_empire: schemas::ChellarisEmpire = schemas::ChellarisEmpire {
            id: db_empire.id,
            group: db_empire.group_id,
            game: db_empire.game_id,
            name: db_empire.name,
            discord_user: Some("deprecated".to_string()),
            machine: db_empire.portrait_group_id.to_string() == dotenvy::var("MACHINE_GROUP_ID").unwrap_or("12".to_string()),
            gestalt: db_empire.gestalt,
            portrait_id: db_empire.portrait_id,
            portrait_group_id: db_empire.portrait_group_id,
            ethics: db_ethics,
        };

        return HttpResponse::Ok().json(parsed_empire);
    } else {
        return HttpResponse::Unauthorized().finish();
    }
}

#[utoipa::path(
    request_body = PostEmpireParams,
    responses(
        (status = 200, description = "OK", body = ChellarisEmpireFlat),
        (status = 422, description = "Missing Game Name"),
        (status = 401, description = "Auth Token Invalid"),
    ),
    security(
        ("api_key" = [])
    ),
)]
#[post("/api/v3/empire")]
pub(crate) async fn create_empire(
    data: web::Data<AppState>,
    params: web::Json<schemas::PostEmpireParams>,
    req: HttpRequest,
) -> impl Responder {
    let auth_token = get_auth_header(&req);
    let params = params.into_inner();

    let user_auth: schemas::AuthReturn = verify_auth(auth_token.as_deref(), &data);

    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do
    if user_auth.admin || user_auth.moderator {
        let mut all_params_present = true;

        if params.empire_name == "" {
            all_params_present = false;
        }

        if all_params_present {
            // Basic Empire Creation
            let db_empire: db::schemas::Empire;

            let mut db_empire_query =
                QueryBuilder::<sqlx::Postgres>::new("INSERT INTO public.empires(");

            db_empire_query.push("group_id");
            db_empire_query.push(", game_id");
            db_empire_query.push(", name");
            db_empire_query.push(", gestalt");
            db_empire_query.push(", portrait_id");
            db_empire_query.push(", portrait_group_id");
            db_empire_query.push(", discord_user");
            db_empire_query.push(") VALUES (");

            db_empire_query.push("").push_bind(params.group_id);
            db_empire_query.push(", ").push_bind(params.game_id);
            db_empire_query.push(", ").push_bind(params.empire_name);
            db_empire_query.push(", ").push_bind(params.gestalt);
            db_empire_query.push(", ").push_bind(params.portrait_id);
            db_empire_query
                .push(", ")
                .push_bind(params.portrait_group_id);

            if let Some(discord_user) = params.discord_user {
                db_empire_query.push(", ").push_bind(discord_user);
            } else {
                let val: Option<String> = None;
                db_empire_query.push(", ").push_bind(val);
            }

            db_empire_query.push(") RETURNING * ");

            db_empire = match db_empire_query
                .build_query_as::<db::schemas::Empire>()
                .fetch_one(&data.db)
                .await
            {
                Ok(data) => data,
                Err(e) => {
                    return HttpResponse::UnprocessableEntity()
                        .body(format!("{:#?}", e.to_string()))
                }
            };

            let mut db_ethics: HashMap<i32, schemas::EmpireEthic> = HashMap::new();

            for ethic in params.ethics {
                let mut db_ethic_query =
                    QueryBuilder::<sqlx::Postgres>::new("INSERT INTO public.empire_ethics(");

                db_ethic_query.push("empire_id").push(", empire_game_id");
                db_ethic_query.push(", ethics_id").push(", fanatic");

                db_ethic_query.push(") VALUES (");

                db_ethic_query.push("").push_bind(db_empire.id);
                db_ethic_query.push(", ").push_bind(db_empire.game_id);
                db_ethic_query.push(", ").push_bind(ethic.ethic_id);
                db_ethic_query.push(", ").push_bind(ethic.fanatic);

                db_ethic_query.push(") RETURNING * ");

                match db_ethic_query
                    .build_query_as::<db::schemas::EmpireEthic>()
                    .fetch_one(&data.db)
                    .await
                {
                    Ok(data) => db_ethics.insert(
                        data.ethics_id,
                        schemas::EmpireEthic {
                            ethic_id: data.ethics_id,
                            fanatic: data.fanatic,
                        },
                    ),
                    Err(e) => {
                        return HttpResponse::UnprocessableEntity()
                            .body(format!("{:#?}", e.to_string()))
                    }
                };
            }

            // Empire Ethic Creation
            let parsed_empire: schemas::ChellarisEmpire = schemas::ChellarisEmpire {
                id: db_empire.id,
                group: db_empire.group_id,
                game: db_empire.game_id,
                name: db_empire.name,
                discord_user: Some("deprecated".to_string()),
                machine: db_empire.portrait_group_id.to_string() == dotenvy::var("MACHINE_GROUP_ID").unwrap_or("12".to_string()),
                gestalt: db_empire.gestalt,
                portrait_id: db_empire.portrait_id,
                portrait_group_id: db_empire.portrait_group_id,
                ethics: db_ethics,
            };

            return HttpResponse::Ok().json(parsed_empire);
        } else {
            return HttpResponse::UnprocessableEntity().finish();
        }
    } else {
        return HttpResponse::Unauthorized().finish();
    }
}

#[utoipa::path(
    request_body = UpdateEmpireParams,
    responses(
        (status = 200, description = "OK", body = ChellarisEmpireFlat),
        (status = 422, description = "Missing Game Name"),
        (status = 401, description = "Auth Token Invalid"),
    ),
    security(
        ("api_key" = [])
    ),
)]
#[put("/api/v3/empire")]
pub(crate) async fn edit_empire(
    data: web::Data<AppState>,
    params: web::Json<schemas::UpdateEmpireParams>,
    req: HttpRequest,
) -> impl Responder {
    let auth_token = get_auth_header(&req);
    let params = params.into_inner();

    let user_auth: schemas::AuthReturn = verify_auth(auth_token.as_deref(), &data);

    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do
    if user_auth.admin || user_auth.moderator {
        let mut any_param_present = false;

        let mut db_empire_query = QueryBuilder::<sqlx::Postgres>::new("UPDATE public.empires SET ");
        let mut db_empire_separated = db_empire_query.separated(", ");

        // More often than not does nothing but is required to ensure data is handled properly
        db_empire_separated
            .push(" game_id = ")
            .push_bind_unseparated(params.game_id);

        if let Some(new_group) = params.group_id {
            any_param_present = true;
            db_empire_separated
                .push(" group_id = ")
                .push_bind_unseparated(new_group);
        }

        if let Some(new_name) = params.empire_name {
            if new_name != "" {
                any_param_present = true;
                db_empire_separated
                    .push(" name = ")
                    .push_bind_unseparated(new_name);
            }
        }

        if let Some(new_gestalt) = params.gestalt {
            any_param_present = true;
            db_empire_separated
                .push(" gestalt = ")
                .push_bind_unseparated(new_gestalt);
        }

        if let Some(new_portrait) = params.portrait_id {
            any_param_present = true;
            db_empire_separated
                .push(" portrait_id = ")
                .push_bind_unseparated(new_portrait);
        }

        if let Some(new_portrait_group) = params.portrait_group_id {
            any_param_present = true;
            db_empire_separated
                .push(" portrait_group_id = ")
                .push_bind_unseparated(new_portrait_group);
        }

        if let Some(new_discord_user) = params.discord_user {
            if new_discord_user != "" {
                any_param_present = true;
                db_empire_separated
                    .push(" discord_user = ")
                    .push_bind_unseparated(new_discord_user);
            }
            else {
                any_param_present = true;
                let val: Option<String> = None;
                db_empire_separated
                    .push(" discord_user = ").push_bind_unseparated(val);
            }
        }

        let db_empire: db::schemas::Empire;

        if any_param_present {
            db_empire_separated
                .push_unseparated(" WHERE id = ")
                .push_bind_unseparated(params.empire_id);
            db_empire_separated
                .push_unseparated(" AND game_id = ")
                .push_bind_unseparated(params.game_id);
            db_empire_separated.push_unseparated(" RETURNING * ");

            db_empire = match db_empire_query
                .build_query_as::<db::schemas::Empire>()
                .fetch_one(&data.db)
                .await
            {
                Ok(data) => data,
                Err(e) => {
                    return HttpResponse::UnprocessableEntity()
                        .body(format!("{:#?}", e.to_string()))
                }
            };
        } else {
            let mut db_empire_query =
                QueryBuilder::<sqlx::Postgres>::new("SELECT * FROM public.empires");
            db_empire_separated
                .push_unseparated(" WHERE id = ")
                .push_bind_unseparated(params.empire_id);
            db_empire_separated
                .push_unseparated(" AND game_id = ")
                .push_bind_unseparated(params.game_id);

            db_empire = match db_empire_query
                .build_query_as::<db::schemas::Empire>()
                .fetch_one(&data.db)
                .await
            {
                Ok(data) => data,
                Err(e) => {
                    return HttpResponse::UnprocessableEntity()
                        .body(format!("{:#?}", e.to_string()))
                }
            };
        }

        let mut ethics_changed = false;
        let mut db_ethics: HashMap<i32, schemas::EmpireEthic> = HashMap::new();

        if let Some(new_ethics) = params.ethics {
            if new_ethics.len() != 0 {
                ethics_changed = true;

                let mut db_ethic_wipe =
                    QueryBuilder::<sqlx::Postgres>::new("DELETE FROM public.empire_ethics");
                db_ethic_wipe
                    .push(" WHERE empire_id = ")
                    .push_bind(params.empire_id);
                db_ethic_wipe
                    .push(" AND empire_game_id = ")
                    .push_bind(params.game_id);

                match db_ethic_wipe.build().execute(&data.db).await {
                    Ok(_) => {}
                    Err(e) => {
                        return HttpResponse::UnprocessableEntity()
                            .body(format!("{:#?}", e.to_string()))
                    }
                };

                for ethic in new_ethics {
                    let mut db_ethic_query =
                        QueryBuilder::<sqlx::Postgres>::new("INSERT INTO public.empire_ethics(");

                    db_ethic_query.push("empire_id").push(", empire_game_id");
                    db_ethic_query.push(", ethics_id").push(", fanatic");

                    db_ethic_query.push(") VALUES (");

                    db_ethic_query.push("").push_bind(db_empire.id);
                    db_ethic_query.push(", ").push_bind(db_empire.game_id);
                    db_ethic_query.push(", ").push_bind(ethic.ethic_id);
                    db_ethic_query.push(", ").push_bind(ethic.fanatic);

                    db_ethic_query.push(") RETURNING * ");

                    match db_ethic_query.build().execute(&data.db).await {
                        Ok(_) => {}
                        Err(e) => {
                            return HttpResponse::UnprocessableEntity()
                                .body(format!("{:#?}", e.to_string()))
                        }
                    };
                }
            }
        }
        let mut db_ethic_query =
            QueryBuilder::<sqlx::Postgres>::new("SELECT * FROM public.empire_ethics");
        db_ethic_query
            .push(" WHERE empire_id = ")
            .push_bind(params.empire_id);
        db_ethic_query
            .push(" AND empire_game_id = ")
            .push_bind(params.game_id);

        match db_ethic_query
            .build_query_as::<db::schemas::EmpireEthic>()
            .fetch_all(&data.db)
            .await
        {
            Ok(data) => data.iter().for_each(|db_ethic| {
                db_ethics.insert(
                    db_ethic.ethics_id,
                    schemas::EmpireEthic {
                        ethic_id: db_ethic.ethics_id,
                        fanatic: db_ethic.fanatic,
                    },
                );
            }),
            Err(e) => {
                return HttpResponse::UnprocessableEntity().body(format!("{:#?}", e.to_string()))
            }
        };

        if any_param_present || ethics_changed {
            let parsed_empire: schemas::ChellarisEmpire = schemas::ChellarisEmpire {
                id: db_empire.id,
                group: db_empire.group_id,
                game: db_empire.game_id,
                name: db_empire.name,
                discord_user: Some("deprecated".to_string()),
                machine: db_empire.portrait_group_id.to_string() == dotenvy::var("MACHINE_GROUP_ID").unwrap_or("12".to_string()),
                gestalt: db_empire.gestalt,
                portrait_id: db_empire.portrait_id,
                portrait_group_id: db_empire.portrait_group_id,
                ethics: db_ethics,
            };
            return HttpResponse::Ok().json(parsed_empire);
        } else {
            return HttpResponse::UnprocessableEntity().finish();
        }
    } else {
        return HttpResponse::Unauthorized().finish();
    }
}

#[utoipa::path(
    params(
        schemas::DeleteEmpireParams,
    ),
    responses(
        (status = 200, description = "OK"),
        (status = 422, description = "Missing Game ID"),
        (status = 401, description = "Auth Token Invalid"),
    ),
    security(
        ("api_key" = [])
    ),
)]
#[delete("/api/v3/empire")]
pub(crate) async fn delete_empire(
    data: web::Data<AppState>,
    req: HttpRequest,
    param: web::Query<schemas::DeleteEmpireParams>,
) -> impl Responder {
    let auth_token = get_auth_header(&req);
    let param = param.into_inner();

    let user_auth: schemas::AuthReturn = verify_auth(auth_token, &data);

    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do
    if user_auth.admin || user_auth.moderator {
        match sqlx::query!(
            "DELETE FROM public.empires WHERE id = $1 AND game_id = $2",
            param.empire_id,
            param.game_id
        )
        .execute(&data.db)
        .await
        {
            Ok(_) => {}
            Err(_) => return HttpResponse::UnprocessableEntity().finish(),
        };

        return HttpResponse::Ok().into();
    } else {
        return HttpResponse::Unauthorized().finish();
    }
}

// Ethics Endpoints
// Data Manipulation Admin Only

#[utoipa::path(
    responses(
        (status = 200, description = "OK", body = ChellarisEthics),
    ),
)]
#[get("/api/v3/ethics")]
pub(crate) async fn get_ethics(data: web::Data<AppState>) -> impl Responder {
    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do
    let mut db_ethics: HashMap<i32, schemas::Ethic> = HashMap::new();

    let mut db_ethic_query = QueryBuilder::<sqlx::Postgres>::new("SELECT * FROM public.ethics");

    match db_ethic_query
        .build_query_as::<db::schemas::Ethic>()
        .fetch_all(&data.db)
        .await
    {
        Ok(data) => {
            for ethic in data {
                db_ethics.insert(
                    ethic.id,
                    schemas::Ethic {
                        id: ethic.id,
                        gestalt: ethic.gestalt_ethic,
                        display: ethic.name,
                    },
                );
            }
        }
        Err(e) => return HttpResponse::UnprocessableEntity().body(format!("{:#?}", e.to_string())),
    };

    // Empire Ethic Creation
    let parsed_data: schemas::ChellarisEthics = schemas::ChellarisEthics { ethics: db_ethics };

    return HttpResponse::Ok().json(parsed_data);
}

// Species & Portrait Endpoints
// Data Manipulation Admin Only

#[utoipa::path(
    responses(
        (status = 200, description = "OK", body = ChellarisPhenotypes),
    ),
)]
#[get("/api/v3/phenotypes")]
pub(crate) async fn get_phenotypes(data: web::Data<AppState>) -> impl Responder {
    // SQL Queries
    // TODO: Optimize by utilizing some SQL magic, for now this has to do
    let mut db_phenotypes: HashMap<i32, schemas::Phenotype> = HashMap::new();

    let mut db_phenotype_query = QueryBuilder::<sqlx::Postgres>::new("SELECT * FROM public.portrait_groups");

    match db_phenotype_query
        .build_query_as::<db::schemas::PortraitGroup>()
        .fetch_all(&data.db)
        .await
    {
        Ok(data) => {
            for phenotype in data {
                db_phenotypes.insert(
                    phenotype.id,
                    schemas::Phenotype {
                        id: phenotype.id,
                        display: phenotype.name,
                        species: HashMap::new(),
                    },
                );
            }
        }
        Err(e) => return HttpResponse::UnprocessableEntity().body(format!("{:#?}", e.to_string())),
    };

    let mut db_species_query = QueryBuilder::<sqlx::Postgres>::new("SELECT * FROM public.portraits");

    match db_species_query
        .build_query_as::<db::schemas::Portrait>()
        .fetch_all(&data.db)
        .await
    {
        Ok(data) => {
            for species in data {
                if let Some(phenotype) = db_phenotypes.get_mut(&species.group_id) {
                    phenotype.species.insert(
                        species.id,
                        schemas::Species {
                            id: species.id,
                            hires: species.hires,
                            lores: species.lores
                        }
                    );
                }
            }
        }
        Err(e) => return HttpResponse::UnprocessableEntity().body(format!("{:#?}", e.to_string())),
    };

    // Empire Ethic Creation
    let parsed_data: schemas::ChellarisPhenotypes = schemas::ChellarisPhenotypes { phenotypes: db_phenotypes };

    return HttpResponse::Ok().json(parsed_data);
}