use atuin_client::database::Sqlite;
use atuin_client::record::sqlite_store::SqliteStore;
use atuin_client::settings::Settings;
use atuin_common::time::{OffsetDateTimeExt, UtcOffsetExt};
use atuin_domain::record::RecordSeriesKey;
use clap::Subcommand;
use eyre::Result;
use itertools::Itertools;
use time::OffsetDateTime;
use tracing::instrument;

#[cfg(feature = "sync")]
mod push;

#[cfg(feature = "sync")]
mod pull;

mod purge;
mod rebuild;
mod rekey;
mod verify;

#[derive(Subcommand, Debug)]
#[command(infer_subcommands = true)]
pub enum Cmd {
    /// Print the current status of the record store
    Status,

    /// Rebuild a store (eg atuin store rebuild history)
    Rebuild(rebuild::Rebuild),

    /// Re-encrypt the store with a new key (potential for data loss!)
    Rekey(rekey::Rekey),

    /// Delete all records in the store that cannot be decrypted with the current key
    Purge(purge::Purge),

    /// Verify that all records in the store can be decrypted with the current key
    Verify(verify::Verify),

    /// Push all records to the remote sync server (one way sync)
    #[cfg(feature = "sync")]
    Push(push::Push),

    /// Pull records from the remote sync server (one way sync)
    #[cfg(feature = "sync")]
    Pull(pull::Pull),
}

impl Cmd {
    #[instrument(level = "trace", skip_all, err)]
    pub async fn run(
        &self,
        settings: &Settings,
        database: &Sqlite,
        store: SqliteStore,
    ) -> Result<()> {
        match self {
            Self::Status => self.status(store).await,
            Self::Rebuild(rebuild) => rebuild.run(settings, store, database).await,
            Self::Rekey(rekey) => rekey.run(settings, store).await,
            Self::Verify(verify) => verify.run(settings, store).await,
            Self::Purge(purge) => purge.run(settings, store).await,

            #[cfg(feature = "sync")]
            Self::Push(push) => push.run(settings, store).await,

            #[cfg(feature = "sync")]
            Self::Pull(pull) => pull.run(settings, store, database).await,
        }
    }

    pub async fn status(&self, store: SqliteStore) -> Result<()> {
        let host_id = Settings::host_id().await?;
        let offset = time::UtcOffset::local_or_utc();

        let status = store.status().await?;

        // TODO: should probs build some data structure and then pretty-print it or smth
        for (host, st) in status.hosts.iter().sorted_by_key(|(h, _)| *h) {
            let host_string = if host == &host_id {
                format!("host: {} <- CURRENT HOST", host.0.as_hyphenated())
            } else {
                format!("host: {}", host.0.as_hyphenated())
            };

            println!("{host_string}");

            for (tag, idx) in st.iter().sorted_by_key(|(tag, _)| *tag) {
                println!("\tstore: {tag}");

                let series = RecordSeriesKey::new(*host, tag.clone());
                let first = store.first(&series).await?;
                let last = store.last(&series).await?;

                println!("\t\tidx: {idx}");

                if let Some(first) = first {
                    println!("\t\tfirst: {}", first.id.0.as_hyphenated());

                    let time =
                        OffsetDateTime::from_unix_nanos_u64(first.timestamp).to_offset(offset);
                    println!("\t\t\tcreated: {time}");
                }

                if let Some(last) = last {
                    println!("\t\tlast: {}", last.id.0.as_hyphenated());

                    let time =
                        OffsetDateTime::from_unix_nanos_u64(last.timestamp).to_offset(offset);
                    println!("\t\t\tcreated: {time}");
                }
            }

            println!();
        }

        Ok(())
    }
}
