package de.kif.backend.repository

import de.kif.backend.database.DbPost
import de.kif.backend.database.dbQuery
import de.kif.backend.util.PushService
import de.kif.common.MessageType
import de.kif.common.Repository
import de.kif.common.RepositoryType
import de.kif.common.model.Post
import de.westermann.kobserve.event.EventHandler
import kotlinx.coroutines.runBlocking
import org.jetbrains.exposed.sql.*
import java.util.Date

object PostRepository : Repository<Post> {

    override val onCreate = EventHandler<Long>()
    override val onUpdate = EventHandler<Long>()
    override val onDelete = EventHandler<Long>()

    private fun rowToModel(row: ResultRow): Post {
        val id = row[DbPost.id]
        val name = row[DbPost.name]
        val content = row[DbPost.content]
        val url = row[DbPost.url]
        val image = row[DbPost.image]
        val pinned = row[DbPost.pinned]
        val hideOnProjector = row[DbPost.hideOnProjector]

        val createdAt = row[DbPost.createdAt]
        val updatedAt = row[DbPost.updatedAt]

        return Post(id, name, content, url, image, pinned, hideOnProjector, createdAt, updatedAt)
    }

    override suspend fun get(id: Long): Post? {
        return dbQuery {
            rowToModel(DbPost.select { DbPost.id eq id }.firstOrNull() ?: return@dbQuery null)
        }
    }

    override suspend fun create(model: Post): Long {
        if (model.pinned) {
            for (it in getPinned()) {
                update(it.copy(pinned = false))
            }
        }

        val now = Date().time

        return dbQuery {
            if (model.pinned) {
                DbPost.update({ DbPost.pinned eq true }) {
                    it[pinned] = false
                }
            }

            val id = DbPost.insert {
                it[name] = model.name
                it[content] = model.content
                it[url] = model.url
                it[image] = model.image
                it[pinned] = model.pinned
                it[hideOnProjector] = model.hideOnProjector

                it[createdAt] = now
                it[updatedAt] = now
            }[DbPost.id] ?: throw IllegalStateException("Cannot create model!")

            onCreate.emit(id)

            id
        }
    }

    override suspend fun update(model: Post) {
        if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")

        if (model.pinned) {
            for (it in getPinned()) {
                if (it.id != model.id) {
                    update(it.copy(pinned = false))
                }
            }
        }

        val now = Date().time

        dbQuery {
            DbPost.update({ DbPost.id eq model.id }) {
                it[name] = model.name
                it[content] = model.content
                it[url] = model.url
                it[image] = model.image
                it[pinned] = model.pinned
                it[hideOnProjector] = model.hideOnProjector

                it[updatedAt] = now
            }

            onUpdate.emit(model.id)
        }
    }

    override suspend fun delete(id: Long) {
        onDelete.emit(id)

        dbQuery {
            DbPost.deleteWhere { DbPost.id eq id }
        }
    }

    override suspend fun all(): List<Post> {
        return dbQuery {
            val result = DbPost.selectAll().orderBy(
                DbPost.pinned to SortOrder.ASC,
                DbPost.id to SortOrder.ASC
            )

            result.map(this::rowToModel)
        }
    }

    suspend fun getByUrl(url: String): Post? {
        return dbQuery {
            val result = DbPost.select { DbPost.url eq url }

            result.firstOrNull()?.let {
                rowToModel(it)
            }
        }
    }

    private suspend fun getPinned(): List<Post> {
        return dbQuery {
            val result = DbPost.select { DbPost.pinned eq true }

            result.map(this::rowToModel)
        }
    }

    fun registerPushService() {
        onCreate {
            runBlocking {
                PushService.notify(MessageType.CREATE, RepositoryType.POST, it)
            }
        }
        onUpdate {
            runBlocking {
                PushService.notify(MessageType.UPDATE, RepositoryType.POST, it)
            }
        }
        onDelete {
            runBlocking {
                PushService.notify(MessageType.DELETE, RepositoryType.POST, it)
            }
        }
    }
}
