package de.storchp.fdroidbuildstatus.adapter.db

import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.text.TextUtils
import android.util.Log
import androidx.core.database.sqlite.transaction
import de.storchp.fdroidbuildstatus.adapter.db.DbConstants.Apps
import de.storchp.fdroidbuildstatus.adapter.db.DbConstants.BuildRuns
import de.storchp.fdroidbuildstatus.adapter.db.DbConstants.Builds
import de.storchp.fdroidbuildstatus.adapter.db.DbConstants.DATABASE_TABLE_APPS
import de.storchp.fdroidbuildstatus.adapter.db.DbConstants.DATABASE_TABLE_BUILDS
import de.storchp.fdroidbuildstatus.adapter.db.DbConstants.DATABASE_TABLE_BUILD_RUNS
import de.storchp.fdroidbuildstatus.adapter.db.DbConstants.DATABASE_TABLE_VERSIONS
import de.storchp.fdroidbuildstatus.adapter.db.DbConstants.Notifications
import de.storchp.fdroidbuildstatus.adapter.db.DbConstants.Versions
import de.storchp.fdroidbuildstatus.adapter.fdroid.Index
import de.storchp.fdroidbuildstatus.adapter.fdroid.RunningResult
import de.storchp.fdroidbuildstatus.adapter.fdroid.UpdateResult
import de.storchp.fdroidbuildstatus.adapter.gitlab.Metadata
import de.storchp.fdroidbuildstatus.model.App
import de.storchp.fdroidbuildstatus.model.AppBuild
import de.storchp.fdroidbuildstatus.model.AppNotification
import de.storchp.fdroidbuildstatus.model.BuildCycle
import de.storchp.fdroidbuildstatus.model.BuildRun
import de.storchp.fdroidbuildstatus.model.BuildStatus
import de.storchp.fdroidbuildstatus.model.StatusFilter
import okio.use
import java.util.Date

private val TAG = DbAdapter::class.java.simpleName

class DbAdapter(private val context: Context) {
    private lateinit var dbHelper: DbOpenHelper
    private lateinit var db: SQLiteDatabase
    fun open() {
        dbHelper = DbOpenHelper(context)
        db = dbHelper.writableDatabase
    }

    @Suppress("UNUSED")
    fun close() {
        db.close()
        dbHelper.close()
    }

    fun loadFavourites(): Map<String, App> {
        val apps = mutableMapOf<String, App>()
        db.rawQuery(
            "SELECT * FROM $DATABASE_TABLE_APPS WHERE ${Apps.FAVOURITE} = 1",
            null
        ).use { cursor ->
            while (cursor.moveToNext()) {
                val app = createAppFromCursor(cursor)
                apps[app.id] = app
            }
        }
        return apps
    }

