use crate::{MigrationContext, MigrationEntry, MigrationError};

#[derive(Clone, Copy)]
pub(crate) struct RegisteredMigration(pub(crate) MigrationVariant);

#[derive(Clone, Copy)]
pub(crate) enum MigrationVariant {
    Generic(MigrationEntry),
    #[cfg(feature = "sqlite")]
    Sqlite(crate::SqliteMigrationEntry),
    #[cfg(feature = "postgres")]
    Postgres(crate::PostgresMigrationEntry),
    #[cfg(feature = "mariadb")]
    Mariadb(crate::MariadbMigrationEntry),
}

impl RegisteredMigration {
    pub(crate) fn generic(entry: MigrationEntry) -> Self {
        Self(MigrationVariant::Generic(entry))
    }

    #[cfg(feature = "sqlite")]
    pub(crate) fn sqlite(entry: crate::SqliteMigrationEntry) -> Self {
        Self(MigrationVariant::Sqlite(entry))
    }

    #[cfg(feature = "postgres")]
    pub(crate) fn postgres(entry: crate::PostgresMigrationEntry) -> Self {
        Self(MigrationVariant::Postgres(entry))
    }

    #[cfg(feature = "mariadb")]
    pub(crate) fn mariadb(entry: crate::MariadbMigrationEntry) -> Self {
        Self(MigrationVariant::Mariadb(entry))
    }

    pub(crate) fn name(self) -> &'static str {
        match self.0 {
            MigrationVariant::Generic(entry) => entry.name,
            #[cfg(feature = "sqlite")]
            MigrationVariant::Sqlite(entry) => entry.name,
            #[cfg(feature = "postgres")]
            MigrationVariant::Postgres(entry) => entry.name,
            #[cfg(feature = "mariadb")]
            MigrationVariant::Mariadb(entry) => entry.name,
        }
    }

    pub(crate) fn version(self) -> u64 {
        match self.0 {
            MigrationVariant::Generic(entry) => entry.version,
            #[cfg(feature = "sqlite")]
            MigrationVariant::Sqlite(entry) => entry.version,
            #[cfg(feature = "postgres")]
            MigrationVariant::Postgres(entry) => entry.version,
            #[cfg(feature = "mariadb")]
            MigrationVariant::Mariadb(entry) => entry.version,
        }
    }

    pub(crate) async fn up<'a>(
        self,
        ctx: &'a mut MigrationContext<'a>,
    ) -> Result<(), MigrationError> {
        match self.0 {
            MigrationVariant::Generic(entry) => (entry.up)(ctx).await,
            #[cfg(feature = "sqlite")]
            MigrationVariant::Sqlite(entry) => match ctx {
                MigrationContext::Sqlite(inner) => (entry.up)(inner).await,
                #[allow(unreachable_patterns)]
                _ => unreachable!("sqlite migration used with non-sqlite context"),
            },
            #[cfg(feature = "postgres")]
            MigrationVariant::Postgres(entry) => match ctx {
                MigrationContext::Postgres(inner) => (entry.up)(inner).await,
                #[allow(unreachable_patterns)]
                _ => unreachable!("postgres migration used with non-postgres context"),
            },
            #[cfg(feature = "mariadb")]
            MigrationVariant::Mariadb(entry) => match ctx {
                MigrationContext::Mariadb(inner) => (entry.up)(inner).await,
                #[allow(unreachable_patterns)]
                _ => unreachable!("mariadb migration used with non-mariadb context"),
            },
        }
    }

    pub(crate) async fn down<'a>(
        self,
        ctx: &'a mut MigrationContext<'a>,
    ) -> Result<(), MigrationError> {
        match self.0 {
            MigrationVariant::Generic(entry) => (entry.down)(ctx).await,
            #[cfg(feature = "sqlite")]
            MigrationVariant::Sqlite(entry) => match ctx {
                MigrationContext::Sqlite(inner) => (entry.down)(inner).await,
                #[allow(unreachable_patterns)]
                _ => unreachable!("sqlite migration used with non-sqlite context"),
            },
            #[cfg(feature = "postgres")]
            MigrationVariant::Postgres(entry) => match ctx {
                MigrationContext::Postgres(inner) => (entry.down)(inner).await,
                #[allow(unreachable_patterns)]
                _ => unreachable!("postgres migration used with non-postgres context"),
            },
            #[cfg(feature = "mariadb")]
            MigrationVariant::Mariadb(entry) => match ctx {
                MigrationContext::Mariadb(inner) => (entry.down)(inner).await,
                #[allow(unreachable_patterns)]
                _ => unreachable!("mariadb migration used with non-mariadb context"),
            },
        }
    }
}
