package ru.yandex.qe.dispenser.ws.quota.request.workflow

import org.springframework.stereotype.Component
import ru.yandex.qe.dispenser.domain.Campaign.Type
import ru.yandex.qe.dispenser.domain.Person
import ru.yandex.qe.dispenser.domain.Project
import ru.yandex.qe.dispenser.domain.QuotaChangeRequest.Status
import ru.yandex.qe.dispenser.domain.hierarchy.Hierarchy
import ru.yandex.qe.dispenser.domain.hierarchy.HierarchySupplier
import ru.yandex.qe.dispenser.domain.hierarchy.PermissionsCache

@Component
class AuthorizationManager(
    private val hierarchySupplier: HierarchySupplier
) {

    private val roleResolvers: Map<Role, (person: Person, project: Project, hierarchy: Hierarchy) -> Boolean> = mapOf(
        Role.MEMBER_WITH_INHERITANCE to { person, project, hierarchy -> PermissionsCache.isMember(hierarchy.projectReader, person, project) },
        Role.CAPACITY_PLANNER_WITH_INHERITANCE to { person, project, hierarchy -> PermissionsCache.isResponsible(hierarchy.projectReader, person, project) },
        Role.PROCESS_RESPONSIBLE to { person, _, hierarchy -> PermissionsCache.isUserProcessResponsible(hierarchy.projectReader, person) },
        Role.DISPENSER_ADMIN to { person, _, hierarchy -> PermissionsCache.isDispenserAdmin(hierarchy.dispenserAdminsReader, person) },
        Role.RESOURCE_ORDER_MANAGER_WITH_INHERITANCE to { person, project, hierarchy -> PermissionsCache.isResourceOrderManager(hierarchy.projectReader, person, project) }
    )

    private val createPermissions: Map<Type, Set<Role>> = mapOf(
        Type.DRAFT to setOf(Role.MEMBER_WITH_INHERITANCE, Role.CAPACITY_PLANNER_WITH_INHERITANCE,
            Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
        Type.AGGREGATED to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
            Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN, Role.RESOURCE_ORDER_MANAGER_WITH_INHERITANCE)
    )

    private val updatePermissions: Map<Type, Set<Role>> = mapOf(
        Type.DRAFT to setOf(Role.MEMBER_WITH_INHERITANCE, Role.CAPACITY_PLANNER_WITH_INHERITANCE,
            Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
        Type.AGGREGATED to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
            Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN, Role.RESOURCE_ORDER_MANAGER_WITH_INHERITANCE)
    )

    private val transitionPermissions: Map<Type, Map<Transition, Set<Role>>> = mapOf(
        Type.DRAFT to mapOf(
            Transition(Status.CANCELLED, Status.NEW) to setOf(Role.MEMBER_WITH_INHERITANCE,
                Role.CAPACITY_PLANNER_WITH_INHERITANCE, Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.REJECTED, Status.NEW) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.NEW, Status.READY_FOR_REVIEW) to setOf(Role.MEMBER_WITH_INHERITANCE,
                Role.CAPACITY_PLANNER_WITH_INHERITANCE, Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.CONFIRMED, Status.REJECTED) to setOf(Role.PROCESS_RESPONSIBLE,
                Role.DISPENSER_ADMIN),
            Transition(Status.READY_FOR_REVIEW, Status.REJECTED) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.READY_FOR_REVIEW, Status.APPROVED) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.READY_FOR_REVIEW, Status.NEED_INFO) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.APPROVED, Status.CONFIRMED) to setOf(Role.PROCESS_RESPONSIBLE,
                Role.DISPENSER_ADMIN),
            Transition(Status.APPROVED, Status.NEED_INFO) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.APPROVED, Status.REJECTED) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.NEED_INFO, Status.APPROVED) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.NEED_INFO, Status.REJECTED) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.NEED_INFO, Status.READY_FOR_REVIEW) to setOf(Role.MEMBER_WITH_INHERITANCE,
                Role.CAPACITY_PLANNER_WITH_INHERITANCE, Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.NEW, Status.CANCELLED) to setOf(Role.MEMBER_WITH_INHERITANCE,
                Role.CAPACITY_PLANNER_WITH_INHERITANCE, Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.READY_FOR_REVIEW, Status.CANCELLED) to setOf(Role.MEMBER_WITH_INHERITANCE,
                Role.CAPACITY_PLANNER_WITH_INHERITANCE, Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.NEED_INFO, Status.CANCELLED) to setOf(Role.MEMBER_WITH_INHERITANCE,
                Role.CAPACITY_PLANNER_WITH_INHERITANCE, Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.APPROVED, Status.CANCELLED) to setOf(Role.MEMBER_WITH_INHERITANCE,
                Role.CAPACITY_PLANNER_WITH_INHERITANCE, Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN)
        ),
        Type.AGGREGATED to mapOf(
            Transition(Status.CANCELLED, Status.NEW) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN, Role.RESOURCE_ORDER_MANAGER_WITH_INHERITANCE),
            Transition(Status.REJECTED, Status.NEW) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.NEW, Status.READY_FOR_REVIEW) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN, Role.RESOURCE_ORDER_MANAGER_WITH_INHERITANCE),
            Transition(Status.CONFIRMED, Status.REJECTED) to setOf(Role.PROCESS_RESPONSIBLE,
                Role.DISPENSER_ADMIN),
            Transition(Status.READY_FOR_REVIEW, Status.REJECTED) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.READY_FOR_REVIEW, Status.APPROVED) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.READY_FOR_REVIEW, Status.NEED_INFO) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.APPROVED, Status.CONFIRMED) to setOf(Role.PROCESS_RESPONSIBLE,
                Role.DISPENSER_ADMIN),
            Transition(Status.APPROVED, Status.NEED_INFO) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.APPROVED, Status.REJECTED) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.NEED_INFO, Status.APPROVED) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.NEED_INFO, Status.REJECTED) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN),
            Transition(Status.NEED_INFO, Status.READY_FOR_REVIEW) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN, Role.RESOURCE_ORDER_MANAGER_WITH_INHERITANCE),
            Transition(Status.NEW, Status.CANCELLED) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN, Role.RESOURCE_ORDER_MANAGER_WITH_INHERITANCE),
            Transition(Status.READY_FOR_REVIEW, Status.CANCELLED) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN, Role.RESOURCE_ORDER_MANAGER_WITH_INHERITANCE),
            Transition(Status.NEED_INFO, Status.CANCELLED) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN, Role.RESOURCE_ORDER_MANAGER_WITH_INHERITANCE),
            Transition(Status.APPROVED, Status.CANCELLED) to setOf(Role.CAPACITY_PLANNER_WITH_INHERITANCE,
                Role.PROCESS_RESPONSIBLE, Role.DISPENSER_ADMIN, Role.RESOURCE_ORDER_MANAGER_WITH_INHERITANCE)
        )
    )

    fun hasCreatePermissionsInCampaignType(person: Person,
                                           project: Project,
                                           campaignType: Type
    ): Boolean {
        return hasCreatePermissionsInAnyOfCampaignTypes(person, project, setOf(campaignType))
    }

    fun hasCreatePermissionsInAnyOfCampaignTypes(person: Person,
                                                 project: Project,
                                                 campaignTypes: Collection<Type>
    ): Boolean {
        return hasCreatePermissionsInCampaignTypes(person, project, campaignTypes).values.any { it }
    }

    fun hasCreatePermissionsInCampaignTypes(person: Person,
                                            project: Project,
                                            campaignTypes: Collection<Type>
    ): Map<Type, Boolean> {
        val campaignTypesSet = campaignTypes.toSet()
        val rolesToResolve = createPermissions.filterKeys { campaignTypesSet.contains(it) }.values.flatten().toSet()
        val resolvedRoles = resolveRoles(person, project, rolesToResolve)
        val result = mutableMapOf<Type, Boolean>()
        campaignTypes.forEach {
            val rolesWithPermission = createPermissions[it] ?: emptySet()
            result[it] = rolesWithPermission.any { role -> resolvedRoles[role] ?: false }
        }
        return result
    }

    fun hasUpdatePermissionsInCampaignType(person: Person,
                                           project: Project,
                                           campaignType: Type
    ): Boolean {
        return hasUpdatePermissionsInAnyOfCampaignTypes(person, project, setOf(campaignType))
    }

    fun hasUpdatePermissionsInAnyOfCampaignTypes(person: Person,
                                                 project: Project,
                                                 campaignTypes: Collection<Type>
    ): Boolean {
        return hasUpdatePermissionsInCampaignTypes(person, project, campaignTypes).values.any { it }
    }

    fun hasUpdatePermissionsInCampaignTypes(person: Person,
                                            project: Project,
                                            campaignTypes: Collection<Type>
    ): Map<Type, Boolean> {
        val campaignTypesSet = campaignTypes.toSet()
        val rolesToResolve = updatePermissions.filterKeys { campaignTypesSet.contains(it) }.values.flatten().toSet()
        val resolvedRoles = resolveRoles(person, project, rolesToResolve)
        val result = mutableMapOf<Type, Boolean>()
        campaignTypes.forEach {
            val rolesWithPermission = updatePermissions[it] ?: emptySet()
            result[it] = rolesWithPermission.any { role -> resolvedRoles[role] ?: false }
        }
        return result
    }

    fun hasTransitionPermissionsInCampaignType(person: Person,
                                               project: Project,
                                               from: Status,
                                               to: Status,
                                               campaignType: Type
    ): Boolean {
        return hasTransitionPermissionsInAnyOfCampaignTypes(person, project, from, to, setOf(campaignType))
    }

    fun hasTransitionPermissionsInAnyOfCampaignTypes(person: Person,
                                                     project: Project,
                                                     from: Status,
                                                     to: Status,
                                                     campaignTypes: Collection<Type>
    ): Boolean {
        return hasTransitionPermissionsInCampaignTypes(person, project, from, to, campaignTypes).values.any { it }
    }

    fun hasTransitionPermissionsInCampaignTypes(person: Person,
                                                project: Project,
                                                from: Status,
                                                to: Status,
                                                campaignTypes: Collection<Type>
    ): Map<Type, Boolean> {
        val transition = Transition(from, to)
        val campaignTypesSet = campaignTypes.toSet()
        val rolesToResolve = transitionPermissions.filterKeys { campaignTypesSet.contains(it) }
            .mapValues { it.value[transition] ?: emptySet() }.values.flatten().toSet()
        val resolvedRoles = resolveRoles(person, project, rolesToResolve)
        val result = mutableMapOf<Type, Boolean>()
        campaignTypes.forEach {
            val rolesWithPermission = (transitionPermissions[it] ?: emptyMap())[transition] ?: emptySet()
            result[it] = rolesWithPermission.any { role -> resolvedRoles[role] ?: false }
        }
        return result
    }

    fun allowedTransitionsInCampaignType(person: Person,
                                         project: Project,
                                         campaignType: Type
    ): Map<Status, Set<Status>> {
        return allowedTransitionsInAnyOfCampaignTypes(person, project, setOf(campaignType))
    }

    fun allowedTransitionsInAnyOfCampaignTypes(person: Person,
                                               project: Project,
                                               campaignTypes: Collection<Type>
    ): Map<Status, Set<Status>> {
        val transitionsByType = allowedTransitionsInCampaignTypes(person, project, campaignTypes)
        if (transitionsByType.isEmpty()) {
            return emptyMap()
        }
        if (transitionsByType.size == 1) {
            return transitionsByType[transitionsByType.keys.first()]!!
        }
        val result = mutableMapOf<Status, MutableSet<Status>>()
        transitionsByType.forEach { (_, transitions) -> transitions
            .forEach { (from, to) -> result.computeIfAbsent(from) { mutableSetOf() }.addAll(to) } }
        return result
    }

    fun allowedTransitionsInCampaignTypes(person: Person,
                                          project: Project,
                                          campaignTypes: Collection<Type>
    ): Map<Type, Map<Status, Set<Status>>> {
        val campaignTypesSet = campaignTypes.toSet()
        val rolesToResolve = transitionPermissions.filterKeys { campaignTypesSet.contains(it) }
            .values.flatMap { it.values.flatten() }.toSet()
        val resolvedRoles = resolveRoles(person, project, rolesToResolve)
        val transitionsPerCampaignType = mutableMapOf<Type, Set<Transition>>()
        campaignTypes.forEach {
            val rolesPerTransition = (transitionPermissions[it] ?: emptyMap())
            val availableTransitions = mutableSetOf<Transition>()
            rolesPerTransition.forEach { (transition, roles) ->
                if (roles.any { role -> resolvedRoles[role] == true }) {
                    availableTransitions.add(transition)
                }
            }
            transitionsPerCampaignType[it] = availableTransitions
        }
        return transitionsPerCampaignType.mapValues { it.value.groupBy { transition -> transition.from }
            .mapValues { v -> v.value.map { t -> t.to }.toSet() } }
    }

    private fun resolveRoles(person: Person,
                             project: Project,
                             roles: Collection<Role>): Map<Role, Boolean> {
        val hierarchy = hierarchySupplier.get()
        val result = mutableMapOf<Role, Boolean>()
        roles.forEach { role -> result[role] = roleResolvers[role]!!(person, project, hierarchy) }
        return result
    }

    data class Transition(
        val from: Status,
        val to: Status
    )

    private enum class Role {
        MEMBER_WITH_INHERITANCE,
        CAPACITY_PLANNER_WITH_INHERITANCE,
        PROCESS_RESPONSIBLE,
        DISPENSER_ADMIN,
        RESOURCE_ORDER_MANAGER_WITH_INHERITANCE
    }

}
