package permission

import (
	"git.perx.ru/perxis/perxis-go/pkg/data"
	perxisexpr "git.perx.ru/perxis/perxis-go/pkg/expr"
)

type Action uint64

const (
	//ActionAny Action = iota
	ActionCreate Action = iota + 1
	ActionRead
	ActionUpdate
	ActionDelete
)

// Rule - правило доступа к контенту
type Rule struct {
	CollectionID string   `json:"collectionId" bson:"collectionId,omitempty"`
	Actions      []Action `json:"actions" bson:"actions,omitempty"`
	Access       Access   `json:"access" bson:"access,omitempty"`
	// Поля не передаются API клиенту
	HiddenFields []string `json:"hiddenFields,omitempty" bson:"hiddenFields,omitempty"`
	// Запрещен чтение указанные полей
	DenyReadFields []string `json:"denyReadFields,omitempty" bson:"denyReadFields,omitempty"`
	// Запрещена запись в указанные поля
	DenyWriteFields []string `json:"denyWriteFields,omitempty" bson:"denyWriteFields,omitempty"`
	// Дополнительный фильтр
	ReadFilter  string `json:"readFilter,omitempty" bson:"readFilter,omitempty"`
	WriteFilter string `json:"writeFilter,omitempty" bson:"writeFilter,omitempty"`
}

func NewRule(collectionID string, actions ...Action) *Rule {
	return &Rule{
		CollectionID: collectionID,
		Actions:      actions,
	}
}

func (r Rule) Clone() *Rule {
	return &Rule{
		CollectionID:    r.CollectionID,
		Actions:         append([]Action(nil), r.Actions...),
		Access:          r.Access,
		HiddenFields:    append([]string(nil), r.HiddenFields...),
		DenyReadFields:  append([]string(nil), r.DenyReadFields...),
		DenyWriteFields: append([]string(nil), r.DenyWriteFields...),
		ReadFilter:      r.ReadFilter,
		WriteFilter:     r.WriteFilter,
	}
}

func (r Rule) WithReadFilter(f string) *Rule {
	return &Rule{
		CollectionID:    r.CollectionID,
		Actions:         append([]Action(nil), r.Actions...),
		Access:          r.Access,
		HiddenFields:    append([]string(nil), r.HiddenFields...),
		DenyReadFields:  append([]string(nil), r.DenyReadFields...),
		DenyWriteFields: append([]string(nil), r.DenyWriteFields...),
		ReadFilter:      f,
		WriteFilter:     r.WriteFilter,
	}
}

func (r Rule) WithWriteFilter(f string) *Rule {
	return &Rule{
		CollectionID:    r.CollectionID,
		Actions:         append([]Action(nil), r.Actions...),
		Access:          r.Access,
		HiddenFields:    append([]string(nil), r.HiddenFields...),
		DenyReadFields:  append([]string(nil), r.DenyReadFields...),
		DenyWriteFields: append([]string(nil), r.DenyWriteFields...),
		ReadFilter:      r.ReadFilter,
		WriteFilter:     f,
	}
}

func (r Rule) WithReadWriteFilter(f string) *Rule {
	return &Rule{
		CollectionID:    r.CollectionID,
		Actions:         append([]Action(nil), r.Actions...),
		Access:          r.Access,
		HiddenFields:    append([]string(nil), r.HiddenFields...),
		DenyReadFields:  append([]string(nil), r.DenyReadFields...),
		DenyWriteFields: append([]string(nil), r.DenyWriteFields...),
		ReadFilter:      f,
		WriteFilter:     f,
	}
}

func (r Rule) WithReadonlyFields(ff ...string) *Rule {
	return &Rule{
		CollectionID:    r.CollectionID,
		Actions:         append([]Action(nil), r.Actions...),
		Access:          r.Access,
		HiddenFields:    append([]string(nil), r.HiddenFields...),
		DenyReadFields:  append(ff, r.DenyReadFields...),
		DenyWriteFields: append([]string(nil), r.DenyWriteFields...),
		ReadFilter:      r.ReadFilter,
		WriteFilter:     r.WriteFilter,
	}
}

func (r Rule) WithHiddenFields(ff ...string) *Rule {
	return &Rule{
		CollectionID:    r.CollectionID,
		Actions:         append([]Action(nil), r.Actions...),
		Access:          r.Access,
		HiddenFields:    append(ff, r.HiddenFields...),
		DenyReadFields:  append([]string(nil), r.DenyReadFields...),
		DenyWriteFields: append([]string(nil), r.DenyWriteFields...),
		ReadFilter:      r.ReadFilter,
		WriteFilter:     r.WriteFilter,
	}
}

