package inn.ocsf.bee.freigeld.core.demo

import inn.ocsf.bee.freigeld.core.calc.CoinUtils
import inn.ocsf.bee.freigeld.core.model.BankAccount
import inn.ocsf.bee.freigeld.core.model.Coin
import inn.ocsf.bee.freigeld.core.model.CoinValue
import org.junit.jupiter.api.RepeatedTest
import org.junit.jupiter.api.Test
import org.slf4j.LoggerFactory
import java.util.*
import kotlin.math.absoluteValue
import kotlin.test.assertEquals
import kotlin.test.assertFalse

open class DemoAccount(val accountId: UUID, val personId: UUID) : BankAccount {

    val internalCoins = mutableMapOf<UUID, Coin>()
    var _overall: Long? = null

    private val log = LoggerFactory.getLogger(javaClass)

    override fun getId(): UUID {
        return accountId
    }

    override fun extractOne(coinId: UUID): Coin {
        return if (internalCoins.containsKey(coinId)) {
            val coin = internalCoins.remove(coinId)!!
            if (coin.current == 0L) {
                _overall = null
                log.debug("${this} leave coin ${coin.current}")
            } else {
                decOverall(coin.current)
                log.debug("${this} credit coin ${coin.current}")
            }
            coin
        } else {
            throw IllegalArgumentException("no such coin")
        }
    }

    override fun getOverall(): Long {
        return if (_overall != null) {
            _overall
        } else {
            _overall = internalCoins.values.map { it.current }.onEach { if (it == 0L) throw RuntimeException("illegal value 0") }.sum()
            _overall
        } ?: 0L
    }

    override fun getCoins(): MutableCollection<Coin> {
        return internalCoins.values
    }

    override fun getOwnerId(): UUID {
        return personId
    }

    private fun decOverall(amount: Long) {
        if (amount == 0L) throw IllegalArgumentException("should not be 0")
        if (_overall != null) {
            _overall = _overall!! - amount
        } else {
            _overall = -amount
        }
    }

    private fun incOverall(amount: Long) {
        if (amount == 0L) throw IllegalArgumentException("should not be 0")
        if (_overall != null) {
            _overall = _overall!! + amount
        } else {
            _overall = amount
        }
    }

    override fun acceptOne(coin: Coin) {
        if (coin.id != null && !internalCoins.containsKey(coin.id)) {
            internalCoins.put(coin.id, coin)
            incOverall(coin.current)
        } else if (coin.id == null) {
            throw IllegalArgumentException("fake coin is not acceptable")
        } else if (internalCoins.containsKey(coin.id)) {
            throw IllegalArgumentException("same coin is not acceptable")
        }
        log.debug("${this} debit coin ${CoinUtils.sum(listOf(coin))}")
    }


    override fun toString(): String {
        return accountId.hashCode().absoluteValue.toString(16)
    }
}

private class TestCoin(val coinId: UUID, val initialValue: CoinValue, var _era: Int = 0) : Coin {

    override fun getId(): UUID = coinId

    override fun getValue(): CoinValue = initialValue

    override fun getEra(): Int {
        return _era
    }

    override fun incEra() {
        TODO("Not yet implemented")
    }

    override fun toString(): String {
        return "${initialValue.name} jd${_era} (${current})"
    }
}

class CoinUtilsTest {

    val fromId = UUID.randomUUID()

    val fromAccount = DemoAccount(fromId, fromId)

    val toId = UUID.randomUUID()

    val toAccount = DemoAccount(toId, toId)

    private val log = LoggerFactory.getLogger(javaClass)

    init {
        fromAccount.accept(mutableListOf(TestCoin(UUID.randomUUID(), CoinValue.one)) as Collection<Coin>)
    }

    @Test
    fun `test coin amount`() {
        assertEquals(fromAccount.overall, CoinValue.one.amount)
    }

    @RepeatedTest(5)
    fun `split one to million`() {
        (1..100).map { Math.round(Math.random() * 100_000) }.forEach { x ->
            val skipValuesValues = CoinUtils.akeis.chunked(10)
            val skipValuesIndex = Math.round((skipValuesValues.size - 1) * Math.random()).toInt()
            val skipValues = skipValuesValues[skipValuesIndex].toMutableSet()
            if (skipValues.contains(1)) skipValues.remove(1)
            val varx = CoinUtils.makeSomeVariants(x, skip = skipValues)
            varx.map { v ->
                log.info("${x} = " + v.map { "${it.key.first.getCurrent(it.key.second)}[${it.key.first.amount}c${it.key.second}] * ${it.value}" }.joinToString(" + "))
                assertEquals(x, v.map { it.key.first.getCurrent(it.key.second) * it.value }.sum())
                assertFalse(v.isEmpty())
            }
            assertFalse(varx.isEmpty())
        }
    }

}