// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT

package base

import (
	"crypto/sha256"
	"fmt"
	"github.com/pb33f/libopenapi/datamodel/low"
	"github.com/pb33f/libopenapi/index"
	"github.com/pb33f/libopenapi/utils"
	"gopkg.in/yaml.v3"
	"sort"
	"strings"
)

// SecurityRequirement is a low-level representation of a Swagger / OpenAPI 3 SecurityRequirement object.
//
// SecurityRequirement lists the required security schemes to execute this operation. The object can have multiple
// security schemes declared in it which are all required (that is, there is a logical AND between the schemes).
//
// The name used for each property MUST correspond to a security scheme declared in the Security Definitions
//   - https://swagger.io/specification/v2/#securityDefinitionsObject
//   - https://swagger.io/specification/#security-requirement-object
type SecurityRequirement struct {
	Requirements low.ValueReference[map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]]]
	*low.Reference
}

// Build will extract security requirements from the node (the structure is odd, to be honest)
func (s *SecurityRequirement) Build(root *yaml.Node, _ *index.SpecIndex) error {
	root = utils.NodeAlias(root)
	utils.CheckForMergeNodes(root)
	s.Reference = new(low.Reference)
	var labelNode *yaml.Node
	valueMap := make(map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]])
	var arr []low.ValueReference[string]
	for i := range root.Content {
		if i%2 == 0 {
			labelNode = root.Content[i]
			arr = []low.ValueReference[string]{} // reset roles.
			continue
		}
		for j := range root.Content[i].Content {
			arr = append(arr, low.ValueReference[string]{
				Value:     root.Content[i].Content[j].Value,
				ValueNode: root.Content[i].Content[j],
			})
		}
		valueMap[low.KeyReference[string]{
			Value:   labelNode.Value,
			KeyNode: labelNode,
		}] = low.ValueReference[[]low.ValueReference[string]]{
			Value:     arr,
			ValueNode: root.Content[i],
		}
	}
	s.Requirements = low.ValueReference[map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]]]{
		Value:     valueMap,
		ValueNode: root,
	}
	return nil
}

// FindRequirement will attempt to locate a security requirement string from a supplied name.
func (s *SecurityRequirement) FindRequirement(name string) []low.ValueReference[string] {
	for k := range s.Requirements.Value {
		if k.Value == name {
			return s.Requirements.Value[k].Value
		}
	}
	return nil
}

// GetKeys returns a string slice of all the keys used in the requirement.
func (s *SecurityRequirement) GetKeys() []string {
	keys := make([]string, len(s.Requirements.Value))
	z := 0
	for k := range s.Requirements.Value {
		keys[z] = k.Value
	}
	return keys
}

// Hash will return a consistent SHA256 Hash of the SecurityRequirement object
func (s *SecurityRequirement) Hash() [32]byte {
	var f []string
	values := make(map[string][]string, len(s.Requirements.Value))
	var valKeys []string
	for k := range s.Requirements.Value {
		var vals []string
		for y := range s.Requirements.Value[k].Value {
			vals = append(vals, s.Requirements.Value[k].Value[y].Value)
			// lol, I know. -------^^^^^ <- this is the actual value.
		}
		sort.Strings(vals)
		valKeys = append(valKeys, k.Value)
		if len(vals) > 0 {
			values[k.Value] = vals
		}
	}
	sort.Strings(valKeys)
	for val := range valKeys {
		f = append(f, fmt.Sprintf("%s-%s", valKeys[val], strings.Join(values[valKeys[val]], "|")))
	}
	return sha256.Sum256([]byte(strings.Join(f, "|")))
}
