package ru.yandex.direct.web.entity.internalads.service;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import javax.annotation.ParametersAreNonnullByDefault;

import one.util.streamex.StreamEx;
import org.springframework.stereotype.Service;

import ru.yandex.direct.core.entity.client.model.ClientWithUsers;
import ru.yandex.direct.core.entity.client.service.ClientService;
import ru.yandex.direct.core.entity.internalads.model.InternalAdsManagerProductAccess;
import ru.yandex.direct.core.entity.internalads.model.InternalAdsProduct;
import ru.yandex.direct.core.entity.internalads.model.InternalAdsProductWithClient;
import ru.yandex.direct.core.entity.internalads.service.InternalAdsManagerProductAccessService;
import ru.yandex.direct.core.entity.internalads.service.InternalAdsProductService;
import ru.yandex.direct.dbutil.model.ClientId;
import ru.yandex.direct.rbac.RbacRole;
import ru.yandex.direct.validation.result.Defect;
import ru.yandex.direct.validation.result.ValidationResult;
import ru.yandex.direct.web.core.model.WebResponse;
import ru.yandex.direct.web.core.model.WebSuccessResponse;
import ru.yandex.direct.web.entity.internalads.model.AddProductAccessResponse;
import ru.yandex.direct.web.entity.internalads.model.GetUserResponse;
import ru.yandex.direct.web.entity.internalads.model.GetUsersResponse;
import ru.yandex.direct.web.entity.internalads.model.ModifyProductAccess;
import ru.yandex.direct.web.entity.internalads.model.RemoveProductAccess;
import ru.yandex.direct.web.entity.internalads.model.UpdateProductAccessResponse;
import ru.yandex.direct.web.entity.internalads.model.WebInternalAdProductAccess;
import ru.yandex.direct.web.entity.internalads.model.WebInternalAdUser;
import ru.yandex.direct.web.validation.kernel.ValidationResultConversionService;

import static org.assertj.core.util.Preconditions.checkState;
import static ru.yandex.direct.utils.FunctionalUtils.listToMap;
import static ru.yandex.direct.utils.FunctionalUtils.listToSet;
import static ru.yandex.direct.utils.FunctionalUtils.mapList;
import static ru.yandex.direct.web.entity.internalads.model.InternalAdProductConverter.toInternalAdsProductAccess;
import static ru.yandex.direct.web.entity.internalads.model.InternalAdProductConverter.toWebInternalAdProductAccess;
import static ru.yandex.direct.web.entity.internalads.model.InternalAdProductConverter.toWebInternalAdUser;

@Service
@ParametersAreNonnullByDefault
public class InternalAdUserWebService {
    private final InternalAdsProductService internalAdsProductService;
    private final ClientService clientService;
    private final ValidationResultConversionService validationResultConversionService;
    private final InternalAdProductValidationService internalAdProductValidationService;
    private final InternalAdsManagerProductAccessService internalAdsManagerProductAccessService;


    public InternalAdUserWebService(
            InternalAdsProductService internalAdsProductService,
            ClientService clientService,
            ValidationResultConversionService validationResultConversionService,
            InternalAdProductValidationService internalAdProductValidationService,
            InternalAdsManagerProductAccessService internalAdsManagerProductAccessService) {
        this.internalAdsProductService = internalAdsProductService;
        this.clientService = clientService;
        this.validationResultConversionService = validationResultConversionService;
        this.internalAdProductValidationService = internalAdProductValidationService;
        this.internalAdsManagerProductAccessService = internalAdsManagerProductAccessService;
    }


    /**
     * Получить список всех маркетологов, у которых не отозванны роли
     * (clients.role = "internal_ad_manager")
     */
    public GetUsersResponse getManagersList() {
        List<ClientWithUsers> internalAdManagers =
                clientService.getClientsWithUsersByRole(RbacRole.INTERNAL_AD_MANAGER);

        List<WebInternalAdUser> response = getWebInternalAdUsers(internalAdManagers);

        return new GetUsersResponse()
                .withResult(response);
    }


    /**
     * Получить информацию о конкретном менеджере
     *
     * @param clientId
     */
    public GetUserResponse getManager(ClientId clientId) {
        ClientWithUsers clientWithUsers = clientService.getClientWithUsers(clientId);

        checkState(clientWithUsers.getRole().equals(RbacRole.INTERNAL_AD_MANAGER),
                "requested client is not internal_ad_manager. clientId=%s, ", clientId.asLong());

        WebInternalAdUser result = getWebInternalAdUsers(Set.of(clientWithUsers))
                .stream().findFirst().orElseThrow(() ->
                        new IllegalStateException("can't fetch manager info. clientId: %s" + clientId.toString()));

        return new GetUserResponse()
                .withResult(result);
    }


