package de.kif.backend.repository

import de.kif.backend.database.DbWorkGroup
import de.kif.backend.database.dbQuery
import de.kif.backend.util.PushService
import de.kif.common.Message
import de.kif.common.MessageType
import de.kif.common.Repository
import de.kif.common.RepositoryType
import de.kif.common.model.Constraint
import de.kif.common.model.WorkGroup
import de.westermann.kobserve.event.EventHandler
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.list
import org.jetbrains.exposed.sql.*
import java.util.Date

object WorkGroupRepository : Repository<WorkGroup> {

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

    private suspend fun rowToModel(row: ResultRow): WorkGroup {
        val id = row[DbWorkGroup.id]
        val name = row[DbWorkGroup.name]
        val description = row[DbWorkGroup.description]
        val interested = row[DbWorkGroup.interested]
        val trackId = row[DbWorkGroup.trackId]
        val projector = row[DbWorkGroup.projector]
        val resolution = row[DbWorkGroup.resolution]
        val internet = row[DbWorkGroup.internet]
        val whiteboard = row[DbWorkGroup.whiteboard]
        val blackboard = row[DbWorkGroup.blackboard]
        val accessible = row[DbWorkGroup.accessible]
        val length = row[DbWorkGroup.length]
        val language = row[DbWorkGroup.language]
        val constraints = Message.json.parse(Constraint.serializer().list, row[DbWorkGroup.constraints])

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

        val track = trackId?.let { TrackRepository.get(it) }

        return WorkGroup(
            id,
            name,
            description,
            interested,
            track,
            projector,
            resolution,
            internet,
            whiteboard,
            blackboard,
            accessible,
            length,
            language,
            constraints,
            createdAt,
            updatedAt
        )
    }

    override suspend fun get(id: Long): WorkGroup? {
        return dbQuery {
            val row = DbWorkGroup.select { DbWorkGroup.id eq id }.firstOrNull() ?: return@dbQuery null

            runBlocking {
                rowToModel(row)
            }
        }
    }

    override suspend fun create(model: WorkGroup): Long {
        val now = Date().time

        return dbQuery {
            val id = DbWorkGroup.insert {
                it[name] = model.name
                it[description] = model.description
                it[interested] = model.interested
                it[trackId] = model.track?.id
                it[projector] = model.projector
                it[resolution] = model.resolution
                it[internet] = model.internet
                it[whiteboard] = model.whiteboard
                it[blackboard] = model.blackboard
                it[accessible] = model.accessible
                it[length] = model.length
                it[language] = model.language
                it[constraints] = Message.json.stringify(Constraint.serializer().list, model.constraints)

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

            onCreate.emit(id)

            id
        }
    }

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

        val now = Date().time

        dbQuery {
            DbWorkGroup.update({ DbWorkGroup.id eq model.id }) {
                it[name] = model.name
                it[description] = model.description
                it[interested] = model.interested
                it[trackId] = model.track?.id
                it[projector] = model.projector
                it[resolution] = model.resolution
                it[internet] = model.internet
                it[whiteboard] = model.whiteboard
                it[blackboard] = model.blackboard
                it[accessible] = model.accessible
                it[length] = model.length
                it[language] = model.language
                it[constraints] = Message.json.stringify(Constraint.serializer().list, model.constraints)

                it[updatedAt] = now
            }

            onUpdate.emit(model.id)
        }
    }

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

        dbQuery {
            DbWorkGroup.deleteWhere { DbWorkGroup.id eq id }
        }
    }

    override suspend fun all(): List<WorkGroup> {
        return dbQuery {
            val result = DbWorkGroup.selectAll()

            result.map {
                runBlocking {
                    rowToModel(it)
                }
            }
        }
    }

    suspend fun getByTrack(trackId: Long?): List<WorkGroup> {
        return dbQuery {
            val result = DbWorkGroup.select { DbWorkGroup.trackId eq trackId }

            result.map {
                runBlocking {
                    rowToModel(it)
                }
            }
        }
    }

    init {
        TrackRepository.onUpdate { roomId ->
            runBlocking {
                getByTrack(roomId).forEach { workGroup ->
                    if (workGroup.id != null) onUpdate.emit(workGroup.id)
                }
            }
        }
        TrackRepository.onDelete { roomId ->
            runBlocking {
                getByTrack(roomId).forEach { workGroup ->
                    if (workGroup.id != null) {
                        update(workGroup.copy(track = null))
                    }
                }
            }
        }
    }

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