package ch.threema.storage.factories

import ch.threema.base.utils.getThreemaLogger
import ch.threema.storage.DatabaseCreationProvider
import ch.threema.storage.DatabaseProvider
import ch.threema.storage.buildContentValues
import ch.threema.storage.runDelete
import ch.threema.storage.runQuery

private val logger = getThreemaLogger("TaskArchiveFactory")

class TaskArchiveFactory(databaseProvider: DatabaseProvider) :
    ModelFactory(databaseProvider, TABLE_NAME) {

    /**
     * Insert a new task. Note that leading and trailing whitespaces are ignored.
     */
    fun insert(task: String) {
        val contentValues = buildContentValues { put(COLUMN_TASK, task.trim()) }
        writableDatabase.insert(tableName, null, contentValues)
    }

    /**
     * Removes the task that corresponds to this string. Note that leading and trailing whitespaces
     * are ignored. If several tasks with the same string representation exist, only the oldest task
     * is being removed. Note that older tasks are NOT deleted. Use this only to remove invalid
     * tasks. To remove the task including all older tasks, use [remove].
     */
    fun removeOne(task: String) {
        val taskId = getOldestIdForTask(task) ?: return
        writableDatabase.runDelete(
            table = tableName,
            whereClause = "$COLUMN_ID=?",
            whereArgs = arrayOf(taskId),
        )
    }

    /**
     * Removes the task that corresponds to this string. Note that leading and trailing whitespaces
     * are ignored. If several tasks with the same string representation exist, only the oldest task
     * is being removed. Note that all older tasks than the given task are deleted as well.
     */
    fun remove(task: String) {
        val taskId = getOldestIdForTask(task) ?: return
        val numDeleted = writableDatabase.runDelete(
            table = tableName,
            whereClause = "$COLUMN_ID<=?",
            whereArgs = arrayOf(taskId),
        )

        // If several tasks have been deleted, then the just finished task was not the oldest one.
        // This means that we skipped some tasks or have a task reordering!
        if (numDeleted > 1) {
            logger.error(
                "{} instead of 1 tasks were deleted. Some tasks may have been skipped or reordered.",
                numDeleted,
            )
        }
    }

    /**
     * Replace all tasks with encoding [oldTaskEncoding] by [newTaskEncoding].
     */
    fun replace(oldTaskEncoding: String, newTaskEncoding: String) {
        writableDatabase.update(
            tableName,
            buildContentValues { put(COLUMN_TASK, newTaskEncoding.trim()) },
            "$COLUMN_TASK = ?",
            arrayOf(oldTaskEncoding.trim()),
        )
    }

    /**
     * Get all tasks currently stored in the queue. The list is ordered ascending, so that the
     * oldest tasks are first.
     */
    fun getAll(): List<String> =
        readableDatabase.runQuery(tableName, orderBy = "$COLUMN_ID ASC")
            .use {
                buildList {
                    val taskColumnId = it.getColumnIndex(COLUMN_TASK)
                    while (it.moveToNext()) {
                        add(it.getString(taskColumnId))
                    }
                }
            }

    private fun getOldestIdForTask(task: String): Long? {
        readableDatabase.runQuery(
            table = tableName,
            selection = "$COLUMN_TASK=?",
            selectionArgs = arrayOf(task.trim()),
            orderBy = "$COLUMN_ID ASC",
            limit = "1",
        ).use {
            if (it.moveToFirst()) {
                return it.getLong(it.getColumnIndexOrThrow(COLUMN_ID))
            }
        }
        return null
    }

    object Creator : DatabaseCreationProvider {
        override fun getCreationStatements() = arrayOf(
            "CREATE TABLE `$TABLE_NAME` (" +
                "`$COLUMN_ID` INTEGER PRIMARY KEY AUTOINCREMENT, " +
                "`$COLUMN_TASK` STRING NOT NULL)",
        )
    }

    companion object {
        private const val TABLE_NAME = "tasks"
        private const val COLUMN_ID = "id"
        private const val COLUMN_TASK = "task"
    }
}
