package dp.kotul.templategroupsbackend.facade

import dp.kotul.solrapimodel.solrsearch.AssetType
import dp.kotul.solrapimodel.solrsearch.SolrUtils
import dp.kotul.solrapimodel.solrsearch.TemplateType
import dp.kotul.starter.filter.RequestCorrelation
import dp.kotul.starter.service.AuthorizationService
import dp.kotul.templategroupsbackend.client.SolrApiClient
import dp.kotul.templategroupsbackend.exception.NotIncludedTemplateVariableException
import dp.kotul.templategroupsbackend.exception.TransitiveTemplateVariableDependencyException
import dp.kotul.templategroupsbackend.helper.SolrSearchHelper
import dp.kotul.templategroupsbackend.model.entity.Template
import dp.kotul.templategroupsbackend.model.entity.TemplateGroup
import dp.kotul.templategroupsbackend.model.entity.TemplateGroupVariable
import dp.kotul.templategroupsbackend.model.entity.TemplateVariable
import dp.kotul.templategroupsbackend.model.entity.VariableSource
import dp.kotul.templategroupsbackend.model.entity.VariableSourceType.TEMPLATE
import dp.kotul.templategroupsbackend.model.request.TemplateGroupRequest
import dp.kotul.templategroupsbackend.model.request.TemplateVariableRequest
import dp.kotul.templategroupsbackend.model.request.UpdateTemplateGroupSolr
import dp.kotul.templategroupsbackend.model.request.UpdateTemplateGroupsSolrRequest
import dp.kotul.templategroupsbackend.model.response.TemplateDetailResponse
import dp.kotul.templategroupsbackend.model.response.TemplateGroupDetailResponse
import dp.kotul.templategroupsbackend.model.response.TemplateGroupResponse
import dp.kotul.templategroupsbackend.model.response.TemplateGroupVariableDetailResponse
import dp.kotul.templategroupsbackend.model.response.TemplateOrder
import dp.kotul.templategroupsbackend.model.response.TemplateResponse
import dp.kotul.templategroupsbackend.model.response.TemplateVariableResponse
import dp.kotul.templategroupsbackend.model.response.VariableSourceResponse
import dp.kotul.templategroupsbackend.model.solrsearch.SolrSearchRequest
import dp.kotul.templategroupsbackend.model.solrsearch.tamplategroup.TemplateGroupSolrResponse
import dp.kotul.templategroupsbackend.model.solrsearch.template.TemplateSolrResponse
import dp.kotul.templategroupsbackend.repository.TemplateGroupRepository
import dp.kotul.templategroupsbackend.repository.TemplateGroupVariableRepository
import dp.kotul.templategroupsbackend.repository.TemplateRepository
import dp.kotul.templategroupsbackend.repository.TemplateVariableRepository
import dp.kotul.templategroupsbackend.repository.VariableSourceRepository
import dp.kotul.templategroupsbackend.util.firstOrWrongTemplateGroupSetupException
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import java.time.Instant

interface TemplateGroupFacade {
    fun getTemplateGroups(): List<TemplateGroup>
    fun getTemplateGroupsSolr(solrSearchRequest: SolrSearchRequest): TemplateGroupSolrResponse?
    fun getTemplateGroup(templateGroupId: Int, laeId: Int?): TemplateGroupResponse?
    fun getTemplateGroupDetail(templateGroupId: Int, laeId: Int?): TemplateGroupDetailResponse?
    fun createTemplateGroup(templateGroupRequest: TemplateGroupRequest): TemplateGroup
    fun updateTemplateGroup(templateGroupRequest: TemplateGroupRequest): TemplateGroup?
}

