package ru.yandex.qe.dispenser.utils

import org.springframework.context.MessageSource
import ru.yandex.qe.dispenser.domain.i18n.LocalizableString
import ru.yandex.qe.dispenser.domain.util.LocalizationUtils
import ru.yandex.qe.dispenser.ws.common.domain.errors.ErrorCollection
import ru.yandex.qe.dispenser.ws.common.domain.errors.TypedError
import ru.yandex.qe.dispenser.ws.common.domain.result.Result
import java.util.*

inline fun <T, E: Any> binding(block: ResultBinding<T, E>.() -> Result<T, E>): Result<T, E> {
    val receiver = ResultBinding<T, E>()
    return try {
        with(receiver) { block() }
    } catch (ex: BindException) {
        Result.failure(receiver.error)
    }
}

internal object BindException : Exception() {
    override fun fillInStackTrace(): Throwable {
        return this
    }
}

class ResultBinding<T, E: Any> {
    @PublishedApi
    internal lateinit var error: E

    fun <T2> Result<T2, E>.bind(): T2? {
        if (isSuccess) {
            return match({ it }, { null })
        } else {
            doOnFailure { this@ResultBinding.error = it }
            throw BindException
        }
    }
}

typealias ApiErrorCollection = ErrorCollection<String, TypedError<String>>
typealias ApiResult<T> = Result<T, ApiErrorCollection>

fun localizedErrors(messageSource: MessageSource, locale: Locale,
                    block: LocalizedErrorCollectionBuilder.() -> Unit): ApiErrorCollection {
    return LocalizedErrorCollectionBuilderImpl(messageSource, locale, ErrorCollection.typedStringBuilder())
        .apply(block).build()
}

interface LocalizedErrorCollectionBuilder {
    fun error(key: String, typed: (error: String) -> TypedError<String>)
    fun fieldError(field: String, key: String, typed: (error: String) -> TypedError<String>)
    fun build(): ApiErrorCollection
}

val INVALID_ERROR: (error: String) -> TypedError<String> = { e -> TypedError.invalid(e) }
val FORBIDDEN_ERROR: (error: String) -> TypedError<String> = { e -> TypedError.forbidden(e) }
val BAD_REQUEST_ERROR: (error: String) -> TypedError<String> = { e -> TypedError.badRequest(e) }
val NOT_FOUND_ERROR: (error: String) -> TypedError<String> = { e -> TypedError.notFound(e) }
val CONFLICT_ERROR: (error: String) -> TypedError<String> = { e -> TypedError.conflict(e) }

private class LocalizedErrorCollectionBuilderImpl(
    private val messageSource: MessageSource,
    private val locale: Locale,
    private val builder: ErrorCollection.Builder<String, TypedError<String>>
): LocalizedErrorCollectionBuilder {

    override fun error(key: String, typed: (error: String) -> TypedError<String>) {
        builder.addError(typed(localizedError(messageSource, key, locale)))
    }

    override fun fieldError(field: String, key: String, typed: (error: String) -> TypedError<String>) {
        builder.addError(field, typed(localizedError(messageSource, key, locale)))
    }

    override fun build(): ApiErrorCollection {
        return builder.build()
    }

}

private fun localizedError(messageSource: MessageSource, messageKey: String, locale: Locale): String {
    return LocalizationUtils.resolveWithDefaultAsKey(messageSource, LocalizableString.of(messageKey), locale)
}
