package ru.yandex.qe.dispenser.ws.base_resources.impl;

import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.inject.Inject;

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.api.v1.DiService;
import ru.yandex.qe.dispenser.domain.Service;
import ru.yandex.qe.dispenser.domain.base_resources.BaseResourceType;
import ru.yandex.qe.dispenser.domain.dao.base_resources.BaseResourceTypeDao;
import ru.yandex.qe.dispenser.domain.dao.service.ServiceReader;
import ru.yandex.qe.dispenser.domain.hierarchy.Hierarchy;
import ru.yandex.qe.dispenser.domain.hierarchy.HierarchySupplier;
import ru.yandex.qe.dispenser.domain.i18n.LocalizableString;
import ru.yandex.qe.dispenser.domain.index.LongIndexBase;
import ru.yandex.qe.dispenser.domain.util.LocalizationUtils;
import ru.yandex.qe.dispenser.ws.base_resources.model.BaseResourceTypeDto;
import ru.yandex.qe.dispenser.ws.base_resources.model.BaseResourceTypesPageDto;
import ru.yandex.qe.dispenser.ws.base_resources.model.ExpandBaseResourceType;
import ru.yandex.qe.dispenser.ws.base_resources.model.SingleBaseResourceTypeDto;
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;

@Component
public class BaseResourceTypesManager {

    private final MessageSource errorMessageSource;
    private final HierarchySupplier hierarchySupplier;
    private final BaseResourceTypeDao baseResourceTypeDao;

    @Inject
    public BaseResourceTypesManager(@Qualifier("errorMessageSource") MessageSource errorMessageSource,
                                    HierarchySupplier hierarchySupplier,
                                    BaseResourceTypeDao baseResourceTypeDao) {
        this.errorMessageSource = errorMessageSource;
        this.hierarchySupplier = hierarchySupplier;
        this.baseResourceTypeDao = baseResourceTypeDao;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public Result<BaseResourceTypesPageDto, ErrorCollection<String, TypedError<String>>> getPage(
            Long from, Integer limit, Locale locale, Set<ExpandBaseResourceType> expand) {
        if (limit != null && (limit < 0 || limit > 100)) {
            return Result.failure(ErrorCollection.typedStringBuilder().addError("limit",
                    TypedError.badRequest(LocalizationUtils.resolveWithDefaultAsKey(errorMessageSource,
                            LocalizableString.of("invalid.limit.value"), locale))).build());
        }
        int actualLimit = limit == null ? 100 : limit;
        Set<ExpandBaseResourceType> actualExpand = expand != null ? expand : Set.of();
        List<BaseResourceType> baseResourceTypes = baseResourceTypeDao.getPage(from, actualLimit);
        BaseResourceTypesPageDto result = preparePageDto(baseResourceTypes, actualExpand);
        return Result.success(result);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public Result<SingleBaseResourceTypeDto, ErrorCollection<String, TypedError<String>>> getById(
            long id, Locale locale, Set<ExpandBaseResourceType> expand) {
        Optional<BaseResourceType> baseResourceTypeO = baseResourceTypeDao.getById(id);
        if (baseResourceTypeO.isEmpty()) {
            return Result.failure(ErrorCollection.typedStringBuilder().addError(
                    TypedError.notFound(LocalizationUtils.resolveWithDefaultAsKey(errorMessageSource,
                            LocalizableString.of("base.resource.type.not.found"), locale))).build());
        }
        Set<ExpandBaseResourceType> actualExpand = expand != null ? expand : Set.of();
        BaseResourceType baseResourceType = baseResourceTypeO.get();
        SingleBaseResourceTypeDto result = prepareSingleDto(baseResourceType, actualExpand);
        return Result.success(result);
    }

    private BaseResourceTypesPageDto preparePageDto(List<BaseResourceType> baseResourceTypes,
                                                    Set<ExpandBaseResourceType> expand) {
        ServiceReader serviceReader = hierarchySupplier.get().getServiceReader();
        Set<Long> serviceIds = baseResourceTypes.stream().map(BaseResourceType::getServiceId)
                .collect(Collectors.toSet());
        boolean expandServices = expand.contains(ExpandBaseResourceType.PROVIDERS);
        Set<Service> services = expandServices ? serviceReader.readByIds(serviceIds) : Set.of();
        Map<Long, DiService> servicesById = services.stream()
                .collect(Collectors.toMap(LongIndexBase::getId, Service::toView));
        List<BaseResourceTypeDto> baseResourceTypeDto = baseResourceTypes.stream()
                .map(t -> new BaseResourceTypeDto(t.getId(), t.getKey(), t.getName(), t.getServiceId(),
                        t.getResourceType(), t.getResourceType().getBaseUnit()))
                .collect(Collectors.toList());
        return new BaseResourceTypesPageDto(baseResourceTypeDto, servicesById,
                baseResourceTypes.isEmpty() ? null : baseResourceTypes.get(baseResourceTypes.size() - 1).getId());
    }

    private SingleBaseResourceTypeDto prepareSingleDto(BaseResourceType baseResourceType,
                                                       Set<ExpandBaseResourceType> expand) {
        Hierarchy hierarchy = hierarchySupplier.get();
        ServiceReader serviceReader = hierarchy.getServiceReader();
        boolean expandServices = expand.contains(ExpandBaseResourceType.PROVIDERS);
        Optional<Service> serviceO = expandServices
                ? Optional.of(serviceReader.read(baseResourceType.getServiceId())) : Optional.empty();
        DiService serviceDto = serviceO.map(Service::toView).orElse(null);
        BaseResourceTypeDto baseResourceTypeDto = new BaseResourceTypeDto(baseResourceType.getId(),
                baseResourceType.getKey(), baseResourceType.getName(), baseResourceType.getServiceId(),
                baseResourceType.getResourceType(), baseResourceType.getResourceType().getBaseUnit());
        return new SingleBaseResourceTypeDto(baseResourceTypeDto, serviceDto);
    }

}