@Component
@Transactional
class TemplateGroupFacadeImpl(
        val authService: AuthorizationService,
        val templateGroupRepository: TemplateGroupRepository,
        val templateRepository: TemplateRepository,
        val templateVariableRepository: TemplateVariableRepository,
        val templateGroupVariableRepository: TemplateGroupVariableRepository,
        val variableSourceRepository: VariableSourceRepository,
        val solrSearchHelper: SolrSearchHelper,
        val solrApiClient: SolrApiClient
) : TemplateGroupFacade {

    override fun getTemplateGroups(): List<TemplateGroup> {
        val user = authService.getUser()
        return templateGroupRepository.getTemplateGroupByComId(user.comId)
    }

    override fun getTemplateGroupsSolr(solrSearchRequest: SolrSearchRequest): TemplateGroupSolrResponse? {
        val user = authService.getUser()
        val languageId = solrSearchRequest.laeId ?: user.laeId

        return solrSearchHelper.getSolrTemplateGroupsResponse(languageId, solrSearchRequest)
    }

    override fun getTemplateGroup(templateGroupId: Int, laeId: Int?): TemplateGroupResponse? {
        val user = authService.getUser()
        val accessToken = authService.getValidAuthorizationHeader()
        val correlationId = RequestCorrelation.getId()
        val languageId = laeId ?: user.laeId

        val templateGroup = templateGroupRepository.findByIdOrNull(templateGroupId) ?: return null

        if (templateGroup.templates.isEmpty()) {
            return TemplateGroupResponse(templateGroupId = templateGroup.templateGroupId, name = templateGroup.name, templates = emptyList())
        }

        val solrTemplatesResponse = solrSearchHelper.getSolrTemplatesResponse(accessToken, correlationId, languageId, templateGroup.templates) ?: return null

        checkSolrTemplateChanges(templateGroup, solrTemplatesResponse, languageId)

        val templateVariablesWithoutSourceTypeTemplate = templateGroup.templates.flatMap { template ->
            template.templateVariables.filter { it.variableSource?.sourceType != TEMPLATE }
        }

        val templateGroupVariables = templateGroup.templateGroupVariables.map { it }

        val templateGroupResponse = TemplateGroupResponse(templateGroupId = templateGroup.templateGroupId, name = templateGroup.name,
                templates = solrTemplatesResponse.templates.map { templateSolr ->
                    val template = templateGroup.templates.first { templateSolr.id == SolrUtils.getUniqueSolrDocumentId(it.templateExtId, AssetType.TEMPLATE, it.templateType.name, languageId) }
                    var sortOrderCounter = 0

                    TemplateResponse(
                            templateId = template.templateId,
                            templateExtId = template.templateExtId,
                            name = templateSolr.name,
                            templateType = template.templateType,
                            thumbnailRef = templateSolr.thumbnailRef,
                            lowResRef = templateSolr.lowResRef,
                            createdDate = templateSolr.createdDate,
                            sortOrder = template.sortOrder ?: 0,
                            templateVariables = templateSolr.templateVariables.map { templateVariableSolr ->
                                val templateVariable = template.templateVariables.first { templateVariableSolr.templateVariableExtId == it.templateVariableExtId }

                                var sourceName: String? = null
                                var sourceFieldName: String? = null

                                if (templateVariable.variableSource?.sourceType == TEMPLATE) {
                                    val templateVariableForSourceTypeTemplate = templateVariablesWithoutSourceTypeTemplate.first{ templateVariableWithoutSourceTypeTemplate ->
                                        templateVariable.variableSource?.sourceId == templateVariableWithoutSourceTypeTemplate.template.templateId.toString() &&
                                                templateVariable.variableSource!!.sourceFieldId == templateVariableWithoutSourceTypeTemplate.templateVariableId.toString()
                                    }

                                    val solrTemplate = solrTemplatesResponse.templates.first { it.id == SolrUtils.getUniqueSolrDocumentId(templateVariableForSourceTypeTemplate.template.templateExtId,
                                                AssetType.TEMPLATE, templateVariableForSourceTypeTemplate.template.templateType.name, languageId) }

                                    sourceName = solrTemplate.name

                                    val solrTemplateVariable = solrTemplate.templateVariables.first { it.templateVariableExtId == templateVariableForSourceTypeTemplate.templateVariableExtId }
                                    sourceFieldName = solrTemplateVariable.name
                                }

                                val alreadyExistTemplateGroupVariable = templateGroupVariables.firstOrNull { templateSolr.id == SolrUtils.getUniqueSolrDocumentId(it.templateVariable.template.templateExtId,
                                        AssetType.TEMPLATE, it.templateVariable.template.templateType.name, languageId) && it.templateVariable.templateVariableExtId == templateVariableSolr.templateVariableExtId }

                                if (templateVariableSolr.mandatoryExt && alreadyExistTemplateGroupVariable == null && templateVariable.variableSource?.sourceType != TEMPLATE) {
                                    val templateGroupVariable = TemplateGroupVariable(
                                        templateGroup = templateGroup,
                                        name = templateVariableSolr.name,
                                        defaultValue = templateVariableSolr.defaultValue,
                                        mandatory = true,
                                        sortOrder = sortOrderCounter++,
                                        templateVariable = templateVariable)

                                    templateGroup.templateGroupVariables.add(templateGroupVariable)
                                }

                                TemplateVariableResponse(
                                        templateVariableId = templateVariable.templateVariableId,
                                        templateVariableExtId = templateVariable.templateVariableExtId,
                                        name = templateVariableSolr.name,
                                        templateVariableExtType = templateVariableSolr.templateVariableExtType,
                                        included = if(templateVariableSolr.mandatoryExt) true else templateVariable.included,
                                        defaultValue = templateVariableSolr.defaultValue,
                                        mandatoryExt = templateVariableSolr.mandatoryExt,
                                        variableSource = if (templateVariable.variableSource != null) {
                                            VariableSourceResponse(
                                                    variableSourceId = templateVariable.variableSource!!.variableSourceId,
                                                    sourceId = templateVariable.variableSource!!.sourceId,
                                                    sourceType = templateVariable.variableSource!!.sourceType,
                                                    sourceFieldId = templateVariable.variableSource!!.sourceFieldId,
                                                    sourceName = sourceName,
                                                    sourceFieldName = sourceFieldName)
                                        } else {
                                            null
                                        }
                                )
                            })
                }.sortedWith(compareBy( { TemplateOrder.BY_TEMPLATE_TYPE.templateTypesOrder.indexOf(it.getType()) }, { it.sortOrder } )))

        templateGroupRepository.save(templateGroup)

        return templateGroupResponse
    }

    override fun getTemplateGroupDetail(templateGroupId: Int, laeId: Int?): TemplateGroupDetailResponse? {
        val user = authService.getUser()

        val accessToken = authService.getValidAuthorizationHeader()
        val correlationId = RequestCorrelation.getId()
        val languageId = laeId ?: user.laeId

        val templateGroup = templateGroupRepository.findByIdOrNull(templateGroupId) ?: return null

        if (templateGroup.templates.isEmpty()) {
            return TemplateGroupDetailResponse(templateGroupId = templateGroup.templateGroupId, name = templateGroup.name,
                    templates = emptyList())
        }

        val templateGroupVariables = templateGroupVariableRepository.getTemplateGroupVariablesByTemplateGroup(templateGroup)

        val solrTemplatesResponse = solrSearchHelper.getSolrTemplatesResponse(accessToken, correlationId, languageId, templateGroup.templates) ?: return null

        return TemplateGroupDetailResponse(templateGroupId = templateGroup.templateGroupId, name = templateGroup.name,
                templates = solrTemplatesResponse.templates.map { templateSolr ->
                    val template = templateGroup.templates.firstOrWrongTemplateGroupSetupException { templateSolr.id == SolrUtils.getUniqueSolrDocumentId(it.templateExtId,
                        AssetType.TEMPLATE, it.templateType.name, languageId) }

                    TemplateDetailResponse(
                            templateId = template.templateId,
                            templateExtId = template.templateExtId,
                            name = templateSolr.name,
                            templateType = template.templateType,
                            thumbnailRef = templateSolr.thumbnailRef,
                            lowResRef = templateSolr.lowResRef,
                            sortOrder = template.sortOrder ?: 0,
                            templateGroupVariables = templateGroupVariables.map { templateGroupVariable ->
                                //includedInCurrentTemplate also for template variables with source type TEMPLATE
                                val isIncludedInCurrentTemplate = template.templateVariables.any {
                                    (it.variableSource?.sourceType == TEMPLATE && templateGroupVariable.templateVariable.templateVariableId.toString() == it.variableSource?.sourceFieldId)
                                            || it.templateVariableId == templateGroupVariable.templateVariable.templateVariableId
                                }

                                val templateVariableSolr = solrTemplatesResponse.templates.firstOrWrongTemplateGroupSetupException {
                                    it.id == SolrUtils.getUniqueSolrDocumentId(templateGroupVariable.templateVariable.template.templateExtId, AssetType.TEMPLATE,
                                        templateGroupVariable.templateVariable.template.templateType.name, languageId) }
                                        .templateVariables.firstOrWrongTemplateGroupSetupException { it.templateVariableExtId == templateGroupVariable.templateVariable.templateVariableExtId }

                                var sourceFieldValue: String? = null

                                val templateVariableMaxLength = getTemplateVariablesWithMaxLength(templateGroupVariable, templateGroup, solrTemplatesResponse, languageId)

                                TemplateGroupVariableDetailResponse(
                                        templateGroupVariableId = templateGroupVariable.templateGroupVariableId,
                                        templateVariableId = templateGroupVariable.templateVariable.templateVariableId,
                                        name = templateGroupVariable.name!!,
                                        sortOrder = templateGroupVariable.sortOrder!!,
                                        defaultValue = templateGroupVariable.defaultValue,
                                        mandatory = templateGroupVariable.mandatory,
                                        maxLength = if(templateVariableMaxLength != 0) templateVariableMaxLength else templateVariableSolr.maxLength,
                                        includedInCurrentTemplate = isIncludedInCurrentTemplate,
                                        templateVariableExtType = templateVariableSolr.templateVariableExtType,
                                        value = sourceFieldValue ?: templateGroupVariable.defaultValue)
                            }.sortedBy { it.sortOrder })
                }.sortedWith(compareBy( { TemplateOrder.BY_TEMPLATE_TYPE.templateTypesOrder.indexOf(it.getType()) }, { it.sortOrder } )))
    }

    override fun createTemplateGroup(templateGroupRequest: TemplateGroupRequest): TemplateGroup {
        val user = authService.getUser()

        val accessToken = authService.getValidAuthorizationHeader()
        val correlationId = RequestCorrelation.getId()

        val templateGroup = TemplateGroup(
                name = templateGroupRequest.name,
                comId = user.comId,
                usrId = user.usrId,
                created = Instant.now(),
                lastModified = Instant.now())

        val templateTypes: MutableSet<TemplateType> = mutableSetOf()

        val templates = templateGroupRequest.templates.map { templateRequest ->
            val template = Template(
                    templateExtId = templateRequest.templateExtId,
                    templateType = templateRequest.templateType,
                    templateGroup = templateGroup,
                    sortOrder = templateRequest.sortOrder)

            template.templateVariables.addAll(templateRequest.templateVariables.map {
                TemplateVariable(
                        templateVariableExtId = it.templateVariableExtId,
                        included = false,
                        template = template,
                        variableSource = null)
            })
            templateTypes.add(template.templateType)
            template
        }

        templateGroup.templates.addAll(templates)
        val savedTemplateGroup = templateGroupRepository.save(templateGroup)

        solrApiClient.updateSolrTemplateGroups(accessToken, correlationId, UpdateTemplateGroupsSolrRequest(
                comId = user.comId,
                usrId = user.usrId,
                unitIds = listOf(),
                templateGroups = listOf(UpdateTemplateGroupSolr(
                        name = savedTemplateGroup.name,
                        templateGroupId = savedTemplateGroup.templateGroupId,
                        templateCount = templates.count(),
                        templateTypes = templateTypes,
                        created = savedTemplateGroup.created,
                        modified = savedTemplateGroup.lastModified))
        ))

        return savedTemplateGroup
    }

    override fun updateTemplateGroup(templateGroupRequest: TemplateGroupRequest): TemplateGroup? {
        val user = authService.getUser()

        val accessToken = authService.getValidAuthorizationHeader()
        val correlationId = RequestCorrelation.getId()

        val templateGroupDb = templateGroupRepository.findByIdOrNull(templateGroupRequest.templateGroupId!!) ?: return null

        val existingTemplateVariableIds = deleteNotIncludedTemplateGroupVariables(templateGroupDb, templateGroupRequest)

        deleteTemplatesAfterChanges(templateGroupDb, templateGroupRequest)

        val templateGroup = TemplateGroup(
                templateGroupId = templateGroupRequest.templateGroupId,
                name = templateGroupRequest.name,
                comId = templateGroupDb.comId,
                usrId = templateGroupDb.usrId,
                created = templateGroupDb.created,
                lastModified = Instant.now())

        val templateTypes: MutableSet<TemplateType> = mutableSetOf()

        val transitiveTemplateVariables: MutableList<TemplateVariableRequest> = mutableListOf()
        val notIncludedTemplateVariables: MutableList<TemplateVariableRequest> = mutableListOf()
        val templateVariablesWithSourceTypeTemplate = templateGroupRequest.templates.flatMap { template ->
            template.templateVariables.filter { it.variableSource?.sourceType == TEMPLATE }
        }

        val templateGroupVariables = templateGroupDb.templateGroupVariables

        var sortOrderCounter = 0
        val templates = templateGroupRequest.templates.map { templateRequest ->

            val template = Template(
                    templateId = templateRequest.templateId ?: 0,
                    templateExtId = templateRequest.templateExtId,
                    templateType = templateRequest.templateType,
                    templateGroup = templateGroup,
                    sortOrder = templateRequest.sortOrder)

            templateTypes.add(template.templateType)

            if (templateRequest.templateId == null) {
                template.templateVariables.addAll(templateRequest.templateVariables.map { templateVariableRequest ->
                    TemplateVariable(
                            templateVariableExtId = templateVariableRequest.templateVariableExtId,
                            included = false,
                            template = template,
                            variableSource = null)
                })
            } else {
                template.templateVariables.addAll(templateRequest.templateVariables.map { templateVariableRequest ->
                    val templateVariable = TemplateVariable(
                            templateVariableId = templateVariableRequest.templateVariableId!!,
                            templateVariableExtId = templateVariableRequest.templateVariableExtId,
                            included = templateVariableRequest.included,
                            template = template,
                            variableSource = null)

                    if (!templateVariableRequest.included) {
                        templateVariablesWithSourceTypeTemplate.forEach {
                            if (it.variableSource!!.sourceId == templateRequest.templateId.toString() && it.variableSource!!.sourceFieldId == templateVariableRequest.templateVariableId.toString()) {
                                notIncludedTemplateVariables.add(templateVariableRequest)
                            }
                        }
                    }

                    val variableSource = if (templateVariableRequest.variableSource != null) {
                        if (templateVariableRequest.variableSource!!.sourceType == TEMPLATE) {
                            templateVariablesWithSourceTypeTemplate.forEach {
                                if (it.variableSource!!.sourceId == templateRequest.templateId.toString() && it.variableSource!!.sourceFieldId == templateVariableRequest.templateVariableId.toString()) {
                                    transitiveTemplateVariables.add(it)
                                }
                            }
                        }

                        VariableSource(
                                variableSourceId = templateVariableRequest.variableSource!!.variableSourceId,
                                sourceId = templateVariableRequest.variableSource!!.sourceId,
                                sourceType = templateVariableRequest.variableSource!!.sourceType,
                                sourceFieldId = templateVariableRequest.variableSource!!.sourceFieldId,
                                templateVariable = templateVariable)
                    } else {
                        null
                    }

                    templateVariable.variableSource = variableSource

                    if(!templateVariableRequest.mandatoryExt) {
                        val templateVariableMandatoryExt = templateVariablesWithSourceTypeTemplate.firstOrNull { it.variableSource!!.sourceId == templateRequest.templateId.toString() &&
                                it.variableSource!!.sourceFieldId == templateVariableRequest.templateVariableId.toString() && it.mandatoryExt }

                        if(templateVariableMandatoryExt != null && templateVariableRequest.included) {
                            val templateGroupVariable = templateGroupVariables.first { it.templateVariable.templateVariableId == templateVariableRequest.templateVariableId }
                            templateGroupVariable.mandatory = true
                        }
                    }

                    if (!existingTemplateVariableIds.contains(templateVariable.templateVariableId) && templateVariable.included && variableSource?.sourceType != TEMPLATE) {
                        val templateGroupVariable = TemplateGroupVariable(
                                templateGroup = templateGroup,
                                name = templateVariableRequest.name,
                                defaultValue = templateVariableRequest.defaultValue,
                                mandatory = templateVariableRequest.mandatoryExt,
                                sortOrder = sortOrderCounter++,
                                templateVariable = templateVariable)

                        templateGroup.templateGroupVariables.add(templateGroupVariable)
                    }
                    templateVariable
                })
            }
            template
        }

        if (notIncludedTemplateVariables.isNotEmpty()) {
            throw NotIncludedTemplateVariableException(notIncludedTemplateVariables.map { it.templateVariableId!! })
        }

        if (transitiveTemplateVariables.isNotEmpty()) {
            throw TransitiveTemplateVariableDependencyException(transitiveTemplateVariables.map { it.templateVariableId!! })
        }

        templateGroup.templates.addAll(templates)
        val updatedTemplateGroup = templateGroupRepository.save(templateGroup)

        solrApiClient.updateSolrTemplateGroups(accessToken, correlationId, UpdateTemplateGroupsSolrRequest(
                comId = user.comId,
                usrId = user.usrId,
                unitIds = listOf(),
                templateGroups = listOf(UpdateTemplateGroupSolr(
                        name = updatedTemplateGroup.name,
                        templateGroupId = updatedTemplateGroup.templateGroupId,
                        templateCount = templates.count(),
                        templateTypes = templateTypes,
                        modified = updatedTemplateGroup.lastModified))
        ))

        return updatedTemplateGroup
    }

    /**
     *during get template group
     *delete templates from template group if there are changes in solr
     *delete template variables with references to variable sources and template group variables if there are changes in solr
     *add template variables if there are changes in solr
     */
    private fun checkSolrTemplateChanges(templateGroup: TemplateGroup, solrTemplatesResponse: TemplateSolrResponse, laeId: Int) {
        val templateIdsSolr = solrTemplatesResponse.templates.map { it.id }

        val templatesToDelete = templateGroup.templates.filter { !templateIdsSolr.contains(SolrUtils.getUniqueSolrDocumentId(it.templateExtId,
            AssetType.TEMPLATE, it.templateType.name, laeId)) }

        if (templatesToDelete.isNotEmpty()) {
            templateGroup.templates.removeAll(templatesToDelete)
            templateRepository.deleteTemplatesByTemplateIdIn(templatesToDelete.map { it.templateId })
        }

        val variableSourcesWithSourceTypeTemplate = getVariableSourcesWithSourceTypeTemplate(templateGroup)
        val templateVariablesWithVariableSource = getTemplateVariablesWithVariableSource(templateGroup)

        solrTemplatesResponse.templates.forEach { solrTemplate ->
            val template = templateGroup.templates.first { SolrUtils.getUniqueSolrDocumentId(it.templateExtId, AssetType.TEMPLATE, it.templateType.name, laeId) == solrTemplate.id }

            val templateVariableExtIds = template.templateVariables.map { it.templateVariableExtId }
            val templateVariableExtIdsToDelete = templateVariableExtIds.minus(solrTemplate.templateVariables.map { it.templateVariableExtId })
            val templateVariablesToDelete = templateVariableExtIdsToDelete.map { templateVariableExtId ->
                template.templateVariables.first { templateVariableExtId == it.templateVariableExtId }
            }

            val templateGroupVariablesToDelete = getTemplateGroupVariablesToDelete(templateGroup, templateVariablesToDelete)

            val variableSourcesToDelete = deleteVariableSourcesAfterChanges(templateVariablesToDelete, templateVariablesWithVariableSource, variableSourcesWithSourceTypeTemplate)

            templateGroupVariableRepository.deleteInBatch(templateGroupVariablesToDelete)
            templateVariableRepository.deleteInBatch(templateVariablesToDelete)
            variableSourceRepository.deleteInBatch(variableSourcesToDelete)

            val templateVariableExtIdsSolr = solrTemplate.templateVariables.map { it.templateVariableExtId }
            val templateVariableExtIdsToAdd = templateVariableExtIdsSolr.minus(template.templateVariables.map { it.templateVariableExtId })

            template.templateVariables.addAll(templateVariableExtIdsToAdd.map {
                TemplateVariable(
                        templateVariableExtId = it,
                        included = false,
                        template = template,
                        variableSource = null)
            })
        }
        templateGroupRepository.save(templateGroup)
    }

    /**
    *during update template group
    *delete only references to template group variables that are not included
    *@return existing preserving template group variable ids
    */
    private fun deleteNotIncludedTemplateGroupVariables(templateGroupDb: TemplateGroup, templateGroupRequest: TemplateGroupRequest): List<Int> {

        val notIncludedTemplateVariableIds = templateGroupRequest.templates.flatMap { template ->
            template.templateVariables.filter { !it.included || it.variableSource?.sourceType == TEMPLATE }
        }.mapNotNull { it.templateVariableId }

        val notIncludedTemplateGroupVariables = templateGroupDb.templateGroupVariables.filter { notIncludedTemplateVariableIds.contains(it.templateVariable.templateVariableId) }

        templateGroupVariableRepository.deleteInBatch(notIncludedTemplateGroupVariables)

        return templateGroupDb.templateGroupVariables.filterNot { notIncludedTemplateVariableIds.contains(it.templateVariable.templateVariableId) }
            .map { it.templateVariable.templateVariableId }
    }

    /**
    *during update template group
    *check and delete templates after removing from template group setup
    */
    private fun deleteTemplatesAfterChanges(templateGroupDb: TemplateGroup, templateGroupRequest: TemplateGroupRequest) {
        val templateIdsDb = templateGroupDb.templates.map { it.templateId }
        val templateIdsToDelete = templateIdsDb.minus(templateGroupRequest.templates.mapNotNull { it.templateId })

        if (templateIdsToDelete.isNotEmpty()) {
            val variableSourcesWithSourceTypeTemplate = getVariableSourcesWithSourceTypeTemplate(templateGroupDb)
            val templateVariablesWithVariableSource = getTemplateVariablesWithVariableSource(templateGroupDb)

            val templatesToDelete = templateIdsToDelete.map { templateIdToDelete ->
                templateGroupDb.templates.first { templateIdToDelete == it.templateId }
            }

            val templateVariablesToDelete = templatesToDelete.flatMap { it.templateVariables }
            val templateGroupVariablesToDelete = getTemplateGroupVariablesToDelete(templateGroupDb, templateVariablesToDelete)

            val variableSourcesToDelete = deleteVariableSourcesAfterChanges(templateVariablesToDelete, templateVariablesWithVariableSource, variableSourcesWithSourceTypeTemplate)

            templateGroupVariableRepository.deleteInBatch(templateGroupVariablesToDelete)
            templateVariableRepository.deleteInBatch(templateVariablesToDelete)
            templateRepository.deleteInBatch(templatesToDelete)
            variableSourceRepository.deleteInBatch(variableSourcesToDelete)

            setVariableSourcesForTemplateGroupRequest(templateGroupRequest, variableSourcesToDelete)
        }
    }

    /**
     *during get template group, update template group
     *update variable sources of type another template to null after removing related template variable
     *@return variable sources to delete
     */
    private fun deleteVariableSourcesAfterChanges(templateVariablesToDelete: List<TemplateVariable>, templateVariablesWithVariableSource: List<TemplateVariable>, variableSourcesWithSourceTypeTemplate: List<VariableSource>): List<VariableSource> {
        val variableSourcesTypeTemplateToDelete = templateVariablesToDelete.flatMap { templateVariableToDelete ->
            variableSourcesWithSourceTypeTemplate.filter { it.sourceId == templateVariableToDelete.template.templateId.toString()
                    && it.sourceFieldId == templateVariableToDelete.templateVariableId.toString()
            }
        }

        val templateVariablesToUpdateBeforeDelete = variableSourcesTypeTemplateToDelete.flatMap { variableSource ->
            templateVariablesWithVariableSource.filter { variableSource.variableSourceId == it.variableSource?.variableSourceId
            }
        }

        //update reference for variables of source type TEMPLATE before deleting
        templateVariablesToUpdateBeforeDelete.forEach { it.variableSource = null; it.included = false }
        templateVariableRepository.saveAll(templateVariablesToUpdateBeforeDelete)

        return templateVariablesToDelete.mapNotNull { it.variableSource } + variableSourcesTypeTemplateToDelete
    }

    /**
     *during update template group
     *set variable sources of type another template to null after removing related template variable
     */
    private fun setVariableSourcesForTemplateGroupRequest(templateGroupRequest: TemplateGroupRequest, variableSourcesToDelete: List<VariableSource>) {
        val templateVariablesRequestToUpdate = templateGroupRequest.templates.flatMap { templateRequest ->
            variableSourcesToDelete.flatMap { variableSourceToDelete ->
                templateRequest.templateVariables.filter { variableSourceToDelete.templateVariable!!.templateVariableId == it.templateVariableId } }
        }

        templateVariablesRequestToUpdate.forEach { it.variableSource = null; it.included = false }
    }

    private fun getTemplateGroupVariablesToDelete(templateGroupDb: TemplateGroup, templateVariablesToDelete: List<TemplateVariable>): List<TemplateGroupVariable> {
        val templateVariableIdsToDelete = templateVariablesToDelete.map { it.templateVariableId }
        return templateGroupDb.templateGroupVariables.filter { templateVariableIdsToDelete.contains(it.templateVariable.templateVariableId) }
    }

    private fun getVariableSourcesWithSourceTypeTemplate(templateGroupDb: TemplateGroup): List<VariableSource> {
        return templateGroupDb.templates.flatMap { template ->
            template.templateVariables.filter { it.variableSource?.sourceType == TEMPLATE }.mapNotNull { it.variableSource }
        }
    }

    private fun getTemplateVariablesWithVariableSource(templateGroupDb: TemplateGroup): List<TemplateVariable> {
        return templateGroupDb.templates.flatMap { template ->
            template.templateVariables.filter { it.variableSource != null }
        }
    }

    /**
     *during get template group detail
     *get template variables with another template references and filter minimum max length
     *@return min max length of passed template variable
     */
    private fun getTemplateVariablesWithMaxLength(templateGroupVariable: TemplateGroupVariable, templateGroup: TemplateGroup, solrTemplatesResponse: TemplateSolrResponse, languageId: Int): Int {
        val templateVariablesWithSourceTemplate = templateGroup.templates.flatMap { template ->
            template.templateVariables.filter { it.variableSource?.sourceType == TEMPLATE }
        }

        val currentTemplateVariableWithVariablesSourceTypeTemplate = templateVariablesWithSourceTemplate.filter {
            it.variableSource?.sourceId == templateGroupVariable.templateVariable.template.templateId.toString()
                && it.variableSource?.sourceFieldId == templateGroupVariable.templateVariable.templateVariableId.toString()
        } + templateGroupVariable.templateVariable

        val templateVariablesMaxLengths = currentTemplateVariableWithVariablesSourceTypeTemplate.map { currentTemplateVariable ->
            val currentTemplateVariableSolr = solrTemplatesResponse.templates.first {
                it.id == SolrUtils.getUniqueSolrDocumentId(currentTemplateVariable.template.templateExtId, AssetType.TEMPLATE,
                        currentTemplateVariable.template.templateType.name, languageId) }
                    .templateVariables.first { it.templateVariableExtId == currentTemplateVariable.templateVariableExtId }

            if(currentTemplateVariableSolr.maxLength == 0) null else currentTemplateVariableSolr.maxLength
        }

        val templateVariableMaxLengthsWithoutZero = templateVariablesMaxLengths.filterNotNull()

        if (templateVariableMaxLengthsWithoutZero.isNotEmpty()) {
            return templateVariableMaxLengthsWithoutZero.minByOrNull { it }!!
        }

        return 0
    }
}
