// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package acl

type policyRulesMergeContext struct {
	aclRule                  string
	agentRules               map[string]*AgentRule
	agentPrefixRules         map[string]*AgentRule
	eventRules               map[string]*EventRule
	eventPrefixRules         map[string]*EventRule
	identityRules            map[string]*IdentityRule
	identityPrefixRules      map[string]*IdentityRule
	keyringRule              string
	keyRules                 map[string]*KeyRule
	keyPrefixRules           map[string]*KeyRule
	meshRule                 string
	peeringRule              string
	nodeRules                map[string]*NodeRule
	nodePrefixRules          map[string]*NodeRule
	operatorRule             string
	preparedQueryRules       map[string]*PreparedQueryRule
	preparedQueryPrefixRules map[string]*PreparedQueryRule
	serviceRules             map[string]*ServiceRule
	servicePrefixRules       map[string]*ServiceRule
	sessionRules             map[string]*SessionRule
	sessionPrefixRules       map[string]*SessionRule
	// namespaceRule is an enterprise-only field
	namespaceRule string
}

func (p *policyRulesMergeContext) init() {
	p.aclRule = ""
	p.agentRules = make(map[string]*AgentRule)
	p.agentPrefixRules = make(map[string]*AgentRule)
	p.eventRules = make(map[string]*EventRule)
	p.eventPrefixRules = make(map[string]*EventRule)
	p.identityRules = make(map[string]*IdentityRule)
	p.identityPrefixRules = make(map[string]*IdentityRule)
	p.keyringRule = ""
	p.keyRules = make(map[string]*KeyRule)
	p.keyPrefixRules = make(map[string]*KeyRule)
	p.meshRule = ""
	p.peeringRule = ""
	p.nodeRules = make(map[string]*NodeRule)
	p.nodePrefixRules = make(map[string]*NodeRule)
	p.operatorRule = ""
	p.preparedQueryRules = make(map[string]*PreparedQueryRule)
	p.preparedQueryPrefixRules = make(map[string]*PreparedQueryRule)
	p.serviceRules = make(map[string]*ServiceRule)
	p.servicePrefixRules = make(map[string]*ServiceRule)
	p.sessionRules = make(map[string]*SessionRule)
	p.sessionPrefixRules = make(map[string]*SessionRule)
}

