package inn.ocsf.bee.freigeld.core.service

import inn.ocsf.bee.freigeld.core.model.Ticket
import inn.ocsf.bee.freigeld.core.model.TicketStatus
import inn.ocsf.bee.freigeld.core.model.data.TicketData
import inn.ocsf.bee.freigeld.core.repo.TicketRepository
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.bson.types.ObjectId
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
import java.util.stream.Stream
import javax.annotation.PostConstruct
import javax.inject.Inject

@Service
class TicketService {

    @Inject
    private lateinit var ticketRepo: TicketRepository

    private val listenerMap = ConcurrentHashMap<String, CopyOnWriteArrayList<(Ticket) -> Boolean?>>()

    private val log = LoggerFactory.getLogger(javaClass)

    fun findAll(topic: String, statusSet: Set<TicketStatus>): Stream<TicketData> {
        return ticketRepo.findAllByTopicAndStatusInOrderByTsDesc(topic, statusSet.toList())
    }

    private fun tick() {
        listenerMap.keys().iterator().forEach { topic ->
            peekFirst(topic)?.let { t ->
                val ok: Boolean? = try {
                    listenerMap.get(topic)?.map { fn ->
                        try {
                            fn(t)
                        } catch (t: Throwable) {
                            log.error("error in event", t)
                            true
                        }
                    }?.firstOrNull { it ?: false }
                } catch (t: Throwable) {
                    log.error("error in event", t)
                    true
                }
                if (ok == true) remove(t)
            }
        }
    }

    @PostConstruct
    fun init() {
        GlobalScope.launch {
            do {
                tick()
                delay(100)
            } while (true)
        }
    }

    private fun remove(e: Ticket) {
        val td = ticketRepo.findOneByTicketId(e.id).orElseThrow { RuntimeException("event ${e.id} not found") }
        td.status = TicketStatus.completed
        ticketRepo.save(td)
    }

    private fun newTicket(topic: String, e: Ticket): TicketData {
        val td = TicketData()
        td.status = TicketStatus.created
        td.ts = Date()
        td.topic = topic
        td.ticket = e
        return td
    }

    fun offerAfter(topic: String, after: Date, e: Ticket): ObjectId {
        val td = newTicket(topic, e)
        td.hold = after
        return ticketRepo.save(td).id
    }

    fun offerLast(topic: String, e: Ticket): ObjectId {
        return ticketRepo.save(newTicket(topic, e)).id
    }

    fun peekFirst(topic: String, modifyFn: ((Ticket) -> Ticket)? = null): Ticket? {
        val dt = Date()
        return findAll(topic, setOf(TicketStatus.created)).filter { it.hold == null || it.hold.before(dt) || it.hold.equals(dt) }.limit(1).findFirst().orElse(null)?.let {
            if (modifyFn != null) {
                it.ticket = modifyFn(it.ticket)
                ticketRepo.save(it)
                it.ticket
            } else {
                it.ticket
            }
        }
    }

    fun listen(topic: String, fn: (Ticket) -> Boolean?) {
        listenerMap.computeIfAbsent(topic) { CopyOnWriteArrayList() }
        listenerMap.get(topic)?.add(fn)
    }
}