use crate::ayb_db::models::{DBType, EntityDatabaseSharingLevel, EntityType, PublicSharingLevel};
use crate::client::config::ClientConfig;
use crate::client::http::AybClient;
use crate::error::AybError;
use crate::formatting::TabularFormatter;
use crate::http::structs::{EntityDatabasePath, ProfileLinkUpdate};
use clap::builder::ValueParser;
use clap::{arg, value_parser, ArgMatches, Command, ValueEnum};
use directories::ProjectDirs;
use regex::Regex;
use rustyline::error::ReadlineError;
use rustyline::DefaultEditor;
use std::collections::HashMap;
use std::path::PathBuf;

pub fn entity_database_parser(value: &str) -> Result<EntityDatabasePath, String> {
    let re = Regex::new(r"^(\S+)/(\S+)$").unwrap();
    if re.is_match(value) {
        let captures = re.captures(value).unwrap();
        Ok(EntityDatabasePath {
            entity: captures.get(1).map_or("", |m| m.as_str()).to_string(),
            database: captures.get(2).map_or("", |m| m.as_str()).to_string(),
        })
    } else {
        Err("Argument must be formatted as 'entity/database'".to_string())
    }
}

pub async fn query_and_display(
    client: &AybClient,
    entity: &str,
    database: &str,
    query: &str,
    format: &OutputFormat,
) -> Result<(), std::io::Error> {
    match client.query(entity, database, query).await {
        Ok(query_result) => {
            if !query_result.rows.is_empty() {
                match format {
                    OutputFormat::Table => query_result.generate_table()?,
                    OutputFormat::Csv => query_result.generate_csv()?,
                }
            }
            println!("\nRows: {}", query_result.rows.len());
        }
        Err(err) => {
            println!("Error: {err}");
        }
    }
    Ok(())
}

#[derive(Clone, ValueEnum)]
pub enum OutputFormat {
    Table = 0,
    Csv = 1,
}

impl OutputFormat {
    pub fn to_str(&self) -> &str {
        match self {
            OutputFormat::Table => "table",
            OutputFormat::Csv => "csv",
        }
    }
}