func (p *policyRulesMergeContext) merge(policy *PolicyRules) {
	if takesPrecedenceOver(policy.ACL, p.aclRule) {
		p.aclRule = policy.ACL
	}

	for _, ap := range policy.Agents {
		update := true
		if permission, found := p.agentRules[ap.Node]; found {
			update = takesPrecedenceOver(ap.Policy, permission.Policy)
		}

		if update {
			p.agentRules[ap.Node] = ap
		}
	}

	for _, ap := range policy.AgentPrefixes {
		update := true
		if permission, found := p.agentPrefixRules[ap.Node]; found {
			update = takesPrecedenceOver(ap.Policy, permission.Policy)
		}

		if update {
			p.agentPrefixRules[ap.Node] = ap
		}
	}

	for _, ep := range policy.Events {
		update := true
		if permission, found := p.eventRules[ep.Event]; found {
			update = takesPrecedenceOver(ep.Policy, permission.Policy)
		}

		if update {
			p.eventRules[ep.Event] = ep
		}
	}

	for _, ep := range policy.EventPrefixes {
		update := true
		if permission, found := p.eventPrefixRules[ep.Event]; found {
			update = takesPrecedenceOver(ep.Policy, permission.Policy)
		}

		if update {
			p.eventPrefixRules[ep.Event] = ep
		}
	}

	for _, id := range policy.Identities {
		existing, found := p.identityRules[id.Name]

		if !found {
			p.identityRules[id.Name] = id
			continue
		}

		if takesPrecedenceOver(id.Policy, existing.Policy) {
			existing.Policy = id.Policy
			existing.EnterpriseRule = id.EnterpriseRule
		}

		if takesPrecedenceOver(id.Intentions, existing.Intentions) {
			existing.Intentions = id.Intentions
		}
	}

	for _, id := range policy.IdentityPrefixes {
		existing, found := p.identityPrefixRules[id.Name]

		if !found {
			p.identityPrefixRules[id.Name] = id
			continue
		}

		if takesPrecedenceOver(id.Policy, existing.Policy) {
			existing.Policy = id.Policy
			existing.EnterpriseRule = id.EnterpriseRule
		}

		if takesPrecedenceOver(id.Intentions, existing.Intentions) {
			existing.Intentions = id.Intentions
		}
	}

	if takesPrecedenceOver(policy.Keyring, p.keyringRule) {
		p.keyringRule = policy.Keyring
	}

	for _, kp := range policy.Keys {
		update := true
		if permission, found := p.keyRules[kp.Prefix]; found {
			update = takesPrecedenceOver(kp.Policy, permission.Policy)
		}

		if update {
			p.keyRules[kp.Prefix] = kp
		}
	}

	for _, kp := range policy.KeyPrefixes {
		update := true
		if permission, found := p.keyPrefixRules[kp.Prefix]; found {
			update = takesPrecedenceOver(kp.Policy, permission.Policy)
		}

		if update {
			p.keyPrefixRules[kp.Prefix] = kp
		}
	}

	for _, np := range policy.Nodes {
		update := true
		if permission, found := p.nodeRules[np.Name]; found {
			update = takesPrecedenceOver(np.Policy, permission.Policy)
		}

		if update {
			p.nodeRules[np.Name] = np
		}
	}

	for _, np := range policy.NodePrefixes {
		update := true
		if permission, found := p.nodePrefixRules[np.Name]; found {
			update = takesPrecedenceOver(np.Policy, permission.Policy)
		}

		if update {
			p.nodePrefixRules[np.Name] = np
		}
	}

	if takesPrecedenceOver(policy.Mesh, p.meshRule) {
		p.meshRule = policy.Mesh
	}

	if takesPrecedenceOver(policy.Peering, p.peeringRule) {
		p.peeringRule = policy.Peering
	}

	if takesPrecedenceOver(policy.Operator, p.operatorRule) {
		p.operatorRule = policy.Operator
	}

	for _, qp := range policy.PreparedQueries {
		update := true
		if permission, found := p.preparedQueryRules[qp.Prefix]; found {
			update = takesPrecedenceOver(qp.Policy, permission.Policy)
		}

		if update {
			p.preparedQueryRules[qp.Prefix] = qp
		}
	}

	for _, qp := range policy.PreparedQueryPrefixes {
		update := true
		if permission, found := p.preparedQueryPrefixRules[qp.Prefix]; found {
			update = takesPrecedenceOver(qp.Policy, permission.Policy)
		}

		if update {
			p.preparedQueryPrefixRules[qp.Prefix] = qp
		}
	}

	for _, sp := range policy.Services {
		existing, found := p.serviceRules[sp.Name]

		if !found {
			p.serviceRules[sp.Name] = sp
			continue
		}

		if takesPrecedenceOver(sp.Policy, existing.Policy) {
			existing.Policy = sp.Policy
			existing.EnterpriseRule = sp.EnterpriseRule
		}

		if takesPrecedenceOver(sp.Intentions, existing.Intentions) {
			existing.Intentions = sp.Intentions
		}
	}

	for _, sp := range policy.ServicePrefixes {
		existing, found := p.servicePrefixRules[sp.Name]

		if !found {
			p.servicePrefixRules[sp.Name] = sp
			continue
		}

		if takesPrecedenceOver(sp.Policy, existing.Policy) {
			existing.Policy = sp.Policy
			existing.EnterpriseRule = sp.EnterpriseRule
		}

		if takesPrecedenceOver(sp.Intentions, existing.Intentions) {
			existing.Intentions = sp.Intentions
		}
	}

	for _, sp := range policy.Sessions {
		update := true
		if permission, found := p.sessionRules[sp.Node]; found {
			update = takesPrecedenceOver(sp.Policy, permission.Policy)
		}

		if update {
			p.sessionRules[sp.Node] = sp
		}
	}

	for _, sp := range policy.SessionPrefixes {
		update := true
		if permission, found := p.sessionPrefixRules[sp.Node]; found {
			update = takesPrecedenceOver(sp.Policy, permission.Policy)
		}

		if update {
			p.sessionPrefixRules[sp.Node] = sp
		}
	}
}