    private List<WebInternalAdUser> getWebInternalAdUsers(Collection<ClientWithUsers> managerClients) {

        Set<ClientId> managerClientIds = listToSet(managerClients, cl -> ClientId.fromLong(cl.getClientId()));

        // получаем права на продукты
        Map<ClientId, List<InternalAdsManagerProductAccess>> clientsRelationsByManagerClientId =
                internalAdsManagerProductAccessService.getProductAccessForManagers(managerClientIds);

        // получаем дополнительную информацию о продуктах
        Set<ClientId> productIds = StreamEx.of(clientsRelationsByManagerClientId.values())
                .flatMap(list -> list.stream().map(InternalAdsManagerProductAccess::getProductClientId))
                .toSet();

        Map<ClientId, InternalAdsProduct> productsByClientId =
                listToMap(internalAdsProductService.getProducts(productIds), InternalAdsProduct::getClientId);

        Map<ClientId, ClientWithUsers> clientsByClientId = listToMap(
                clientService.massGetClientWithUsers(mapList(productIds, ClientId::asLong)),
                client -> ClientId.fromLong(client.getClientId()));

        return mapList(managerClients,
                manager -> {
                    List<WebInternalAdProductAccess> productsAccesses =
                            mapList(clientsRelationsByManagerClientId.getOrDefault(
                                    ClientId.fromLong(manager.getClientId()),
                                    Collections.emptyList()),
                                    access ->
                                            toWebInternalAdProductAccess(access,
                                                    InternalAdsProductWithClient.of(
                                                            productsByClientId.get(access.getProductClientId()),
                                                            clientsByClientId.get(access.getProductClientId()))));
                    return toWebInternalAdUser(manager, productsAccesses);
                });
    }


    /**
     * Обновить доступ к продукту для менеджера внутренней рекламы (маркетолога)
     * Если продукт отсутствует в списке, доступ к нему удалится
     *
     * @param clientId
     * @return возвращаем список всех продуктов доступных продуктов клиента
     */
    public WebResponse updateManagerProductAccess(ClientId clientId,
                                                  ModifyProductAccess modifyProductAccess) {

        ValidationResult<ModifyProductAccess, Defect> validationResult =
                internalAdProductValidationService.validateUpdateInternalAdProductAccess(clientId, modifyProductAccess);
        if (validationResult.hasAnyErrors()) {
            return validationResultConversionService.buildValidationResponse(validationResult);
        }

        internalAdsManagerProductAccessService.updateProductAccess(
                toInternalAdsProductAccess(clientId, modifyProductAccess));

        return new UpdateProductAccessResponse().withResult(
                getManagerAccessToSingleProduct(clientId, ClientId.fromLong(modifyProductAccess.getProductClientId())));
    }

    /**
     * Добавить доступ к указанным продуктам
     *
     * @return возвращаем добавленный продукт
     */
    public WebResponse addUserProductAccess(ClientId clientId,
                                            ModifyProductAccess productToAddAccess) {

        ValidationResult<ModifyProductAccess, Defect> validationResult =
                internalAdProductValidationService.validateAddInternalAdProductAccess(clientId, productToAddAccess);
        if (validationResult.hasAnyErrors()) {
            return validationResultConversionService.buildValidationResponse(validationResult);
        }

        internalAdsManagerProductAccessService.addProductAccess(
                toInternalAdsProductAccess(clientId, productToAddAccess));

        return new AddProductAccessResponse().withResult(
                getManagerAccessToSingleProduct(clientId, ClientId.fromLong(productToAddAccess.getProductClientId())));
    }

    private WebInternalAdProductAccess getManagerAccessToSingleProduct(
            ClientId managerClientId, ClientId productClientId) {
        InternalAdsManagerProductAccess access = internalAdsManagerProductAccessService
                .getManagerAccessToSingleProduct(managerClientId, productClientId);
        Objects.requireNonNull(access);

        InternalAdsProduct product = internalAdsProductService.getProduct(productClientId);
        ClientWithUsers productClient = clientService.getClientWithUsers(productClientId);
        InternalAdsProductWithClient productWithClient = InternalAdsProductWithClient.of(product, productClient);

        return toWebInternalAdProductAccess(access, productWithClient);
    }

    /**
     * Отозвать доступ маркетолога к указанным продуктам
     *
     * @return возвращаем множество объектов для которых фактически удалили доступ.
     */
    public WebResponse removeUserProductAccess(ClientId managerClientId, List<RemoveProductAccess> productIds) {

        ValidationResult<List<RemoveProductAccess>, Defect> validationResult =
                internalAdProductValidationService.validateRemoveInternalAdProductAccess(managerClientId, productIds);
        if (validationResult.hasAnyErrors()) {
            return validationResultConversionService.buildValidationResponse(validationResult);
        }

        Collection<ClientId> productClientIds = mapList(productIds,
                access -> ClientId.fromLong(access.getProductClientId()));
        internalAdsManagerProductAccessService.removeAccessToMultipleProducts(managerClientId, productClientIds);

        return new WebSuccessResponse();
    }
}