pub fn client_commands() -> Command {
    Command::new("client")
        .about("Connect to an HTTP server")
        .arg(
            arg!(--config <FILE> "Path to the client's configuration file")
                .value_parser(value_parser!(PathBuf))
                .env("AYB_CLIENT_CONFIG_FILE")
        )
        .arg(
            arg!(--url <VALUE> "The server URL")
                .env("AYB_SERVER_URL")
                .required(false)
        )
        .arg(
            arg!(--token <VALUE> "A client API token")
                .env("AYB_API_TOKEN")
                .required(false)
        )
        .subcommand(
            Command::new("create_database")
                .about("Create a database")
                .arg(arg!(<database> "The database to create (e.g., entity/database.sqlite)")
                     .value_parser(ValueParser::new(entity_database_parser))
                     .required(true)
                )
                .arg(
                    arg!(<type> "The type of DB")
                        .value_parser(value_parser!(DBType))
                        .default_value(DBType::Sqlite.to_str())
                        .required(false)
                )
                .arg(
                    arg!(<public_sharing_level> "The sharing level that the public/an anonymous user will have")
                        .value_parser(value_parser!(PublicSharingLevel))
                        .default_value(PublicSharingLevel::NoAccess.to_str())
                        .required(false)
                ),

        )
        .subcommand(
            Command::new("query")
                .about("Query a database")
                .arg(arg!(<database> "The database to which to connect (e.g., entity/database.sqlite)")
                     .value_parser(ValueParser::new(entity_database_parser))
                     .required(true)
                )
                .arg(arg!(<query> "The query to execute. If not provided, an interactive session to write queries will be launched.")
                     .required(false))
                .arg(
                    arg!(--format <type> "The format in which to output the result")
                        .value_parser(value_parser!(OutputFormat))
                        .default_value(OutputFormat::Table.to_str())
                        .required(false)),
        )
        .subcommand(
            Command::new("register")
                .about("Register a user/organization")
                .arg(arg!(<entity> "The entity to create")
                     .required(true))
                .arg(arg!(<email_address> "The email address of the entity")
                     .required(true))
                .arg(
                    arg!(<type> "The type of entity")
                        .value_parser(value_parser!(EntityType))
                        .default_value(EntityType::User.to_str())
                        .required(false)),
        )
        .subcommand(
            Command::new("confirm")
                .about("Confirm an email-based login/registration")
                .arg(arg!(<authentication_token> "The authentication token")
                     .required(true))
        )
        .subcommand(
            Command::new("log_in")
                .about("Log in to ayb via email authentication")
                .arg(arg!(<entity> "The entity to log in as")
                     .required(true))
        )
        .subcommand(
            Command::new("list")
                .about("List the databases of a given entity")
                .arg(arg!(<entity> "The entity to query")
                     .required(true))
                .arg(
                    arg!(--format <type> "The format in which to output the result")
                        .value_parser(value_parser!(OutputFormat))
                        .default_value(OutputFormat::Table.to_str())
                        .required(false)),
        )
        .subcommand(
            Command::new("profile")
                .about("Show the profile of an entity")
                .arg(arg!(<entity> "The entity to query")
                     .required(true))
                .arg(
                    arg!(--format <type> "The format in which to output the result")
                        .value_parser(value_parser!(OutputFormat))
                        .default_value(OutputFormat::Table.to_str())
                        .required(false))
        )
        .subcommand(
            Command::new("update_profile")
                .about("Update the profile of an entity")
                .arg(arg!(<entity> "The entity to update").required(true))
                .arg(arg!(--display_name <value> "New display name").required(false))
                .arg(arg!(--description <value> "New description").required(false))
                .arg(arg!(--organization <value> "New organization").required(false))
                .arg(arg!(--location <value> "New location").required(false))
                .arg(
                    arg!(--links <value> "New links")
                        .required(false)
                        .num_args(0..)
                )
        )
        .subcommand(
            Command::new("update_database")
                .about("Update properties of a database")
                .arg(arg!(<database> "The database to which to connect (e.g., entity/database.sqlite)")
                     .value_parser(ValueParser::new(entity_database_parser))
                     .required(true)
                )
                // As we add other updateable properties, this one won't be required anymore.
                .arg(arg!(--public_sharing_level <value> "The level of public access to enable for this database").value_parser(value_parser!(PublicSharingLevel)).required(true))
        )
        .subcommand(
            Command::new("set_default_url")
                .about("Set the default server URL for future requests in ayb.json")
                .arg(arg!(<url> "The URL to use in the future")
                     .required(true))
        )
        .subcommand(
            Command::new("share")
                .about("Share a database with another entity")
                .arg(arg!(<database> "The database to share (e.g., entity/database.sqlite)")
                     .value_parser(ValueParser::new(entity_database_parser))
                     .required(true)
                )
                .arg(arg!(<entity> "The entity with which to share")
                     .required(true))
                .arg(arg!(<sharing_level> "The level of access for this entity").value_parser(value_parser!(EntityDatabaseSharingLevel)).required(true))
        )
        .subcommand(
            Command::new("list_snapshots")
                .about("List snapshots/backups of a database")
                .arg(arg!(<database> "The database for which to list snapshots (e.g., entity/database.sqlite)")
                     .value_parser(ValueParser::new(entity_database_parser))
                     .required(true)
                )
                .arg(
                    arg!(--format <type> "The format in which to output the result")
                        .value_parser(value_parser!(OutputFormat))
                        .default_value(OutputFormat::Table.to_str())
                        .required(false))
        )
        .subcommand(
            Command::new("database_details")
                .about("Show detailed information about a database")
                .arg(arg!(<database> "The database to get details for (e.g., entity/database.sqlite)")
                     .value_parser(ValueParser::new(entity_database_parser))
                     .required(true)
                )
        )
        .subcommand(
            Command::new("restore_snapshot")
                .about("Restore a database to a particular snapshot/backup")
                .arg(arg!(<database> "The database for which to load a snapshot (e.g., entity/database.sqlite)")
                     .value_parser(ValueParser::new(entity_database_parser))
                     .required(true)
                )
                .arg(arg!(<snapshot_id> "The id of the snapshot to load").required(true))
        )
        .subcommand(
            Command::new("list_database_permissions")
                .about("List entities that have access to a database")
                .arg(arg!(<database> "The database to list permissions for (e.g., entity/database.sqlite)")
                     .value_parser(ValueParser::new(entity_database_parser))
                     .required(true)
                )
                .arg(
                    arg!(--format <type> "The format in which to output the result")
                        .value_parser(value_parser!(OutputFormat))
                        .default_value(OutputFormat::Table.to_str())
                        .required(false))
        )
        .subcommand(
            Command::new("list_tokens")
                .about("List your API tokens")
                .arg(
                    arg!(--format <type> "The format in which to output the result")
                        .value_parser(value_parser!(OutputFormat))
                        .default_value(OutputFormat::Table.to_str())
                        .required(false))
        )
        .subcommand(
            Command::new("revoke_token")
                .about("Revoke an API token")
                .arg(arg!(<short_token> "The short token to revoke (from list_tokens output)")
                     .required(true))
        )
}

