package ru.yandex.qe.dispenser.ws.bot

import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.MessageSource
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import ru.yandex.qe.dispenser.domain.bot.BigOrder
import ru.yandex.qe.dispenser.domain.dao.bot.bigorder.BigOrderCache
import ru.yandex.qe.dispenser.domain.dao.bot.bigorder.BigOrderDao
import ru.yandex.qe.dispenser.domain.dao.campaign.CampaignDao
import ru.yandex.qe.dispenser.utils.ApiResult
import ru.yandex.qe.dispenser.utils.BAD_REQUEST_ERROR
import ru.yandex.qe.dispenser.utils.INVALID_ERROR
import ru.yandex.qe.dispenser.utils.NOT_FOUND_ERROR
import ru.yandex.qe.dispenser.utils.binding
import ru.yandex.qe.dispenser.utils.localizedErrors
import ru.yandex.qe.dispenser.ws.common.domain.result.Result
import java.util.*

@Component
open class BigOrderManager(
    private val bigOrderDao: BigOrderDao,
    private val campaignDao: CampaignDao,
    private val bigOrderCache: BigOrderCache,
    @Qualifier("errorMessageSource") private val messageSource: MessageSource
) {

    @Transactional(propagation = Propagation.REQUIRED)
    open fun getById(id: Long, locale: Locale): ApiResult<BigOrderDto> {
        val bigOrder = bigOrderDao.getById(id)
        if (bigOrder == null || bigOrder.isDeleted) {
            return Result.failure(localizedErrors(messageSource, locale) { error("big.order.not.found", NOT_FOUND_ERROR) })
        }
        return Result.success(toDto(bigOrder))
    }

    @Transactional(propagation = Propagation.REQUIRED)
    open fun getPage(from: Long?,
                     limit: Long?,
                     locale: Locale): ApiResult<BigOrdersPageDto> {
        if (limit != null && (limit < 0 || limit > 100)) {
            return Result.failure(localizedErrors(messageSource, locale) { fieldError("limit","invalid.limit.value", BAD_REQUEST_ERROR) })
        }
        val bigOrders = bigOrderDao.getPage(from, limit ?: 100)
        return Result.success(BigOrdersPageDto(bigOrders.map { toDto(it) }, bigOrders.map { it.id }.lastOrNull()))
    }

    @Transactional(propagation = Propagation.REQUIRED)
    open fun create(
        body: CreateBigOrderDto,
        locale: Locale
    ): ApiResult<BigOrderDto> = binding {
        val validated = validateCreate(body, locale).bind()!!
        val created = bigOrderDao.create(validated)
        return Result.success(toDto(created))
    }

    @Transactional(propagation = Propagation.REQUIRED)
    open fun update(
        id: Long,
        body: UpdateBigOrderDto,
        locale: Locale
    ): ApiResult<BigOrderDto> = binding {
        val bigOrder = bigOrderDao.getById(id)
        if (bigOrder == null || bigOrder.isDeleted) {
            return Result.failure(localizedErrors(messageSource, locale) { error("big.order.not.found", NOT_FOUND_ERROR) })
        }
        val validated = validateUpdate(body, bigOrder, locale).bind()!!
        val updated = bigOrderDao.update(validated)
        return Result.success(toDto(updated))
    }

    @Transactional(propagation = Propagation.REQUIRED)
    open fun delete(
        id: Long,
        locale: Locale
    ): ApiResult<Unit> {
        val bigOrder = bigOrderDao.getById(id) ?: return Result
            .failure(localizedErrors(messageSource, locale) { error("big.order.not.found", NOT_FOUND_ERROR) })
        if (bigOrder.isDeleted) {
            return ApiResult.success(Unit)
        }
        val campaignBigOrders = campaignDao.getCampaignOrdersByOrderIds(setOf(bigOrder.id))
        if (campaignBigOrders.isNotEmpty()) {
            return Result.failure(localizedErrors(messageSource, locale) { error("big.order.is.used.in.campaigns", INVALID_ERROR) })
        }
        bigOrderDao.update(BigOrder.update(bigOrder).deleted(true))
        return ApiResult.success(Unit)
    }

    @Transactional(propagation = Propagation.REQUIRED)
    open fun getById(id: Long): BigOrder? {
        return bigOrderDao.getById(id)
    }

    @Transactional(propagation = Propagation.REQUIRED)
    open fun getByIds(ids: Collection<Long>): Set<BigOrder> {
        return bigOrderDao.getByIds(ids)
    }

    @Transactional(propagation = Propagation.REQUIRED)
    open fun getByIdCached(id: Long): BigOrder? {
        return bigOrderCache.getById(id).orElse(null)
    }

    @Transactional(propagation = Propagation.REQUIRED)
    open fun getByIdsCached(ids: Collection<Long>): Set<BigOrder> {
        return bigOrderCache.getByIds(ids)
    }

    @Transactional(propagation = Propagation.REQUIRED)
    open fun getAll(): Set<BigOrder> {
        return bigOrderDao.getAll()
    }

    @Transactional(propagation = Propagation.REQUIRED)
    open fun create(builder: BigOrder.Builder): BigOrder {
        return bigOrderDao.create(builder)
    }

    @Transactional(propagation = Propagation.REQUIRED)
    open fun clear() {
        bigOrderDao.clear()
        bigOrderCache.clear()
    }

    private fun toDto(bigOrder: BigOrder): BigOrderDto {
        return BigOrderDto(bigOrder.id, bigOrder.date)
    }

    private fun validateCreate(body: CreateBigOrderDto, locale: Locale): ApiResult<BigOrder.Builder> {
        if (body.date == null) {
            return Result.failure(localizedErrors(messageSource, locale) { fieldError("date","field.is.required", INVALID_ERROR) })
        }
        return ApiResult.success(BigOrder.builder(body.date))
    }

    private fun validateUpdate(body: UpdateBigOrderDto, bigOrder: BigOrder, locale: Locale): ApiResult<BigOrder.Update> {
        if (body.date == null) {
            return Result.failure(localizedErrors(messageSource, locale) { fieldError("date","field.is.required", INVALID_ERROR) })
        }
        return ApiResult.success(BigOrder.update(bigOrder).date(body.date))
    }

}
