package main

import (
	"errors"
	"fmt"
	"regexp"
	"slices"

	"github.com/prymitive/karma/internal/alertmanager"
	"github.com/prymitive/karma/internal/config"
	"github.com/prymitive/karma/internal/models"
	"github.com/prymitive/karma/internal/regex"
)

const (
	aclActionRequireMatcher = "requireMatcher"
	aclActionBlock          = "block"
	aclActionAllow          = "allow"
)

var allACLActions = []string{aclActionAllow, aclActionBlock, aclActionRequireMatcher}

type silenceFilter struct {
	NameRegex  *regexp.Regexp
	ValueRegex *regexp.Regexp
	IsRegex    *bool
	IsEqual    *bool
	Name       string
	Value      string
}

func (sf *silenceFilter) isMatch(silence *models.Silence) bool {
	for _, m := range silence.Matchers {
		var nameMatch bool
		if sf.Name != "" && sf.Name == m.Name {
			nameMatch = true
		} else if sf.NameRegex != nil && sf.NameRegex.MatchString(m.Name) {
			nameMatch = true
		}

		var valueMatch bool
		if sf.Value != "" && sf.Value == m.Value {
			valueMatch = true
		} else if sf.ValueRegex != nil && sf.ValueRegex.MatchString(m.Value) {
			valueMatch = true
		}

		isRegexMatch := true
		if sf.IsRegex != nil && *sf.IsRegex != m.IsRegex {
			isRegexMatch = false
		}

		isEqualMatch := true
		if sf.IsEqual != nil && *sf.IsEqual != m.IsEqual {
			isEqualMatch = false
		}

		if nameMatch && valueMatch && isRegexMatch && isEqualMatch {
			return true
		}
	}

	return false
}

type silenceMatcher struct {
	NameRegex  *regexp.Regexp
	ValueRegex *regexp.Regexp
	IsRegex    *bool
	IsEqual    *bool
	Name       string
	Value      string
}

func (sm *silenceMatcher) isMatch(m models.SilenceMatcher) bool {
	if sm.NameRegex != nil && !sm.NameRegex.MatchString(m.Name) {
		return false
	}
	if sm.Name != "" && sm.Name != m.Name {
		return false
	}
	if sm.ValueRegex != nil && !sm.ValueRegex.MatchString(m.Value) {
		return false
	}
	if sm.Value != "" && sm.Value != m.Value {
		return false
	}
	if sm.IsRegex != nil && *sm.IsRegex != m.IsRegex {
		return false
	}
	if sm.IsEqual != nil && *sm.IsEqual != m.IsEqual {
		return false
	}
	return true
}

type aclMatchers struct {
	Required []silenceMatcher
}

type silenceACLScope struct {
	Groups        []string
	Alertmanagers []string
	Filters       []silenceFilter
}

type silenceACL struct {
	Action   string
	Reason   string
	Scope    silenceACLScope
	Matchers aclMatchers
}

func (acl *silenceACL) isAllowed(amName string, silence *models.Silence, groups []string) (bool, error) {
	groupMatch := len(acl.Scope.Groups) == 0
	for _, aclGroup := range acl.Scope.Groups {
		if slices.Contains(groups, aclGroup) {
			groupMatch = true
			break
		}
	}

	amMatch := len(acl.Scope.Alertmanagers) == 0
	if slices.Contains(acl.Scope.Alertmanagers, amName) {
		amMatch = true
	}

	filterMatch := true
	for _, aclFilter := range acl.Scope.Filters {
		if m := aclFilter.isMatch(silence); !m {
			filterMatch = m
		}
	}

	if groupMatch && amMatch && filterMatch {
		switch acl.Action {
		case aclActionAllow:
			return true, nil
		case aclActionBlock:
			return false, fmt.Errorf("silence blocked by ACL rule: %s", acl.Reason)
		case aclActionRequireMatcher:
			for _, aclM := range acl.Matchers.Required {
				var wasFound bool
				if slices.ContainsFunc(silence.Matchers, aclM.isMatch) {
					wasFound = true
				}
				if !wasFound {
					return false, fmt.Errorf("silence blocked by ACL rule: %s", acl.Reason)
				}
			}
		}
	}

	return false, nil
}