func (p *policyRulesMergeContext) fill(merged *PolicyRules) {
	merged.ACL = p.aclRule
	merged.Keyring = p.keyringRule
	merged.Operator = p.operatorRule
	merged.Mesh = p.meshRule
	merged.Peering = p.peeringRule

	// All the for loop appends are ugly but Go doesn't have a way to get
	// a slice of all values within a map so this is necessary

	merged.Agents = []*AgentRule{}
	for _, policy := range p.agentRules {
		merged.Agents = append(merged.Agents, policy)
	}

	merged.AgentPrefixes = []*AgentRule{}
	for _, policy := range p.agentPrefixRules {
		merged.AgentPrefixes = append(merged.AgentPrefixes, policy)
	}

	merged.Events = []*EventRule{}
	for _, policy := range p.eventRules {
		merged.Events = append(merged.Events, policy)
	}

	merged.EventPrefixes = []*EventRule{}
	for _, policy := range p.eventPrefixRules {
		merged.EventPrefixes = append(merged.EventPrefixes, policy)
	}

	merged.Identities = []*IdentityRule{}
	for _, policy := range p.identityRules {
		merged.Identities = append(merged.Identities, policy)
	}

	merged.IdentityPrefixes = []*IdentityRule{}
	for _, policy := range p.identityPrefixRules {
		merged.IdentityPrefixes = append(merged.IdentityPrefixes, policy)
	}

	merged.Keys = []*KeyRule{}
	for _, policy := range p.keyRules {
		merged.Keys = append(merged.Keys, policy)
	}

	merged.KeyPrefixes = []*KeyRule{}
	for _, policy := range p.keyPrefixRules {
		merged.KeyPrefixes = append(merged.KeyPrefixes, policy)
	}

	merged.Nodes = []*NodeRule{}
	for _, policy := range p.nodeRules {
		merged.Nodes = append(merged.Nodes, policy)
	}

	merged.NodePrefixes = []*NodeRule{}
	for _, policy := range p.nodePrefixRules {
		merged.NodePrefixes = append(merged.NodePrefixes, policy)
	}

	merged.PreparedQueries = []*PreparedQueryRule{}
	for _, policy := range p.preparedQueryRules {
		merged.PreparedQueries = append(merged.PreparedQueries, policy)
	}

	merged.PreparedQueryPrefixes = []*PreparedQueryRule{}
	for _, policy := range p.preparedQueryPrefixRules {
		merged.PreparedQueryPrefixes = append(merged.PreparedQueryPrefixes, policy)
	}

	merged.Services = []*ServiceRule{}
	for _, policy := range p.serviceRules {
		merged.Services = append(merged.Services, policy)
	}

	merged.ServicePrefixes = []*ServiceRule{}
	for _, policy := range p.servicePrefixRules {
		merged.ServicePrefixes = append(merged.ServicePrefixes, policy)
	}

	merged.Sessions = []*SessionRule{}
	for _, policy := range p.sessionRules {
		merged.Sessions = append(merged.Sessions, policy)
	}

	merged.SessionPrefixes = []*SessionRule{}
	for _, policy := range p.sessionPrefixRules {
		merged.SessionPrefixes = append(merged.SessionPrefixes, policy)
	}
}

type PolicyMerger struct {
	policyRulesMergeContext
	enterprisePolicyRulesMergeContext
}

func (m *PolicyMerger) init() {
	m.policyRulesMergeContext.init()
	m.enterprisePolicyRulesMergeContext.init()
}

func (m *PolicyMerger) Merge(policy *Policy) {
	m.policyRulesMergeContext.merge(&policy.PolicyRules)
	m.enterprisePolicyRulesMergeContext.merge(&policy.EnterprisePolicyRules)
}

// Policy outputs the merged policy
func (m *PolicyMerger) Policy() *Policy {
	merged := &Policy{}
	m.policyRulesMergeContext.fill(&merged.PolicyRules)
	m.enterprisePolicyRulesMergeContext.fill(&merged.EnterprisePolicyRules)

	return merged
}

func MergePolicies(policies []*Policy) *Policy {
	var merger PolicyMerger
	merger.init()
	for _, p := range policies {
		merger.Merge(p)
	}

	return merger.Policy()
}