    private fun createAppFromCursor(cursor: Cursor) = App(
        id = cursor.getString(cursor.getColumnIndexOrThrow(Apps.ID)),
        name = cursor.getString(cursor.getColumnIndexOrThrow(Apps.NAME)),
        favourite = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.FAVOURITE)) == 1,
        sourceCode = cursor.getString(cursor.getColumnIndexOrThrow(Apps.SOURCE_CODE)),
        disabled = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.DISABLED)) == 1,
        needsUpdate = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.NEEDS_UPDATE)) == 1,
        archived = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.ARCHIVED)) == 1,
        noPackages = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.NO_PACKAGES)) == 1,
        noUpdateCheck = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.NO_UPDATE_CHECK)) == 1
    )

    fun saveBuildRun(buildRun: RunningResult?, buildCycle: BuildCycle) {
        Log.i(TAG, "Save new buildRun $buildCycle ${buildRun!!.lastModified}")
        db.transaction {
            delete(
                DATABASE_TABLE_BUILD_RUNS,
                "${BuildRuns.BUILD_RUN_TYPE} = ?",
                arrayOf(buildCycle.name)
            )
            val valuesBuildRun = ContentValues().apply {
                put(BuildRuns.BUILD_RUN_TYPE, buildCycle.name)
                put(BuildRuns.START, buildRun.startTimestamp)
                put(BuildRuns.END, buildRun.endTimestamp)
                put(
                    BuildRuns.LAST_MODIFIED,
                    if (buildRun.lastModified != null) buildRun.lastModified!!.time else null
                )
                put(BuildRuns.LAST_UPDATED, System.currentTimeMillis())
                put(
                    BuildRuns.MAX_BUILD_TIME_REACHED,
                    if (buildRun.getMaxBuildTimeReached()) 1 else 0
                )
                put(BuildRuns.SUBCOMMAND, buildRun.subcommand)
                put(BuildRuns.DATA_COMMIT_ID, buildRun.fdroiddata.commitId)
            }

            insert(DATABASE_TABLE_BUILD_RUNS, null, valuesBuildRun)
            delete(
                DATABASE_TABLE_BUILDS,
                "${Builds.BUILD_RUN_TYPE} = ?",
                arrayOf(buildCycle.name)
            )
            for (item in buildRun.getBuildItems()) {
                item!!.id
                val valuesBuild = ContentValues().apply {
                    put(Builds.BUILD_RUN_TYPE, buildCycle.name)
                    put(Builds.ID, item.id)
                    put(Builds.VERSION_CODE, item.versionCode)
                    put(Builds.STATUS, item.status.name)
                }
                insert(DATABASE_TABLE_BUILDS, null, valuesBuild)
                insertAppIfMissing(item.id)
            }
        }
    }

    private fun insertAppIfMissing(id: String) {
        val values = ContentValues()
        values.put(Apps.ID, id)
        db.insertWithOnConflict(DATABASE_TABLE_APPS, null, values, SQLiteDatabase.CONFLICT_IGNORE)
    }

    fun saveFavourite(id: String) {
        db.query(DATABASE_TABLE_APPS, null, "${Apps.ID} = ?", arrayOf(id), null, null, null)
            .use { cursor ->
                val values = ContentValues()
                values.put(Apps.FAVOURITE, 1)
                if (cursor.moveToFirst()) {
                    db.update(
                        DATABASE_TABLE_APPS,
                        values,
                        "${Apps.ID} = ?",
                        arrayOf(id)
                    )
                    return
                }
                values.put(Apps.ID, id)
                db.insert(DATABASE_TABLE_APPS, null, values)
            }
    }

    fun toggleFavourite(id: String) {
        db.query(DATABASE_TABLE_APPS, null, "${Apps.ID} = ?", arrayOf(id), null, null, null)
            .use { cursor ->
                val values = ContentValues()
                if (cursor.moveToFirst()) {
                    val fav =
                        cursor.getInt(cursor.getColumnIndexOrThrow(Apps.FAVOURITE))
                    values.put(Apps.FAVOURITE, if (fav == 0) 1 else 0)
                    db.update(
                        DATABASE_TABLE_APPS,
                        values,
                        "${Apps.ID} = ?",
                        arrayOf(id)
                    )
                } else {
                    values.put(Apps.ID, id)
                    values.put(Apps.FAVOURITE, 1)
                    db.insert(DATABASE_TABLE_APPS, null, values)
                }
            }
    }

    fun saveUpdate(updateResult: UpdateResult, running: Boolean) {
        Log.i(TAG, "Save new update ${updateResult.lastModified}")
        db.transaction {
            updateAppFeature(Apps.DISABLED, running, updateResult.disabled)
            updateAppFeature(Apps.NEEDS_UPDATE, running, updateResult.needsUpdate)
            updateAppFeature(Apps.ARCHIVED, running, updateResult.archivePolicy0)
            updateAppFeature(Apps.NO_PACKAGES, running, updateResult.noPackages)
            updateAppFeature(Apps.NO_UPDATE_CHECK, running, updateResult.noUpdateCheck)
            saveBuildRun(updateResult, if (running) BuildCycle.RUNNING else BuildCycle.UPDATE)
        }
    }

    private fun updateAppFeature(column: String, running: Boolean, appIds: Set<String>) {
        val updateValues = ContentValues()
        updateValues.put(column, false)
        if (!running) {
            db.update(DATABASE_TABLE_APPS, updateValues, null, null)
        }
        updateValues.put(column, true)
        for (id in appIds) {
            val updated =
                db.update(DATABASE_TABLE_APPS, updateValues, "${Apps.ID} = ?", arrayOf(id))
            if (updated == 0) {
                val insertValues = ContentValues()
                insertValues.put(Apps.ID, id)
                insertValues.put(column, true)
                db.insert(DATABASE_TABLE_APPS, null, insertValues)
            }
        }
    }

    fun updateApps(apps: Collection<Index.App>) {
        for (app in apps) {
            db.query(
                DATABASE_TABLE_APPS,
                null,
                "${Apps.ID}=?",
                arrayOf(app.packageName.toString()),
                null,
                null,
                null
            ).use { cursor ->
                val values = ContentValues()
                if (cursor.moveToFirst()) {
                    values.put(Apps.NAME, app.appName)
                    if (app.sourceCode != null) {
                        values.put(Apps.SOURCE_CODE, app.sourceCode)
                    }
                    db.update(
                        DATABASE_TABLE_APPS,
                        values,
                        "${Apps.ID} = ?",
                        arrayOf(app.packageName)
                    )
                } else {
                    values.put(Apps.ID, app.packageName)
                    values.put(Apps.NAME, app.appName)
                    values.put(Apps.SOURCE_CODE, app.sourceCode)
                    db.insert(DATABASE_TABLE_APPS, null, values)
                }
            }
        }
    }

    fun loadBuildRuns() = buildMap {
        db.rawQuery(
            "SELECT * FROM $DATABASE_TABLE_BUILD_RUNS",
            arrayOf<String>()
        ).use { cursorBuildRuns ->
            while (cursorBuildRuns.moveToNext()) {
                val buildRun = createBuildRunFromCursor(cursorBuildRuns)
                if (buildRun != null) {
                    put(buildRun.buildCycle, buildRun)
                    db.rawQuery(
                        """
                            SELECT ${Builds.STATUS}, COUNT(*) AS ${DbConstants.COUNTER} 
                            FROM $DATABASE_TABLE_BUILDS 
                            WHERE ${Builds.BUILD_RUN_TYPE} = ? 
                            GROUP BY ${Builds.STATUS}
                     """.trimIndent(),
                        arrayOf(buildRun.buildCycle.name)
                    ).use { cursorBuildCount ->
                        while (cursorBuildCount.moveToNext()) {
                            val count = cursorBuildCount.getInt(
                                cursorBuildCount.getColumnIndexOrThrow(DbConstants.COUNTER)
                            )
                            when (BuildStatus.valueOf(
                                cursorBuildCount.getString(
                                    cursorBuildCount.getColumnIndexOrThrow(
                                        Builds.STATUS
                                    )
                                )
                            )) {
                                BuildStatus.SUCCESS -> buildRun.successCount = count
                                BuildStatus.MISSING, BuildStatus.FAILED -> buildRun.failedCount =
                                    count
                            }
                        }
                    }
                }
            }
        }
    }

    fun loadApp(id: String): App? {
        db.rawQuery(
            "SELECT * FROM $DATABASE_TABLE_APPS WHERE ${Apps.ID} = ?",
            arrayOf(id)
        ).use { cursor ->
            if (cursor.moveToFirst()) {
                return createAppFromCursor(cursor)
            }
        }
        return null
    }

    fun updateApp(id: String, metadata: Metadata) {
        db.query(DATABASE_TABLE_APPS, null, "${Apps.ID}=?", arrayOf(id), null, null, null)
            .use { cursor ->
                val values = ContentValues()
                if (cursor.moveToFirst()) {
                    values.put(Apps.NAME, metadata.appName)
                    metadata.sourceCode?.let {
                        values.put(Apps.SOURCE_CODE, it)
                    }
                    db.update(
                        DATABASE_TABLE_APPS,
                        values,
                        "${Apps.ID} = ?",
                        arrayOf(id)
                    )
                } else {
                    values.put(Apps.ID, id)
                    values.put(Apps.NAME, metadata.appName)
                    values.put(Apps.SOURCE_CODE, metadata.sourceCode)
                    db.insert(DATABASE_TABLE_APPS, null, values)
                }
            }
        metadata.builds.forEach { build ->
            upsertVersion(id, build.versionCode, build.versionName)
        }
    }

    private fun upsertVersion(id: String, versionCode: Long, versionName: String?) {
        db.query(
            DATABASE_TABLE_VERSIONS,
            null,
            "${Builds.ID} = ? AND ${Builds.VERSION_CODE} = ?",
            arrayOf(id, versionCode.toString()),
            null,
            null,
            null
        ).use { cursor ->
            val values = ContentValues()
            if (cursor.moveToFirst()) {
                values.put(Versions.VERSION_NAME, versionName)
                db.update(
                    DATABASE_TABLE_VERSIONS,
                    values,
                    "${Builds.ID} = ? AND ${Builds.VERSION_CODE} = ?",
                    arrayOf(id, versionCode.toString())
                )
            } else {
                values.put(Versions.ID, id)
                values.put(Versions.VERSION_CODE, versionCode)
                values.put(Versions.VERSION_NAME, versionName)
                db.insert(DATABASE_TABLE_VERSIONS, null, values)
            }
        }
    }

    fun loadAppBuilds(id: String): App? {
        var app: App? = null
        db.rawQuery(
            """
                    SELECT b.${Builds.BUILD_RUN_TYPE}, 
                        b.${Builds.ID}, 
                        b.${Builds.STATUS}, 
                        v.${Versions.VERSION_NAME}, 
                        b.${Builds.VERSION_CODE}, 
                        a.${Apps.SOURCE_CODE}, 
                        a.${Apps.NAME}, 
                        a.${Apps.FAVOURITE}, 
                        r.${BuildRuns.DATA_COMMIT_ID}, 
                        a.${Apps.DISABLED}, 
                        a.${Apps.NEEDS_UPDATE}, 
                        a.${Apps.ARCHIVED}, 
                        a.${Apps.NO_PACKAGES}, 
                        a.${Apps.NO_UPDATE_CHECK} 
                    FROM $DATABASE_TABLE_APPS a  
                        LEFT JOIN $DATABASE_TABLE_BUILDS b ON b.${Builds.ID} = a.${Apps.ID} 
                        LEFT JOIN $DATABASE_TABLE_VERSIONS v ON b.${Builds.ID} = v.${Versions.ID} AND b.${Builds.VERSION_CODE} = v.${Versions.VERSION_CODE} 
                        LEFT JOIN $DATABASE_TABLE_BUILD_RUNS r ON r.${BuildRuns.BUILD_RUN_TYPE} = b.${BuildRuns.BUILD_RUN_TYPE} 
                    WHERE b.${Builds.ID} = ?
                    """.trimIndent(), listOf(id).toTypedArray()
        ).use { cursor ->
            while (cursor.moveToNext()) {
                if (app == null) {
                    app = App(
                        id = cursor.getString(cursor.getColumnIndexOrThrow(Builds.ID)),
                        name = cursor.getString(cursor.getColumnIndexOrThrow(Apps.NAME)),
                        favourite = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.FAVOURITE)) == 1,
                        sourceCode = cursor.getString(cursor.getColumnIndexOrThrow(Apps.SOURCE_CODE)),
                        disabled = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.DISABLED)) == 1,
                        needsUpdate = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.NEEDS_UPDATE)) == 1,
                        archived = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.ARCHIVED)) == 1,
                        noPackages = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.NO_PACKAGES)) == 1,
                        noUpdateCheck = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.NO_UPDATE_CHECK)) == 1
                    )
                }
                val appBuild = createAppBuildFromCursor(cursor)
                if (appBuild != null) {
                    app.appBuilds.add(appBuild)
                }
            }
        }
        return app
    }

    fun loadAppBuilds(
        buildCycle: BuildCycle,
        favouritesFilter: Boolean,
        statusFilter: StatusFilter,
        search: String?,
        lastAppId: String?,
        limit: Int,
    ): MutableList<App> {
        Log.i(TAG, "loadAppBuilds")
        val appFilter = when {
            statusFilter === StatusFilter.DISABLED -> " AND a.${Apps.DISABLED} = 1"
            statusFilter === StatusFilter.ARCHIVED -> " AND a.${Apps.ARCHIVED} = 1"
            statusFilter === StatusFilter.NEEDS_UPDATE -> " AND a.${Apps.NEEDS_UPDATE} = 1"
            statusFilter === StatusFilter.NO_PACKAGES -> " AND a.${Apps.NO_PACKAGES} = 1"
            statusFilter === StatusFilter.NO_UPDATE_CHECK -> " AND a.${Apps.NO_UPDATE_CHECK} = 1"
            else -> ""
        }
        val buildRunTypeFilter = buildString {
            if (buildCycle === BuildCycle.UPDATE) {
                append(" AND ( b.${Builds.BUILD_RUN_TYPE} = '${buildCycle.name}'")
                append(" OR  a.${Apps.DISABLED} = 1")
                append(" OR  a.${Apps.ARCHIVED} = 1")
                append(" OR  a.${Apps.NEEDS_UPDATE} = 1")
                append(" OR  a.${Apps.NO_PACKAGES} = 1")
                append(" OR  a.${Apps.NO_UPDATE_CHECK} = 1 )")
            } else if (buildCycle.isAppBuildFilter) {
                append(" AND b.${Builds.BUILD_RUN_TYPE} = '${buildCycle.name}'")
            }
        }

        val favFilter = if (favouritesFilter) {
            " AND a.${Apps.FAVOURITE} = 1"
        } else {
            " AND (a.${Apps.FAVOURITE} = 0 OR a.${Apps.FAVOURITE} IS NULL)"
        }
        val selectionArgs = mutableListOf<String>()
        var lastAppIdFilter = ""
        if (lastAppId != null) {
            lastAppIdFilter = " AND a.${Apps.ID} > ?"
            selectionArgs.add(lastAppId)
        }
        var searchFilter = ""
        if (!TextUtils.isEmpty(search)) {
            searchFilter = " AND (a.${Apps.NAME} LIKE ? OR a.${Apps.ID} LIKE ?)"
            selectionArgs.add("%$search%")
            selectionArgs.add("%$search%")
        }

        val apps = mutableListOf<App>()
        db.rawQuery(
            """
                SELECT b.${Builds.BUILD_RUN_TYPE}, 
                    a.${Apps.ID}, 
                    b.${Builds.STATUS}, 
                    v.${Versions.VERSION_NAME}, 
                    b.${Builds.VERSION_CODE}, 
                    a.${Apps.SOURCE_CODE}, 
                    a.${Apps.NAME}, 
                    a.${Apps.FAVOURITE}, 
                    r.${BuildRuns.DATA_COMMIT_ID}, 
                    a.${Apps.DISABLED}, 
                    a.${Apps.NEEDS_UPDATE}, 
                    a.${Apps.ARCHIVED}, 
                    a.${Apps.NO_PACKAGES}, 
                    a.${Apps.NO_UPDATE_CHECK} 
                FROM $DATABASE_TABLE_APPS a  
                    LEFT JOIN $DATABASE_TABLE_BUILDS b ON b.${Builds.ID} = a.${Apps.ID} 
                    LEFT JOIN $DATABASE_TABLE_VERSIONS v ON b.${Builds.ID} = v.${Versions.ID} AND b.${Builds.VERSION_CODE} = v.${Versions.VERSION_CODE} 
                    LEFT JOIN $DATABASE_TABLE_BUILD_RUNS r ON r.${BuildRuns.BUILD_RUN_TYPE} = b.${BuildRuns.BUILD_RUN_TYPE} 
                WHERE 1 = 1 $buildRunTypeFilter$appFilter$favFilter$lastAppIdFilter$searchFilter 
                ORDER BY a.${Apps.ID}
                """.trimIndent(),
            selectionArgs.toTypedArray()
        ).use { cursor ->
            var app: App? = null
            while (cursor.moveToNext()) {
                val id = cursor.getString(cursor.getColumnIndexOrThrow(Apps.ID))
                if (app == null || app.id != id) {
                    app = App(
                        id = id,
                        name = cursor.getString(cursor.getColumnIndexOrThrow(Apps.NAME)),
                        favourite = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.FAVOURITE)) == 1,
                        sourceCode = cursor.getString(cursor.getColumnIndexOrThrow(Apps.SOURCE_CODE)),
                        disabled = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.DISABLED)) == 1,
                        needsUpdate = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.NEEDS_UPDATE)) == 1,
                        archived = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.ARCHIVED)) == 1,
                        noPackages = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.NO_PACKAGES)) == 1,
                        noUpdateCheck = cursor.getInt(cursor.getColumnIndexOrThrow(Apps.NO_UPDATE_CHECK)) == 1
                    )
                }
                val appBuild = createAppBuildFromCursor(cursor)
                if (appBuild != null && (
                            !statusFilter.isBuildFilter ||
                                    (statusFilter === StatusFilter.SUCCESSFUL_BUILD && appBuild.status === BuildStatus.SUCCESS) ||
                                    (statusFilter === StatusFilter.FAILED_BUILD && appBuild.status === BuildStatus.FAILED) ||
                                    (statusFilter === StatusFilter.MISSING_BUILD && appBuild.status === BuildStatus.MISSING)
                            )
                ) {
                    app.appBuilds.add(appBuild)
                }
                if (apps.contains(app)) {
                    continue
                }
                if (statusFilter.isBuildFilter && app.appBuilds.isEmpty()) {
                    continue
                }
                if (!statusFilter.isAppFilter && app.appBuilds.isEmpty() && buildCycle != BuildCycle.NONE && app.hasNoBuildStatusProperties()) {
                    continue
                }
                apps.add(app)
                if (apps.size == limit) {
                    break
                }
            }
        }
        return apps
    }

    private fun createAppBuildFromCursor(cursor: Cursor) =
        getBuildRunTypeFromCursor(cursor)?.let {
            AppBuild(
                versionCode = cursor.getLong(cursor.getColumnIndexOrThrow(Builds.VERSION_CODE)),
                versionName = cursor.getString(cursor.getColumnIndexOrThrow(Versions.VERSION_NAME)),
                buildCycle = it,
                status = BuildStatus.valueOf(cursor.getString(cursor.getColumnIndexOrThrow(Builds.STATUS))),
                dataCommitId = cursor.getString(cursor.getColumnIndexOrThrow(BuildRuns.DATA_COMMIT_ID))
            )
        }

    private fun createBuildRunFromCursor(cursor: Cursor) =
        getBuildRunTypeFromCursor(cursor)?.let {
            BuildRun(
                buildCycle = it,
                startTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(BuildRuns.START)),
                endTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(BuildRuns.END)),
                lastModified = Date(cursor.getLong(cursor.getColumnIndexOrThrow(BuildRuns.LAST_MODIFIED))),
                lastUpdated = Date(cursor.getLong(cursor.getColumnIndexOrThrow(BuildRuns.LAST_UPDATED))),
                isMaxBuildTimeReached = cursor.getInt(cursor.getColumnIndexOrThrow(BuildRuns.MAX_BUILD_TIME_REACHED)) == 1,
                subcommand = cursor.getString(cursor.getColumnIndexOrThrow(BuildRuns.SUBCOMMAND)),
            )
        }

    private fun getBuildRunTypeFromCursor(cursor: Cursor) =
        cursor.getString(cursor.getColumnIndexOrThrow(BuildRuns.BUILD_RUN_TYPE))?.let {
            try {
                BuildCycle.valueOf(it)
            } catch (_: Exception) {
                null
            }
        }

    fun getNotificationsFor(buildCycle: BuildCycle, startTimestamp: Long) =
        buildSet {
            db.query(
                DbConstants.DATABASE_TABLE_NOTIFICATIONS,
                arrayOf(Notifications.ID, Notifications.VERSION_CODE),
                "${Notifications.BUILD_RUN_TYPE} = ? AND ${Notifications.START} = ?",
                arrayOf(buildCycle.name, startTimestamp.toString()),
                null,
                null,
                null
            ).use { cursor ->
                while (cursor.moveToNext()) {
                    add(
                        AppNotification(
                            id = cursor.getString(cursor.getColumnIndexOrThrow(Notifications.ID)),
                            versionCode = cursor.getLong(
                                cursor.getColumnIndexOrThrow(
                                    Notifications.VERSION_CODE
                                )
                            )
                        )
                    )
                }
            }
        }

    fun saveNotifications(
        buildCycle: BuildCycle, startTimestamp: Long, notifiedApps: Set<AppNotification>?
    ) {
        db.transaction {
            delete(
                DbConstants.DATABASE_TABLE_NOTIFICATIONS,
                "${Notifications.BUILD_RUN_TYPE} = ?",
                arrayOf(buildCycle.name)
            )
            for (appNotification in notifiedApps!!) {
                val values = ContentValues().apply {
                    put(Notifications.BUILD_RUN_TYPE, buildCycle.name)
                    put(Notifications.START, startTimestamp)
                    put(Notifications.ID, appNotification.id)
                    put(Notifications.VERSION_CODE, appNotification.versionCode)
                }
                insert(DbConstants.DATABASE_TABLE_NOTIFICATIONS, null, values)
            }
        }
    }

    fun saveNotification(buildCycle: BuildCycle, notification: AppNotification) {
        db.transaction {
            delete(
                DbConstants.DATABASE_TABLE_NOTIFICATIONS,
                "${Notifications.BUILD_RUN_TYPE} = ? AND ${Notifications.ID} = ?",
                arrayOf(buildCycle.name, notification.id)
            )

            val values = ContentValues().apply {
                put(Notifications.BUILD_RUN_TYPE, buildCycle.name)
                put(Notifications.START, 0)
                put(Notifications.ID, notification.id)
                put(Notifications.VERSION_CODE, notification.versionCode)
            }
            insert(DbConstants.DATABASE_TABLE_NOTIFICATIONS, null, values)

        }
    }

}
