package ru.yandex.dispenser.validation.providers.mds

import mu.KotlinLogging
import org.springframework.stereotype.Component
import ru.yandex.dispenser.validation.client.CampaignType
import ru.yandex.dispenser.validation.client.Environment
import ru.yandex.dispenser.validation.client.ErrorCollection
import ru.yandex.dispenser.validation.client.OrdersSubset
import ru.yandex.dispenser.validation.client.Result
import ru.yandex.dispenser.validation.client.model.Amount
import ru.yandex.dispenser.validation.client.model.CreateChange
import ru.yandex.dispenser.validation.client.model.CreateQuotaRequest
import ru.yandex.dispenser.validation.client.model.QuotaRequest
import ru.yandex.dispenser.validation.client.model.Units
import ru.yandex.dispenser.validation.providers.CommonSettings
import ru.yandex.dispenser.validation.providers.ProviderValidator
import ru.yandex.dispenser.validation.providers.RequestedAmount
import ru.yandex.dispenser.validation.providers.RequestedResource
import ru.yandex.dispenser.validation.providers.SegmentedResourceKey

private val logger = KotlinLogging.logger {}

@Component
class AvatarsValidator(
    private val commonSettings: CommonSettings
): ProviderValidator {

    private val avatarsProviderKey = "avatars"
    private val resources = setOf(
        RequestedResource("replicas_x2_hdd", Units.Bytes.TEBIBYTES),
        RequestedResource("replicas_x2_ssd", Units.Bytes.TEBIBYTES),
        RequestedResource("replicas_x3_hdd", Units.Bytes.TEBIBYTES),
        RequestedResource("replicas_x3_ssd", Units.Bytes.TEBIBYTES),
        RequestedResource("lrc_x1_5_hdd", Units.Bytes.TEBIBYTES)
    )

    override fun prepareTestRequest(environment: Environment, campaignType: CampaignType): CreateQuotaRequest {
        val builder = commonSettings.baseQuotaRequest(providerName())
        val orders = commonSettings.orders(campaignType, OrdersSubset.FULL)
        var currentValue = 1L
        for (resource in resources) {
            for (orderId in orders) {
                builder
                    .addChange(CreateChange.builder()
                        .serviceKey(avatarsProviderKey)
                        .orderId(orderId)
                        .resourceKey(resource.key)
                        .amount(Amount.builder()
                            .value(currentValue++)
                            .unit(resource.unit)
                            .build())
                        .build())
            }
        }
        return builder.build()
    }

    override fun validateTestRequest(request: QuotaRequest,
                                     environment: Environment,
                                     campaignType: CampaignType): Result<String> {
        val requestedResources = mutableMapOf<Long, MutableMap<SegmentedResourceKey, RequestedAmount>>()
        request.changes.forEach{ change ->
            val key = SegmentedResourceKey(change.service.key, change.resource.key, change.segmentKeys)
            val amount = RequestedAmount(change.amount.value, Units.UNITS[change.amount.unit])
            requestedResources.computeIfAbsent(change.order.id) { mutableMapOf() }[key] = amount
        }
        val baseResources = mutableMapOf<Long, MutableMap<SegmentedResourceKey, RequestedAmount>>()
        request.baseResourceChanges.forEach { change ->
            val key = SegmentedResourceKey(change.baseResource.type.provider.key, change.baseResource.type.key,
                change.baseResource.segmentKeys)
            val amount = RequestedAmount(change.totalAmount.value, Units.UNITS[change.totalAmount.unit])
            baseResources.computeIfAbsent(change.bigOrder.id) { mutableMapOf() }[key] = amount
        }
        val expectedBaseResources = mutableMapOf<Long, MutableMap<SegmentedResourceKey, RequestedAmount>>()
        requestedResources.forEach { (orderId, resources) ->
            val requestedHddX2 = resources[SegmentedResourceKey(avatarsProviderKey,
                "replicas_x2_hdd", emptySet())]!!
            if (requestedHddX2.unit != Units.Bytes.BYTES) {
                logger.error("Unexpected HDD x2 unit: {}", requestedHddX2)
                return Result.failure(ErrorCollection.builder().addError("Unexpected HDD x2 unit").build())
            }
            val requestedSsdX2 = resources[SegmentedResourceKey(avatarsProviderKey,
                "replicas_x2_ssd", emptySet())]!!
            if (requestedSsdX2.unit != Units.Bytes.BYTES) {
                logger.error("Unexpected SSD x2 unit: {}", requestedSsdX2)
                return Result.failure(ErrorCollection.builder().addError("Unexpected SSD x2 unit").build())
            }
            val requestedHddX3 = resources[SegmentedResourceKey(avatarsProviderKey,
                "replicas_x3_hdd", emptySet())]!!
            if (requestedHddX3.unit != Units.Bytes.BYTES) {
                logger.error("Unexpected HDD x3 unit: {}", requestedHddX3)
                return Result.failure(ErrorCollection.builder().addError("Unexpected HDD x3 unit").build())
            }
            val requestedSsdX3 = resources[SegmentedResourceKey(avatarsProviderKey,
                "replicas_x3_ssd", emptySet())]!!
            if (requestedSsdX3.unit != Units.Bytes.BYTES) {
                logger.error("Unexpected SSD x3 unit: {}", requestedSsdX3)
                return Result.failure(ErrorCollection.builder().addError("Unexpected SSD x3 unit").build())
            }
            val requestedHddX15 = resources[SegmentedResourceKey(avatarsProviderKey,
                "lrc_x1_5_hdd", emptySet())]!!
            if (requestedHddX15.unit != Units.Bytes.BYTES) {
                logger.error("Unexpected HDD x1.5 unit: {}", requestedHddX15)
                return Result.failure(ErrorCollection.builder().addError("Unexpected HDD x1.5 unit").build())
            }
            expectedBaseResources.computeIfAbsent(orderId) { mutableMapOf() }[SegmentedResourceKey(avatarsProviderKey,
                "avatars:replicas_x2_hdd", emptySet())] = RequestedAmount(toGibibytes(requestedHddX2.amount),
                Units.Gibibytes.GIBIBYTES)
            expectedBaseResources.computeIfAbsent(orderId) { mutableMapOf() }[SegmentedResourceKey(avatarsProviderKey,
                "avatars:replicas_x2_ssd", emptySet())] = RequestedAmount(toGibibytes(requestedSsdX2.amount),
                Units.Gibibytes.GIBIBYTES)
            expectedBaseResources.computeIfAbsent(orderId) { mutableMapOf() }[SegmentedResourceKey(avatarsProviderKey,
                "avatars:replicas_x3_hdd", emptySet())] = RequestedAmount(toGibibytes(requestedHddX3.amount),
                Units.Gibibytes.GIBIBYTES)
            expectedBaseResources.computeIfAbsent(orderId) { mutableMapOf() }[SegmentedResourceKey(avatarsProviderKey,
                "avatars:replicas_x3_ssd", emptySet())] = RequestedAmount(toGibibytes(requestedSsdX3.amount),
                Units.Gibibytes.GIBIBYTES)
            expectedBaseResources.computeIfAbsent(orderId) { mutableMapOf() }[SegmentedResourceKey(avatarsProviderKey,
                "avatars:lrc_x1_5_hdd", emptySet())] = RequestedAmount(toGibibytes(requestedHddX15.amount),
                Units.Gibibytes.GIBIBYTES)
        }
        logger.info { "Expected ${providerName()} base resources: $expectedBaseResources" }
        logger.info { "Actual ${providerName()} base resources: $baseResources" }
        val match = baseResources == expectedBaseResources
        return if (match) {
            Result.success(providerName())
        } else {
            Result.failure(ErrorCollection.builder().addError("Base resource mismatch for ${providerName()}").build())
        }
    }

    override fun providerName() = "Avatars"

    private fun toGibibytes(bytes: Long) = bytes / (1024L * 1024L * 1024L)

}
