package inn.ocsf.bee.freigeld.utils

import org.bson.types.ObjectId
import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.stereotype.Service
import java.util.*
import javax.annotation.PostConstruct
import javax.inject.Inject

@Service
class KeyValueStorage {

    @Inject
    private lateinit var keyValueRepo: KeyValueRepository

    @PostConstruct
    fun init() {
        keyValueRepo.findAll().forEach {
            if (it.keyStr == null) it.keyStr = getKeyStr(it.key!!)
            keyValueRepo.save(it)
        }
        //keyValueRepo.findAll().filter { it.bucket == KeyValueBucket.personToSite }.forEach { put(KeyValueBucket.siteToPerson, it.value!!, it.key!!) }
    }

    private fun getKeyStr(key: Any): String {
        val keyStr = when (key) {
            is KeyValueId -> key.getKey()
            else -> key.toString()
        }
        return keyStr
    }

    fun <T> get(bucket: KeyValueBucket, key: Any): T? {
        return keyValueRepo.findOneByBucketAndKeyStr(bucket, getKeyStr(key)).map { it.value as T }.orElse(null)
    }

    fun remove(bucket: KeyValueBucket, key: Any) {
        keyValueRepo.deleteByBucketAndKeyStr(bucket, getKeyStr(key))
    }

    fun put(bucket: KeyValueBucket, key: Any, value: Any) {
        val keyStr = when (key) {
            is KeyValueId -> key.getKey()
            else -> key.toString()
        }
        val oldObj = keyValueRepo.findOneByBucketAndKeyStr(bucket, keyStr)
        if (oldObj.isPresent) {
            oldObj.ifPresent {
                it.value = value
                keyValueRepo.save(it)
            }
        } else {
            keyValueRepo.save(KeyValueObject(bucket, keyStr, key, value))
        }

    }

    fun <T> getByValue(bucket: KeyValueBucket, value: Any): List<T> {
        return keyValueRepo.findAllByBucketAndValue(bucket, value).map { it.key as T }
    }

    fun <K, V> getAll(bucket: KeyValueBucket): List<Pair<K, V>> {
        return keyValueRepo.findAllByBucket(bucket).map { it.key as K to it.value as V }
    }
}

enum class KeyValueBucket(val bucket: String) {
    siteToPerson("site-to-person"),
    @Deprecated("wrong relation model")
    personToSite("person-to-site"),
    emitterToPlan("emitter-to-plan"),
    secretToSite("secret-to-site"),
    personToLabel("person-to-label"),
    personFirstRun("person-first-run")
}

interface KeyValueRepository : MongoRepository<KeyValueObject, ObjectId> {
    fun findOneByBucketAndKeyStr(bucket: KeyValueBucket, key: String): Optional<KeyValueObject>
    fun findAllByBucketAndValue(bucket: KeyValueBucket, value: Any): Collection<KeyValueObject>
    fun findAllByBucket(bucket: KeyValueBucket): Collection<KeyValueObject>
    fun deleteByBucketAndKeyStr(bucket: KeyValueBucket, key: String)
}

@Document("kv-store")
class KeyValueObject() {

    @Id
    var id: ObjectId? = null
    var bucket: KeyValueBucket? = null
    var keyStr: String? = null
    var key: Any? = null
    var value: Any? = null

    constructor(bucket: KeyValueBucket, keyStr: String, key: Any, value: Any) : this() {
        this.bucket = bucket
        this.keyStr = keyStr
        this.key = key
        this.value = value
    }
}

abstract class KeyValueId {

    abstract fun getKey(): String

}