func (r Rule) GetPermission(action Action) *Permission {
	for _, a := range r.Actions {
		if a == action {
			p := &Permission{
				Permitted: true,
			}

			switch action {
			case ActionRead:
				p.Filter = r.ReadFilter
				p.UnallowedFields = append(p.UnallowedFields, r.HiddenFields...)
				p.UnallowedFields = append(p.UnallowedFields, r.DenyWriteFields...)
			case ActionCreate, ActionUpdate, ActionDelete:
				p.Filter = r.WriteFilter
				p.UnallowedFields = append(p.UnallowedFields, r.DenyReadFields...)
			}

			p.UnallowedFields = data.SetFromSlice(p.UnallowedFields)

			return p
		}
	}

	return &Permission{}
}

type Ruleset interface {
	GetRule(collectionID string) *Rule
	Permission(collectionID string, action Action) *Permission
}

type Rules []*Rule

func (r Rules) Permission(collectionID string, action Action) *Permission {
	rule := r.GetRule(collectionID)
	return rule.GetPermission(action)
}

func (r Rules) GetRule(collectionID string) *Rule {
	for _, rule := range r {
		if data.GlobMatch(collectionID, rule.CollectionID) {
			return rule
		}
	}
	return nil
}

func MergeRules(src, in Rules) Rules {
	dst := make(Rules, 0, len(src)+len(in))
	seen := make(map[string]struct{})

	for _, rule := range src {
		if _, ok := seen[rule.CollectionID]; !ok {
			dst = append(dst, rule)
			seen[rule.CollectionID] = struct{}{}
		}
	}

	for _, rule := range in {
		if _, ok := seen[rule.CollectionID]; !ok {
			dst = append(dst, rule)
			seen[rule.CollectionID] = struct{}{}
		}
	}

	return dst
}

// MergeRule объединяет несколько Rule в один
//   - пересечение действий
//   - объединение hidden, readOnly, writeOnly fields
//   - объединение фильтров
func MergeRule(rules ...*Rule) *Rule {

	if len(rules) == 0 {
		return nil
	}

	var result *Rule
	var writeFilter []string
	var readFilter []string

	for i, r := range rules {
		if i == 0 { // first element

			result = r.Clone()

			result.CollectionID = ""
			if result.WriteFilter != "" {
				writeFilter = append(writeFilter, result.WriteFilter)
			}
			if result.ReadFilter != "" {
				readFilter = append(readFilter, result.ReadFilter)
			}

			continue
		}

		result.Actions = data.GetIntersection(result.Actions, r.Actions)
		result.HiddenFields = data.SetFromSlice(append(result.HiddenFields, r.HiddenFields...))
		result.DenyReadFields = data.SetFromSlice(append(result.DenyReadFields, r.DenyReadFields...))
		result.DenyWriteFields = data.SetFromSlice(append(result.DenyWriteFields, r.DenyWriteFields...))
		if r.WriteFilter != "" {
			writeFilter = append(writeFilter, r.WriteFilter)
		}
		if r.ReadFilter != "" {
			readFilter = append(readFilter, r.ReadFilter)
		}
	}

	result.WriteFilter = perxisexpr.And(data.SetFromSlice(writeFilter)...)
	result.ReadFilter = perxisexpr.And(data.SetFromSlice(readFilter)...)

	return result
}

type PrivilegedRuleset struct{}

func (r PrivilegedRuleset) Permission(_ string, _ Action) *Permission {
	return &Permission{
		Permitted:       true,
		UnallowedFields: []string{},
	}
}

func (r PrivilegedRuleset) GetRule(collectionID string) *Rule {
	return &Rule{
		CollectionID:    collectionID,
		Actions:         []Action{ActionRead, ActionCreate, ActionUpdate, ActionDelete},
		HiddenFields:    []string{},
		DenyReadFields:  []string{},
		DenyWriteFields: []string{},
	}
}

func Create(r Ruleset, collectionID string) *Permission {
	return r.Permission(collectionID, ActionCreate)
}

func Read(r Ruleset, collectionID string) *Permission {
	return r.Permission(collectionID, ActionRead)
}

func Update(r Ruleset, collectionID string) *Permission {
	return r.Permission(collectionID, ActionUpdate)
}

func Delete(r Ruleset, collectionID string) *Permission {
	return r.Permission(collectionID, ActionDelete)
}