package com.iqser.red.service.persistence.management.v1.processor.service;

import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.ADD_UPDATE_DICTIONARY_TYPE;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.DELETE_DICTIONARY_TYPE;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_DICTIONARY_TYPES;

import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.ComponentDefinitionEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ComponentDefinitionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierTemplateRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinition;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionAddRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionUpdateRequest;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;

import io.micrometer.common.util.StringUtils;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;

@Service
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class ComponentDefinitionService {

    ComponentDefinitionPersistenceService componentDefinitionPersistenceService;
    DossierTemplateRepository dossierTemplateRepository;


    @PreAuthorize("hasAuthority('" + ADD_UPDATE_DICTIONARY_TYPE + "')")
    @Transactional
    public List<ComponentDefinition> createComponents(String dossierTemplateId, List<ComponentDefinitionAddRequest> componentDefinitionAddRequests) {

        validateDossierTemplateExists(dossierTemplateId);
        validateAddRequest(componentDefinitionAddRequests);

        List<ComponentDefinitionEntity> componentEntities = new ArrayList<>();
        componentDefinitionAddRequests.forEach(componentDefinitionAddRequest -> {
            Optional<String> optionalIdOfSoftDeleted = componentDefinitionPersistenceService.findIdByDossierTemplateIdAndTechnicalNameAndIsSoftDeleted(dossierTemplateId,
                                                                                                                                                       componentDefinitionAddRequest.getTechnicalName());
            ComponentDefinitionEntity entity;
            if (optionalIdOfSoftDeleted.isEmpty()) {
                entity = componentDefinitionPersistenceService.insert(componentDefinitionAddRequest, dossierTemplateId);
            } else {
                int rank = componentDefinitionPersistenceService.maxRankByDossierTemplateId(dossierTemplateId) + 1;
                entity = componentDefinitionPersistenceService.restoreComponent(dossierTemplateId, optionalIdOfSoftDeleted.get());
                entity.setDescription(componentDefinitionAddRequest.getDescription());
                entity.setRank(rank);
                entity = componentDefinitionPersistenceService.update(entity);
            }
            componentEntities.add(entity);
        });
        return componentEntities.stream()
                .map(componentDefinitionEntity -> MagicConverter.convert(componentDefinitionEntity, ComponentDefinition.class))
                .sorted(Comparator.comparing(ComponentDefinition::getRank))
                .toList();
    }


    private void validateAddRequest(List<ComponentDefinitionAddRequest> componentDefinitionAddRequests) {

        long nrOfComponentsWithEmptyTechnicalName = componentDefinitionAddRequests.stream()
                .filter(c -> StringUtils.isEmpty(c.getTechnicalName()))
                .count();
        if (nrOfComponentsWithEmptyTechnicalName != 0) {
            throw new BadRequestException("Technical name can not be empty for a component.");
        }
    }


    @PreAuthorize("hasAuthority('" + READ_DICTIONARY_TYPES + "')")
    public ComponentDefinition getComponentByDossierTemplateIdAndComponentId(String dossierTemplateId, String componentId) {

        validateDossierTemplateExists(dossierTemplateId);

        return MagicConverter.convert(componentDefinitionPersistenceService.findComponentByDossierTemplateIdAndComponentId(dossierTemplateId, componentId),
                                      ComponentDefinition.class);
    }


    @PreAuthorize("hasAuthority('" + READ_DICTIONARY_TYPES + "')")
    public List<ComponentDefinition> getComponentsByDossierTemplateId(String dossierTemplateId, boolean includeSoftDeleted) {

        validateDossierTemplateExists(dossierTemplateId);

        List<ComponentDefinitionEntity> componentDefinitionEntities;

        if (includeSoftDeleted) {
            componentDefinitionEntities = componentDefinitionPersistenceService.findComponentsByDossierTemplateId(dossierTemplateId);
        } else {
            componentDefinitionEntities = componentDefinitionPersistenceService.findByDossierTemplateIdAndNotSoftDeleted(dossierTemplateId);
        }

        return componentDefinitionEntities.stream()
                .map(entity -> MagicConverter.convert(entity, ComponentDefinition.class))
                .sorted(Comparator.comparingInt(ComponentDefinition::getRank))
                .collect(Collectors.toList());
    }


    @PreAuthorize("hasAuthority('" + ADD_UPDATE_DICTIONARY_TYPE + "')")
    @Transactional
    public List<ComponentDefinition> updateComponents(String dossierTemplateId, List<ComponentDefinitionUpdateRequest> componentDefinitionUpdateRequests) {

        validateDossierTemplateExists(dossierTemplateId);

        long nrOfComponentUpdateRequestsWithEmptyDisplayName = componentDefinitionUpdateRequests.stream()
                .filter(componentDefinitionUpdateRequest -> StringUtils.isEmpty(componentDefinitionUpdateRequest.getDisplayName()))
                .count();
        if (nrOfComponentUpdateRequestsWithEmptyDisplayName != 0) {
            throw new BadRequestException("Component display name can not be empty");
        }

        List<ComponentDefinitionEntity> componentEntities = new ArrayList<>();
        componentDefinitionUpdateRequests.forEach(componentDefinitionUpdateRequest -> {
            ComponentDefinitionEntity componentDefinitionEntity = componentDefinitionPersistenceService.findComponent(dossierTemplateId, componentDefinitionUpdateRequest.getId());
            componentDefinitionEntity.setDescription(componentDefinitionUpdateRequest.getDescription());
            componentDefinitionEntity.setDisplayName(componentDefinitionUpdateRequest.getDisplayName());
            componentEntities.add(componentDefinitionPersistenceService.update(componentDefinitionEntity));
        });
        return componentEntities.stream()
                .map(componentDefinitionEntity -> MagicConverter.convert(componentDefinitionEntity, ComponentDefinition.class))
                .sorted(Comparator.comparing(ComponentDefinition::getRank))
                .toList();
    }


    @PreAuthorize("hasAuthority('" + DELETE_DICTIONARY_TYPE + "')")
    @Transactional
    public List<ComponentDefinitionEntity> deleteComponents(String dossierTemplateId, List<String> componentIds) {

        validateDossierTemplateExists(dossierTemplateId);
        return componentDefinitionPersistenceService.softDeleteComponents(dossierTemplateId, componentIds, OffsetDateTime.now());
    }


    @PreAuthorize("hasAuthority('" + ADD_UPDATE_DICTIONARY_TYPE + "')")
    @Transactional
    public List<ComponentDefinition> restoreComponents(String dossierTemplateId, List<String> componentIds) {

        validateDossierTemplateExists(dossierTemplateId);

        List<ComponentDefinitionEntity> componentDefinitionEntities = componentDefinitionPersistenceService.restoreComponents(dossierTemplateId, componentIds);
        int rank = componentDefinitionPersistenceService.maxRankByDossierTemplateId(dossierTemplateId);
        for (ComponentDefinitionEntity componentDefinitionEntity : componentDefinitionEntities) {
            componentDefinitionEntity.setRank(++rank);
            componentDefinitionPersistenceService.update(componentDefinitionEntity);
        }
        return componentDefinitionEntities.stream()
                .map(componentDefinitionEntity -> MagicConverter.convert(componentDefinitionEntity, ComponentDefinition.class))
                .sorted(Comparator.comparing(ComponentDefinition::getRank))
                .toList();
    }


    @PreAuthorize("hasAuthority('" + ADD_UPDATE_DICTIONARY_TYPE + "')")
    @Transactional
    public List<ComponentDefinition> reorderComponents(String dossierTemplateId, List<String> componentIds) {

        validateDossierTemplateExists(dossierTemplateId);

        List<ComponentDefinition> orderedComponents = new ArrayList<>();
        List<ComponentDefinitionEntity> existingComponents = componentDefinitionPersistenceService.findByDossierTemplateIdAndNotSoftDeleted(dossierTemplateId);

        Map<String, ComponentDefinitionEntity> componentMap = existingComponents.stream()
                .collect(Collectors.toMap(ComponentDefinitionEntity::getId, component -> component));

        List<ComponentDefinitionEntity> remainingComponents = new ArrayList<>(existingComponents);

        int rank = 1;
        for (String componentId : componentIds) {
            ComponentDefinitionEntity component = componentMap.get(componentId);
            if (component != null) {
                component.setRank(rank++);
                componentDefinitionPersistenceService.update(component);
                orderedComponents.add(MagicConverter.convert(component, ComponentDefinition.class));
                remainingComponents.remove(component);
            }
        }

        // Continue updating ranks for remaining components
        for (ComponentDefinitionEntity component : remainingComponents) {
            component.setRank(rank++);
            componentDefinitionPersistenceService.update(component);
            orderedComponents.add(MagicConverter.convert(component, ComponentDefinition.class));
        }

        orderedComponents.sort(Comparator.comparing(ComponentDefinition::getRank));

        return orderedComponents;
    }


    private void validateDossierTemplateExists(String dossierTemplateId) {

        if (!dossierTemplateRepository.existsByIdAndNotDeleted(dossierTemplateId)) {
            throw new NotFoundException(String.format("DossierTemplate with Id %s not found.", dossierTemplateId));
        }
    }

}