func newSilenceACLFromConfig(cfg config.SilenceACLRule) (*silenceACL, error) {
	acl := silenceACL{
		Action: cfg.Action,
		Reason: cfg.Reason,
		Scope: silenceACLScope{
			Groups:        []string{},
			Alertmanagers: []string{},
			Filters:       []silenceFilter{},
		},
		Matchers: aclMatchers{},
	}

	if !slices.Contains(allACLActions, acl.Action) {
		return nil, fmt.Errorf("silence ACL rule requires 'action' to be one of %v, got %q", allACLActions, acl.Action)
	}

	if acl.Reason == "" {
		return nil, errors.New("silence ACL rule requires 'reason' to be set")
	}

	for _, groupName := range cfg.Scope.Groups {
		// Can't validate the group name if it comes dynamically from a http proxy
		if config.Config.Authentication.Header.GroupName == "" {
			var wasFound bool
			for _, authGroup := range config.Config.Authorization.Groups {
				if authGroup.Name == groupName {
					wasFound = true
					break
				}
			}
			if !wasFound {
				return nil, fmt.Errorf("invalid silence ACL rule, no group with name %q found in authorization.groups configuration", groupName)
			}
		}

		acl.Scope.Groups = append(acl.Scope.Groups, groupName)
	}

	for _, amName := range cfg.Scope.Alertmanagers {
		am := alertmanager.GetAlertmanagerByName(amName)
		if am == nil {
			return nil, fmt.Errorf("invalid ACL rule, no alertmanager with name %q found", amName)
		}
		acl.Scope.Alertmanagers = append(acl.Scope.Alertmanagers, am.Name)
	}

	for _, filter := range cfg.Scope.Filters {
		if filter.Name == "" && filter.NameRegex == "" {
			return nil, errors.New("silence ACL rule filter requires 'name' or 'name_re' to be set")
		}
		if filter.Name != "" && filter.NameRegex != "" {
			return nil, errors.New("silence ACL rule filter can only have 'name' or 'name_re' set, not both")
		}

		if filter.Value == "" && filter.ValueRegex == "" {
			return nil, errors.New("silence ACL rule filter requires 'value' or 'value_re' to be set")
		}
		if filter.Value != "" && filter.ValueRegex != "" {
			return nil, errors.New("silence ACL rule filter can only have 'value' or 'value_re' set, not both")
		}

		f := silenceFilter{
			Name:    filter.Name,
			Value:   filter.Value,
			IsRegex: filter.IsRegex,
			IsEqual: filter.IsEqual,
		}

		if filter.NameRegex != "" {
			re, err := regex.CompileAnchored(filter.NameRegex)
			if err != nil {
				return nil, fmt.Errorf("invalid ACL rule, failed to parse name_re %q: %w", filter.NameRegex, err)
			}
			f.NameRegex = re
		}

		if filter.ValueRegex != "" {
			re, err := regex.CompileAnchored(filter.ValueRegex)
			if err != nil {
				return nil, fmt.Errorf("invalid ACL rule, failed to parse value_re %q: %w", filter.ValueRegex, err)
			}
			f.ValueRegex = re
		}

		acl.Scope.Filters = append(acl.Scope.Filters, f)
	}

	if acl.Action == aclActionRequireMatcher {
		for _, matcherConfig := range cfg.Matchers.Required {
			if matcherConfig.Name == "" && matcherConfig.NameRegex == "" {
				return nil, errors.New("silence ACL rule matcher requires 'name' or 'name_re' to be set")
			}
			if matcherConfig.Name != "" && matcherConfig.NameRegex != "" {
				return nil, errors.New("silence ACL rule matcher can only have 'name' or 'name_re' set, not both")
			}

			if matcherConfig.Value == "" && matcherConfig.ValueRegex == "" {
				return nil, errors.New("silence ACL rule matcher requires 'value' or 'value_re' to be set")
			}
			if matcherConfig.Value != "" && matcherConfig.ValueRegex != "" {
				return nil, errors.New("silence ACL rule matcher can only have 'value' or 'value_re' set, not both")
			}

			m := silenceMatcher{
				Name:    matcherConfig.Name,
				Value:   matcherConfig.Value,
				IsRegex: matcherConfig.IsRegex,
				IsEqual: matcherConfig.IsEqual,
			}

			if matcherConfig.NameRegex != "" {
				re, err := regex.CompileAnchored(matcherConfig.NameRegex)
				if err != nil {
					return nil, fmt.Errorf("invalid ACL rule, failed to parse name_re %q: %w", matcherConfig.NameRegex, err)
				}
				m.NameRegex = re
			}

			if matcherConfig.ValueRegex != "" {
				re, err := regex.CompileAnchored(matcherConfig.ValueRegex)
				if err != nil {
					return nil, fmt.Errorf("invalid ACL rule, failed to parse value_re %q: %w", matcherConfig.ValueRegex, err)
				}
				m.ValueRegex = re
			}

			acl.Matchers.Required = append(acl.Matchers.Required, m)
		}
	}

	return &acl, nil
}