pub async fn execute_client_command(matches: &ArgMatches) -> std::io::Result<()> {
    let config_path = if let Some(path) = matches.get_one::<PathBuf>("config") {
        path.clone()
    } else {
        ProjectDirs::from("org", "ayb", "ayb")
            .expect("can't determine ayb project directory directory")
            .config_dir()
            .join("ayb.json")
    };
    let mut config = ClientConfig::from_file(&config_path)?;

    if let Some(matches) = matches.subcommand_matches("set_default_url") {
        if let Some(url) = matches.get_one::<String>("url") {
            config.default_url = Some(url.to_string());
            config.to_file(&config_path)?;
            println!("Saved {url} as new default_url");
            return Ok(());
        }
    }

    let url = if let Some(server_url) = matches.get_one::<String>("url") {
        if config.default_url.is_none() {
            config.default_url = Some(server_url.to_string());
            config.to_file(&config_path)?;
        }
        server_url.to_string()
    } else if let Some(ref server_url) = config.default_url {
        server_url.to_string()
    } else {
        panic!("Server URL is required through --url parameter, AYB_SERVER_URL environment variable, or default_url in {}", config_path.display());
    };

    let token = matches
        .get_one::<String>("token")
        .or(config.authentication.get(&url))
        .cloned();
    let client = AybClient {
        base_url: url.to_string(),
        api_token: token,
    };

    if let Some(matches) = matches.subcommand_matches("create_database") {
        if let (Some(entity_database), Some(db_type), Some(public_sharing_level)) = (
            matches.get_one::<EntityDatabasePath>("database"),
            matches.get_one::<DBType>("type"),
            matches.get_one::<PublicSharingLevel>("public_sharing_level"),
        ) {
            match client
                .create_database(
                    &entity_database.entity,
                    &entity_database.database,
                    db_type,
                    public_sharing_level,
                )
                .await
            {
                Ok(_response) => {
                    println!(
                        "Successfully created {}/{}",
                        entity_database.entity, entity_database.database
                    );
                }
                Err(err) => {
                    println!("Error: {err}");
                }
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("register") {
        if let (Some(entity), Some(email_address), Some(entity_type)) = (
            matches.get_one::<String>("entity"),
            matches.get_one::<String>("email_address"),
            matches.get_one::<EntityType>("type"),
        ) {
            match client.register(entity, email_address, entity_type).await {
                Ok(_response) => {
                    println!("Check your email to finish registering {entity}");
                }
                Err(err) => {
                    println!("Error: {err}");
                }
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("confirm") {
        if let Some(authentication_token) = matches.get_one::<String>("authentication_token") {
            match client.confirm(authentication_token).await {
                Ok(api_token) => {
                    config
                        .authentication
                        .insert(url.clone(), api_token.token.clone());
                    config.to_file(&config_path)?;
                    println!(
                        "Successfully authenticated {} and saved token {}",
                        api_token.entity, api_token.token
                    );
                }
                Err(err) => {
                    println!("Error: {err}");
                }
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("log_in") {
        if let Some(entity) = matches.get_one::<String>("entity") {
            match client.log_in(entity).await {
                Ok(_response) => {
                    println!("Check your email to finish logging in {entity}");
                }
                Err(err) => {
                    println!("Error: {err}");
                }
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("profile") {
        if let (Some(entity), Some(format)) = (
            matches.get_one::<String>("entity"),
            matches.get_one::<OutputFormat>("format"),
        ) {
            match client.entity_details(entity).await {
                Ok(response) => match format {
                    OutputFormat::Table => response.profile.generate_table()?,
                    OutputFormat::Csv => response.profile.generate_csv()?,
                },
                Err(err) => println!("Error: {err}"),
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("update_profile") {
        if let Some(entity) = matches.get_one::<String>("entity") {
            let mut profile_update = HashMap::new();
            if let Some(display_name) = matches.get_one::<String>("display_name").cloned() {
                profile_update.insert("display_name".to_owned(), Some(display_name));
            }

            if let Some(description) = matches.get_one::<String>("description").cloned() {
                profile_update.insert("description".to_owned(), Some(description));
            }

            if let Some(organization) = matches.get_one::<String>("organization").cloned() {
                profile_update.insert("organization".to_owned(), Some(organization));
            }

            if let Some(location) = matches.get_one::<String>("location").cloned() {
                profile_update.insert("location".to_owned(), Some(location));
            }

            if matches.get_many::<String>("links").is_some() {
                profile_update.insert(
                    "links".to_owned(),
                    Some(serde_json::to_string(
                        &matches
                            .get_many::<String>("links")
                            .map(|v| v.into_iter().collect::<Vec<&String>>())
                            .map(|v| {
                                v.into_iter()
                                    .map(|v| ProfileLinkUpdate { url: v.clone() })
                                    .collect::<Vec<ProfileLinkUpdate>>()
                            }),
                    )?),
                );
            }

            match client.update_profile(entity, &profile_update).await {
                Ok(_) => println!("Successfully updated profile"),
                Err(err) => println!("Error: {err}"),
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("list") {
        if let (Some(entity), Some(format)) = (
            matches.get_one::<String>("entity"),
            matches.get_one::<OutputFormat>("format"),
        ) {
            match client.entity_details(entity).await {
                Ok(response) => {
                    if response.databases.is_empty() {
                        println!("No queryable databases owned by {entity}");
                    } else {
                        match format {
                            OutputFormat::Table => response.databases.generate_table()?,
                            OutputFormat::Csv => response.databases.generate_csv()?,
                        }
                    }
                }
                Err(err) => {
                    println!("Error: {err}");
                }
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("query") {
        if let (Some(entity_database), Some(format)) = (
            matches.get_one::<EntityDatabasePath>("database"),
            matches.get_one::<OutputFormat>("format"),
        ) {
            if let Some(query) = matches.get_one::<String>("query") {
                query_and_display(
                    &client,
                    &entity_database.entity,
                    &entity_database.database,
                    query,
                    format,
                )
                .await?;
            } else {
                println!(
                    "Launching an interactive session for {}/{}",
                    entity_database.entity, entity_database.database
                );

                match DefaultEditor::new() {
                    Ok(mut rl) => loop {
                        let prompt =
                            format!("{}/{}> ", entity_database.entity, entity_database.database);
                        let line = rl.readline(&prompt);
                        match line {
                            Ok(line) if line.is_empty() => {}
                            Ok(query) => {
                                let result = rl.add_history_entry(query.as_str());
                                if let Err(err) = result {
                                    println!("Error adding line to history: {err}");
                                };
                                query_and_display(
                                    &client,
                                    &entity_database.entity,
                                    &entity_database.database,
                                    &query,
                                    format,
                                )
                                .await?;
                            }
                            Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break,
                            Err(err) => {
                                println!("Error reading next line: {err}");
                                break;
                            }
                        }
                    },
                    Err(err) => {
                        println!("Error starting readline editor: {err}");
                    }
                }
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("list_snapshots") {
        if let (Some(entity_database), Some(format)) = (
            matches.get_one::<EntityDatabasePath>("database"),
            matches.get_one::<OutputFormat>("format"),
        ) {
            match client
                .list_snapshots(&entity_database.entity, &entity_database.database)
                .await
            {
                Ok(response) => {
                    if response.snapshots.is_empty() {
                        println!(
                            "No snapshots for {}/{}",
                            entity_database.entity, entity_database.database
                        );
                    } else {
                        match format {
                            OutputFormat::Table => response.snapshots.generate_table()?,
                            OutputFormat::Csv => response.snapshots.generate_csv()?,
                        }
                    }
                }
                Err(err) => {
                    println!("Error: {err}");
                }
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("restore_snapshot") {
        if let (Some(entity_database), Some(snapshot_id)) = (
            matches.get_one::<EntityDatabasePath>("database"),
            matches.get_one::<String>("snapshot_id"),
        ) {
            match client
                .restore_snapshot(
                    &entity_database.entity,
                    &entity_database.database,
                    snapshot_id,
                )
                .await
            {
                Ok(_response) => {
                    println!(
                        "Restored {}/{} to snapshot {}",
                        entity_database.entity, entity_database.database, snapshot_id
                    );
                }
                Err(AybError::SnapshotDoesNotExistError) => {
                    println!(
                        "Error: Snapshot {} does not exist for {}/{}",
                        snapshot_id, entity_database.entity, entity_database.database
                    )
                }
                Err(err) => {
                    println!("Error: {err}");
                }
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("update_database") {
        if let (Some(entity_database), Some(public_sharing_level)) = (
            matches.get_one::<EntityDatabasePath>("database"),
            matches.get_one::<PublicSharingLevel>("public_sharing_level"),
        ) {
            match client
                .update_database(
                    &entity_database.entity,
                    &entity_database.database,
                    public_sharing_level,
                )
                .await
            {
                Ok(_response) => {
                    println!(
                        "Database {}/{} updated successfully",
                        entity_database.entity, entity_database.database
                    );
                }
                Err(err) => {
                    println!("Error: {err}");
                }
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("share") {
        if let (Some(entity_database), Some(entity), Some(sharing_level)) = (
            matches.get_one::<EntityDatabasePath>("database"),
            matches.get_one::<String>("entity"),
            matches.get_one::<EntityDatabaseSharingLevel>("sharing_level"),
        ) {
            match client
                .share(
                    &entity_database.entity,
                    &entity_database.database,
                    entity,
                    sharing_level,
                )
                .await
            {
                Ok(_response) => {
                    println!(
                        "Permissions for {} on {}/{} updated successfully",
                        entity, entity_database.entity, entity_database.database
                    );
                }
                Err(err) => {
                    println!("Error: {err}");
                }
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("database_details") {
        if let Some(entity_database) = matches.get_one::<EntityDatabasePath>("database") {
            match client
                .database_details(&entity_database.entity, &entity_database.database)
                .await
            {
                Ok(details) => {
                    println!(
                        "Database: {}/{}",
                        entity_database.entity, entity_database.database
                    );
                    println!("Type: {}", details.database_type);

                    let access_level = match &details.highest_query_access_level {
                        Some(mode) => format!("{mode:?}"),
                        None => "No query access".to_string(),
                    };
                    println!("Access level: {access_level}");

                    if details.can_manage_database {
                        println!("You have management permissions for this database");
                    }
                }
                Err(err) => {
                    println!("Error: {err}");
                }
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("list_database_permissions") {
        if let (Some(entity_database), Some(format)) = (
            matches.get_one::<EntityDatabasePath>("database"),
            matches.get_one::<OutputFormat>("format"),
        ) {
            match client
                .list_database_permissions(&entity_database.entity, &entity_database.database)
                .await
            {
                Ok(response) => {
                    if response.permissions.is_empty() {
                        println!(
                            "No shared permissions for {}/{}",
                            entity_database.entity, entity_database.database
                        );
                    } else {
                        match format {
                            OutputFormat::Table => response.permissions.generate_table()?,
                            OutputFormat::Csv => response.permissions.generate_csv()?,
                        }
                    }
                }
                Err(err) => {
                    println!("Error: {err}");
                }
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("list_tokens") {
        if let Some(format) = matches.get_one::<OutputFormat>("format") {
            match client.list_tokens().await {
                Ok(response) => {
                    if response.tokens.is_empty() {
                        println!("No active API tokens");
                    } else {
                        match format {
                            OutputFormat::Table => response.tokens.generate_table()?,
                            OutputFormat::Csv => response.tokens.generate_csv()?,
                        }
                    }
                }
                Err(err) => {
                    println!("Error: {err}");
                }
            }
        }
    } else if let Some(matches) = matches.subcommand_matches("revoke_token") {
        if let Some(short_token) = matches.get_one::<String>("short_token") {
            match client.revoke_token(short_token).await {
                Ok(_) => {
                    println!("Successfully revoked token {short_token}");
                }
                Err(err) => {
                    println!("Error: {err}");
                }
            }
        }
    }

    Ok(())
}
