use std::collections::BTreeMap;
use indoc::indoc;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use serde_json::json;
use crate::{
    core::{common::UnixUser, database_privileges::DatabasePrivilegeRowDiff},
    server::sql::{
        database_operations::DatabaseRow, database_privilege_operations::DatabasePrivilegeRow,
        user_operations::DatabaseUser,
    },
};
use super::{MySQLDatabase, MySQLUser};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum DbOrUser {
    Database,
    User,
}
impl DbOrUser {
    pub fn lowercased(&self) -> &'static str {
        match self {
            DbOrUser::Database => "database",
            DbOrUser::User => "user",
        }
    }
    pub fn capitalized(&self) -> &'static str {
        match self {
            DbOrUser::Database => "Database",
            DbOrUser::User => "User",
        }
    }
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
pub enum NameValidationError {
    EmptyString,
    InvalidCharacters,
    TooLong,
}
impl NameValidationError {
    pub fn to_error_message(self, name: &str, db_or_user: DbOrUser) -> String {
        match self {
            NameValidationError::EmptyString => {
                format!("{} name cannot be empty.", db_or_user.capitalized()).to_owned()
            }
            NameValidationError::TooLong => format!(
                "{} is too long. Maximum length is 64 characters.",
                db_or_user.capitalized()
            )
            .to_owned(),
            NameValidationError::InvalidCharacters => format!(
                indoc! {r#"
                  Invalid characters in {} name: '{}'
                  Only A-Z, a-z, 0-9, _ (underscore) and - (dash) are permitted.
                "#},
                db_or_user.lowercased(),
                name
            )
            .to_owned(),
        }
    }
}
impl OwnerValidationError {
    pub fn to_error_message(self, name: &str, db_or_user: DbOrUser) -> String {
        let user = UnixUser::from_enviroment();
        let UnixUser { username, groups } = user.unwrap_or(UnixUser {
            username: "???".to_string(),
            groups: vec![],
        });
        match self {
            OwnerValidationError::NoMatch => format!(
                indoc! {r#"
                  Invalid {} name prefix: '{}' does not match your username or any of your groups.
                  Are you sure you are allowed to create {} names with this prefix?
                  The format should be: <prefix>_<{} name>
                  Allowed prefixes:
                    - {}
                  {}
                "#},
                db_or_user.lowercased(),
                name,
                db_or_user.lowercased(),
                db_or_user.lowercased(),
                username,
                groups
                    .iter()
                    .map(|g| format!("  - {}", g))
                    .sorted()
                    .join("\n"),
            )
            .to_owned(),
            OwnerValidationError::StringEmpty => format!(
                "'{}' is not a valid {} name.",
                name,
                db_or_user.lowercased()
            )
            .to_string(),
        }
    }
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
pub enum OwnerValidationError {
    NoMatch,
    StringEmpty,
}
pub type CreateDatabasesOutput = BTreeMap<MySQLDatabase, Result<(), CreateDatabaseError>>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum CreateDatabaseError {
    SanitizationError(NameValidationError),
    OwnershipError(OwnerValidationError),
    DatabaseAlreadyExists,
    MySqlError(String),
}
pub fn print_create_databases_output_status(output: &CreateDatabasesOutput) {
    for (database_name, result) in output {
        match result {
            Ok(()) => {
                println!("Database '{}' created successfully.", database_name);
            }
            Err(err) => {
                println!("{}", err.to_error_message(database_name));
                println!("Skipping...");
            }
        }
        println!();
    }
}
pub fn print_create_databases_output_status_json(output: &CreateDatabasesOutput) {
    let value = output
        .iter()
        .map(|(name, result)| match result {
            Ok(()) => (name.to_string(), json!({ "status": "success" })),
            Err(err) => (
                name.to_string(),
                json!({
                  "status": "error",
                  "error": err.to_error_message(name),
                }),
            ),
        })
        .collect::<serde_json::Map<_, _>>();
    println!(
        "{}",
        serde_json::to_string_pretty(&value)
            .unwrap_or("Failed to serialize result to JSON".to_string())
    );
}
impl CreateDatabaseError {
    pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
        match self {
            CreateDatabaseError::SanitizationError(err) => {
                err.to_error_message(database_name, DbOrUser::Database)
            }
            CreateDatabaseError::OwnershipError(err) => {
                err.to_error_message(database_name, DbOrUser::Database)
            }
            CreateDatabaseError::DatabaseAlreadyExists => {
                format!("Database {} already exists.", database_name)
            }
            CreateDatabaseError::MySqlError(err) => {
                format!("MySQL error: {}", err)
            }
        }
    }
}
pub type DropDatabasesOutput = BTreeMap<MySQLDatabase, Result<(), DropDatabaseError>>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum DropDatabaseError {
    SanitizationError(NameValidationError),
    OwnershipError(OwnerValidationError),
    DatabaseDoesNotExist,
    MySqlError(String),
}
pub fn print_drop_databases_output_status(output: &DropDatabasesOutput) {
    for (database_name, result) in output {
        match result {
            Ok(()) => {
                println!(
                    "Database '{}' dropped successfully.",
                    database_name.as_str()
                );
            }
            Err(err) => {
                println!("{}", err.to_error_message(database_name));
                println!("Skipping...");
            }
        }
        println!();
    }
}
pub fn print_drop_databases_output_status_json(output: &DropDatabasesOutput) {
    let value = output
        .iter()
        .map(|(name, result)| match result {
            Ok(()) => (name.to_string(), json!({ "status": "success" })),
            Err(err) => (
                name.to_string(),
                json!({
                  "status": "error",
                  "error": err.to_error_message(name),
                }),
            ),
        })
        .collect::<serde_json::Map<_, _>>();
    println!(
        "{}",
        serde_json::to_string_pretty(&value)
            .unwrap_or("Failed to serialize result to JSON".to_string())
    );
}
impl DropDatabaseError {
    pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
        match self {
            DropDatabaseError::SanitizationError(err) => {
                err.to_error_message(database_name, DbOrUser::Database)
            }
            DropDatabaseError::OwnershipError(err) => {
                err.to_error_message(database_name, DbOrUser::Database)
            }
            DropDatabaseError::DatabaseDoesNotExist => {
                format!("Database {} does not exist.", database_name)
            }
            DropDatabaseError::MySqlError(err) => {
                format!("MySQL error: {}", err)
            }
        }
    }
}
pub type ListDatabasesOutput = BTreeMap<MySQLDatabase, Result<DatabaseRow, ListDatabasesError>>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ListDatabasesError {
    SanitizationError(NameValidationError),
    OwnershipError(OwnerValidationError),
    DatabaseDoesNotExist,
    MySqlError(String),
}
impl ListDatabasesError {
    pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
        match self {
            ListDatabasesError::SanitizationError(err) => {
                err.to_error_message(database_name, DbOrUser::Database)
            }
            ListDatabasesError::OwnershipError(err) => {
                err.to_error_message(database_name, DbOrUser::Database)
            }
            ListDatabasesError::DatabaseDoesNotExist => {
                format!("Database '{}' does not exist.", database_name)
            }
            ListDatabasesError::MySqlError(err) => {
                format!("MySQL error: {}", err)
            }
        }
    }
}
pub type ListAllDatabasesOutput = Result<Vec<DatabaseRow>, ListAllDatabasesError>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ListAllDatabasesError {
    MySqlError(String),
}
impl ListAllDatabasesError {
    pub fn to_error_message(&self) -> String {
        match self {
            ListAllDatabasesError::MySqlError(err) => format!("MySQL error: {}", err),
        }
    }
}
pub type GetDatabasesPrivilegeData =
    BTreeMap<MySQLDatabase, Result<Vec<DatabasePrivilegeRow>, GetDatabasesPrivilegeDataError>>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum GetDatabasesPrivilegeDataError {
    SanitizationError(NameValidationError),
    OwnershipError(OwnerValidationError),
    DatabaseDoesNotExist,
    MySqlError(String),
}
impl GetDatabasesPrivilegeDataError {
    pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
        match self {
            GetDatabasesPrivilegeDataError::SanitizationError(err) => {
                err.to_error_message(database_name, DbOrUser::Database)
            }
            GetDatabasesPrivilegeDataError::OwnershipError(err) => {
                err.to_error_message(database_name, DbOrUser::Database)
            }
            GetDatabasesPrivilegeDataError::DatabaseDoesNotExist => {
                format!("Database '{}' does not exist.", database_name)
            }
            GetDatabasesPrivilegeDataError::MySqlError(err) => {
                format!("MySQL error: {}", err)
            }
        }
    }
}
pub type GetAllDatabasesPrivilegeData =
    Result<Vec<DatabasePrivilegeRow>, GetAllDatabasesPrivilegeDataError>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum GetAllDatabasesPrivilegeDataError {
    MySqlError(String),
}
impl GetAllDatabasesPrivilegeDataError {
    pub fn to_error_message(&self) -> String {
        match self {
            GetAllDatabasesPrivilegeDataError::MySqlError(err) => format!("MySQL error: {}", err),
        }
    }
}
pub type ModifyDatabasePrivilegesOutput =
    BTreeMap<(MySQLDatabase, MySQLUser), Result<(), ModifyDatabasePrivilegesError>>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ModifyDatabasePrivilegesError {
    DatabaseSanitizationError(NameValidationError),
    DatabaseOwnershipError(OwnerValidationError),
    UserSanitizationError(NameValidationError),
    UserOwnershipError(OwnerValidationError),
    DatabaseDoesNotExist,
    DiffDoesNotApply(DiffDoesNotApplyError),
    MySqlError(String),
}
#[allow(clippy::enum_variant_names)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum DiffDoesNotApplyError {
    RowAlreadyExists(MySQLDatabase, MySQLUser),
    RowDoesNotExist(MySQLDatabase, MySQLUser),
    RowPrivilegeChangeDoesNotApply(DatabasePrivilegeRowDiff, DatabasePrivilegeRow),
}
pub fn print_modify_database_privileges_output_status(output: &ModifyDatabasePrivilegesOutput) {
    for ((database_name, username), result) in output {
        match result {
            Ok(()) => {
                println!(
                    "Privileges for user '{}' on database '{}' modified successfully.",
                    username, database_name
                );
            }
            Err(err) => {
                println!("{}", err.to_error_message(database_name, username));
                println!("Skipping...");
            }
        }
        println!();
    }
}
impl ModifyDatabasePrivilegesError {
    pub fn to_error_message(&self, database_name: &MySQLDatabase, username: &MySQLUser) -> String {
        match self {
            ModifyDatabasePrivilegesError::DatabaseSanitizationError(err) => {
                err.to_error_message(database_name, DbOrUser::Database)
            }
            ModifyDatabasePrivilegesError::DatabaseOwnershipError(err) => {
                err.to_error_message(database_name, DbOrUser::Database)
            }
            ModifyDatabasePrivilegesError::UserSanitizationError(err) => {
                err.to_error_message(username, DbOrUser::User)
            }
            ModifyDatabasePrivilegesError::UserOwnershipError(err) => {
                err.to_error_message(username, DbOrUser::User)
            }
            ModifyDatabasePrivilegesError::DatabaseDoesNotExist => {
                format!("Database '{}' does not exist.", database_name)
            }
            ModifyDatabasePrivilegesError::DiffDoesNotApply(diff) => {
                format!(
                    "Could not apply privilege change:\n{}",
                    diff.to_error_message()
                )
            }
            ModifyDatabasePrivilegesError::MySqlError(err) => {
                format!("MySQL error: {}", err)
            }
        }
    }
}
impl DiffDoesNotApplyError {
    pub fn to_error_message(&self) -> String {
        match self {
            DiffDoesNotApplyError::RowAlreadyExists(database_name, username) => {
                format!(
                    "Privileges for user '{}' on database '{}' already exist.",
                    username, database_name
                )
            }
            DiffDoesNotApplyError::RowDoesNotExist(database_name, username) => {
                format!(
                    "Privileges for user '{}' on database '{}' do not exist.",
                    username, database_name
                )
            }
            DiffDoesNotApplyError::RowPrivilegeChangeDoesNotApply(diff, row) => {
                format!(
                    "Could not apply privilege change {:?} to row {:?}",
                    diff, row
                )
            }
        }
    }
}
pub type CreateUsersOutput = BTreeMap<MySQLUser, Result<(), CreateUserError>>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum CreateUserError {
    SanitizationError(NameValidationError),
    OwnershipError(OwnerValidationError),
    UserAlreadyExists,
    MySqlError(String),
}
pub fn print_create_users_output_status(output: &CreateUsersOutput) {
    for (username, result) in output {
        match result {
            Ok(()) => {
                println!("User '{}' created successfully.", username);
            }
            Err(err) => {
                println!("{}", err.to_error_message(username));
                println!("Skipping...");
            }
        }
        println!();
    }
}
pub fn print_create_users_output_status_json(output: &CreateUsersOutput) {
    let value = output
        .iter()
        .map(|(name, result)| match result {
            Ok(()) => (name.to_string(), json!({ "status": "success" })),
            Err(err) => (
                name.to_string(),
                json!({
                  "status": "error",
                  "error": err.to_error_message(name),
                }),
            ),
        })
        .collect::<serde_json::Map<_, _>>();
    println!(
        "{}",
        serde_json::to_string_pretty(&value)
            .unwrap_or("Failed to serialize result to JSON".to_string())
    );
}
impl CreateUserError {
    pub fn to_error_message(&self, username: &MySQLUser) -> String {
        match self {
            CreateUserError::SanitizationError(err) => {
                err.to_error_message(username, DbOrUser::User)
            }
            CreateUserError::OwnershipError(err) => err.to_error_message(username, DbOrUser::User),
            CreateUserError::UserAlreadyExists => {
                format!("User '{}' already exists.", username)
            }
            CreateUserError::MySqlError(err) => {
                format!("MySQL error: {}", err)
            }
        }
    }
}
pub type DropUsersOutput = BTreeMap<MySQLUser, Result<(), DropUserError>>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum DropUserError {
    SanitizationError(NameValidationError),
    OwnershipError(OwnerValidationError),
    UserDoesNotExist,
    MySqlError(String),
}
pub fn print_drop_users_output_status(output: &DropUsersOutput) {
    for (username, result) in output {
        match result {
            Ok(()) => {
                println!("User '{}' dropped successfully.", username);
            }
            Err(err) => {
                println!("{}", err.to_error_message(username));
                println!("Skipping...");
            }
        }
        println!();
    }
}
pub fn print_drop_users_output_status_json(output: &DropUsersOutput) {
    let value = output
        .iter()
        .map(|(name, result)| match result {
            Ok(()) => (name.to_string(), json!({ "status": "success" })),
            Err(err) => (
                name.to_string(),
                json!({
                  "status": "error",
                  "error": err.to_error_message(name),
                }),
            ),
        })
        .collect::<serde_json::Map<_, _>>();
    println!(
        "{}",
        serde_json::to_string_pretty(&value)
            .unwrap_or("Failed to serialize result to JSON".to_string())
    );
}
impl DropUserError {
    pub fn to_error_message(&self, username: &MySQLUser) -> String {
        match self {
            DropUserError::SanitizationError(err) => err.to_error_message(username, DbOrUser::User),
            DropUserError::OwnershipError(err) => err.to_error_message(username, DbOrUser::User),
            DropUserError::UserDoesNotExist => {
                format!("User '{}' does not exist.", username)
            }
            DropUserError::MySqlError(err) => {
                format!("MySQL error: {}", err)
            }
        }
    }
}
pub type SetPasswordOutput = Result<(), SetPasswordError>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SetPasswordError {
    SanitizationError(NameValidationError),
    OwnershipError(OwnerValidationError),
    UserDoesNotExist,
    MySqlError(String),
}
pub fn print_set_password_output_status(output: &SetPasswordOutput, username: &MySQLUser) {
    match output {
        Ok(()) => {
            println!("Password for user '{}' set successfully.", username);
        }
        Err(err) => {
            println!("{}", err.to_error_message(username));
            println!("Skipping...");
        }
    }
}
impl SetPasswordError {
    pub fn to_error_message(&self, username: &MySQLUser) -> String {
        match self {
            SetPasswordError::SanitizationError(err) => {
                err.to_error_message(username, DbOrUser::User)
            }
            SetPasswordError::OwnershipError(err) => err.to_error_message(username, DbOrUser::User),
            SetPasswordError::UserDoesNotExist => {
                format!("User '{}' does not exist.", username)
            }
            SetPasswordError::MySqlError(err) => {
                format!("MySQL error: {}", err)
            }
        }
    }
}
pub type LockUsersOutput = BTreeMap<MySQLUser, Result<(), LockUserError>>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum LockUserError {
    SanitizationError(NameValidationError),
    OwnershipError(OwnerValidationError),
    UserDoesNotExist,
    UserIsAlreadyLocked,
    MySqlError(String),
}
pub fn print_lock_users_output_status(output: &LockUsersOutput) {
    for (username, result) in output {
        match result {
            Ok(()) => {
                println!("User '{}' locked successfully.", username);
            }
            Err(err) => {
                println!("{}", err.to_error_message(username));
                println!("Skipping...");
            }
        }
        println!();
    }
}
pub fn print_lock_users_output_status_json(output: &LockUsersOutput) {
    let value = output
        .iter()
        .map(|(name, result)| match result {
            Ok(()) => (name.to_string(), json!({ "status": "success" })),
            Err(err) => (
                name.to_string(),
                json!({
                  "status": "error",
                  "error": err.to_error_message(name),
                }),
            ),
        })
        .collect::<serde_json::Map<_, _>>();
    println!(
        "{}",
        serde_json::to_string_pretty(&value)
            .unwrap_or("Failed to serialize result to JSON".to_string())
    );
}
impl LockUserError {
    pub fn to_error_message(&self, username: &MySQLUser) -> String {
        match self {
            LockUserError::SanitizationError(err) => err.to_error_message(username, DbOrUser::User),
            LockUserError::OwnershipError(err) => err.to_error_message(username, DbOrUser::User),
            LockUserError::UserDoesNotExist => {
                format!("User '{}' does not exist.", username)
            }
            LockUserError::UserIsAlreadyLocked => {
                format!("User '{}' is already locked.", username)
            }
            LockUserError::MySqlError(err) => {
                format!("MySQL error: {}", err)
            }
        }
    }
}
pub type UnlockUsersOutput = BTreeMap<MySQLUser, Result<(), UnlockUserError>>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum UnlockUserError {
    SanitizationError(NameValidationError),
    OwnershipError(OwnerValidationError),
    UserDoesNotExist,
    UserIsAlreadyUnlocked,
    MySqlError(String),
}
pub fn print_unlock_users_output_status(output: &UnlockUsersOutput) {
    for (username, result) in output {
        match result {
            Ok(()) => {
                println!("User '{}' unlocked successfully.", username);
            }
            Err(err) => {
                println!("{}", err.to_error_message(username));
                println!("Skipping...");
            }
        }
        println!();
    }
}
pub fn print_unlock_users_output_status_json(output: &UnlockUsersOutput) {
    let value = output
        .iter()
        .map(|(name, result)| match result {
            Ok(()) => (name.to_string(), json!({ "status": "success" })),
            Err(err) => (
                name.to_string(),
                json!({
                  "status": "error",
                  "error": err.to_error_message(name),
                }),
            ),
        })
        .collect::<serde_json::Map<_, _>>();
    println!(
        "{}",
        serde_json::to_string_pretty(&value)
            .unwrap_or("Failed to serialize result to JSON".to_string())
    );
}
impl UnlockUserError {
    pub fn to_error_message(&self, username: &MySQLUser) -> String {
        match self {
            UnlockUserError::SanitizationError(err) => {
                err.to_error_message(username, DbOrUser::User)
            }
            UnlockUserError::OwnershipError(err) => err.to_error_message(username, DbOrUser::User),
            UnlockUserError::UserDoesNotExist => {
                format!("User '{}' does not exist.", username)
            }
            UnlockUserError::UserIsAlreadyUnlocked => {
                format!("User '{}' is already unlocked.", username)
            }
            UnlockUserError::MySqlError(err) => {
                format!("MySQL error: {}", err)
            }
        }
    }
}
pub type ListUsersOutput = BTreeMap<MySQLUser, Result<DatabaseUser, ListUsersError>>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ListUsersError {
    SanitizationError(NameValidationError),
    OwnershipError(OwnerValidationError),
    UserDoesNotExist,
    MySqlError(String),
}
impl ListUsersError {
    pub fn to_error_message(&self, username: &MySQLUser) -> String {
        match self {
            ListUsersError::SanitizationError(err) => {
                err.to_error_message(username, DbOrUser::User)
            }
            ListUsersError::OwnershipError(err) => err.to_error_message(username, DbOrUser::User),
            ListUsersError::UserDoesNotExist => {
                format!("User '{}' does not exist.", username)
            }
            ListUsersError::MySqlError(err) => {
                format!("MySQL error: {}", err)
            }
        }
    }
}
pub type ListAllUsersOutput = Result<Vec<DatabaseUser>, ListAllUsersError>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ListAllUsersError {
    MySqlError(String),
}
impl ListAllUsersError {
    pub fn to_error_message(&self) -> String {
        match self {
            ListAllUsersError::MySqlError(err) => format!("MySQL error: {}", err),
        }
    }
}