//
// Author:: Darren Murray (<darren.murray@lacework.net>)
// Copyright:: Copyright 2021, Lacework Inc.
// License:: Apache License, Version 2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package api

import (
	_ "embed"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/pkg/errors"
)

type resourceGroupType int

const (
	// type that defines a non-existing Resource Group
	NoneResourceGroup resourceGroupType = iota
	AwsResourceGroup
	AzureResourceGroup
	ContainerResourceGroup
	GcpResourceGroup
	MachineResourceGroup
	OciResourceGroup
	KubernetesResourceGroup
)

// query templates
var (
	NoneResourceGroupQueryTemplate string = ""
	//go:embed _templates/resource_groups/aws.json
	AwsResourceGroupQueryTemplate string
	//go:embed _templates/resource_groups/azure.json
	AzureResourceGroupQueryTemplate string
	//go:embed _templates/resource_groups/container.json
	ContainerResourceGroupQueryTemplate string
	//go:embed _templates/resource_groups/gcp.json
	GcpResourceGroupQueryTemplate string
	//go:embed _templates/resource_groups/machine.json
	MachineResourceGroupQueryTemplate string
	//go:embed _templates/resource_groups/oci.json
	OciResourceGroupQueryTemplate string
	//go:embed _templates/resource_groups/kubernetes.json
	KubernetesResourceGroupQueryTemplate string
)

type resourceGroupContext struct {
	resourceGroupType string
	queryTemplate     string
}

// ResourceGroupTypes is the list of available Resource Group types
var ResourceGroupTypes = map[resourceGroupType]resourceGroupContext{
	NoneResourceGroup:       {resourceGroupType: "None", queryTemplate: NoneResourceGroupQueryTemplate},
	AwsResourceGroup:        {resourceGroupType: "AWS", queryTemplate: AwsResourceGroupQueryTemplate},
	AzureResourceGroup:      {resourceGroupType: "AZURE", queryTemplate: AzureResourceGroupQueryTemplate},
	ContainerResourceGroup:  {resourceGroupType: "CONTAINER", queryTemplate: ContainerResourceGroupQueryTemplate},
	GcpResourceGroup:        {resourceGroupType: "GCP", queryTemplate: GcpResourceGroupQueryTemplate},
	MachineResourceGroup:    {resourceGroupType: "MACHINE", queryTemplate: MachineResourceGroupQueryTemplate},
	OciResourceGroup:        {resourceGroupType: "OCI", queryTemplate: OciResourceGroupQueryTemplate},
	KubernetesResourceGroup: {resourceGroupType: "KUBERNETES", queryTemplate: KubernetesResourceGroupQueryTemplate},
}

func NewResourceGroup(name string, iType resourceGroupType,
	description string, query *RGQuery) ResourceGroupData {
	return ResourceGroupData{
		Name:        name,
		Type:        iType.String(),
		Enabled:     1,
		Query:       query,
		Description: description,
	}
}

func (svc *ResourceGroupsService) List() (response ResourceGroupsResponse, err error) {
	var rawResponse ResourceGroupsResponse
	err = svc.client.RequestDecoder("GET", apiV2ResourceGroups, nil, &rawResponse)
	if err != nil {
		return rawResponse, err
	}

	err = sanitizeFieldsInRawResponseList(&rawResponse, &response)
	if err != nil {
		return rawResponse, err
	}

	return rawResponse, nil
}

func sanitizeFieldsInRawResponse(rawResponse *ResourceGroupResponse, response interface{}) error {
	// update filters keys to match the query template
	updateFiltersKeys(&rawResponse.Data)

	j, err := json.Marshal(rawResponse)
	if err != nil {
		return err
	}

	return json.Unmarshal(j, &response)
}

func sanitizeFieldsInRawResponseList(rawResponse *ResourceGroupsResponse, response interface{}) error {
	for i := range rawResponse.Data {
		// update filters keys to match the query template
		updateFiltersKeys(&rawResponse.Data[i])
	}

	j, err := json.Marshal(rawResponse)
	if err != nil {
		return err
	}

	return json.Unmarshal(j, &response)
}

func (svc *ResourceGroupsService) Create(group ResourceGroupData) (
	response ResourceGroupResponse,
	err error,
) {
	var rawResponse ResourceGroupResponse
	err = svc.create(group, &rawResponse)
	if err != nil {
		return
	}

	err = sanitizeFieldsInRawResponse(&rawResponse, &response)

	return
}

func (svc *ResourceGroupsService) Update(data *ResourceGroupData) (
	response ResourceGroupResponse,
	err error,
) {
	if data == nil {
		err = errors.New("resource group must not be empty")
		return
	}
	guid := data.ID()
	data.ResetResourceGUID()

	var rawResponse ResourceGroupResponse
	err = svc.update(guid, data, &rawResponse)

	if err != nil {
		return
	}

	err = sanitizeFieldsInRawResponse(&rawResponse, &response)

	return
}

func collectFilterNames(children []*RGChild, filterNames map[string]string) {
	for _, child := range children {
		if child.FilterName != "" {
			normalizedKey := strings.ReplaceAll(strings.ToLower(child.FilterName), "_", "")
			filterNames[normalizedKey] = child.FilterName
		}
		if len(child.Children) > 0 {
			collectFilterNames(child.Children, filterNames)
		}
	}
}

/*
updateFiltersKeys updates the keys in the Filters map of ResourceGroupData to ensure they match the filter names
defined in the nested children of the query expression. This is necessary because JSON decoding/encoding can
convert keys to camel case, causing mismatches. The function normalizes the keys by removing underscores and
converting them to lower case, then compares them with the filter names. If a mismatch is found, the key is
updated to the value in RGExpression.Children
*/
func updateFiltersKeys(data *ResourceGroupData) {
	if data.Query == nil || data.Query.Expression == nil {
		return
	}

	filterNames := make(map[string]string)
	collectFilterNames(data.Query.Expression.Children, filterNames)

	updatedFilters := make(map[string]*RGFilter)
	for key, value := range data.Query.Filters {
		normalizedKey := strings.ReplaceAll(strings.ToLower(key), "_", "")
		if _, exists := filterNames[normalizedKey]; exists {
			updatedFilters[filterNames[normalizedKey]] = value
		} else {
			updatedFilters[key] = value
		}
	}

	data.Query.Filters = updatedFilters
}

func (group *ResourceGroupData) ResetResourceGUID() {
	group.ResourceGroupGuid = ""
	group.UpdatedBy = ""
	group.UpdatedTime = nil
	group.CreatedBy = ""
	group.CreatedTime = nil
	group.IsDefaultBoolean = nil
}

func (svc *ResourceGroupsService) Delete(guid string) error {
	if guid == "" {
		return errors.New("specify a resourceGuid")
	}

	return svc.client.RequestDecoder(
		"DELETE",
		fmt.Sprintf(apiV2ResourceGroupsFromGUID, guid),
		nil,
		nil,
	)
}

func (svc *ResourceGroupsService) Get(guid string, response interface{}) error {
	var rawResponse ResourceGroupResponse

	err := svc.get(guid, &rawResponse)
	if err != nil {
		return err
	}

	err = sanitizeFieldsInRawResponse(&rawResponse, response)
	if err != nil {
		return err
	}

	return nil
}

func (svc *ResourceGroupsService) create(data interface{}, response interface{}) error {
	return svc.client.RequestEncoderDecoder("POST", apiV2ResourceGroups, data, response)
}

func (svc *ResourceGroupsService) get(guid string, response interface{}) error {
	if guid == "" {
		return errors.New("specify an resource group guid")
	}
	apiPath := fmt.Sprintf(apiV2ResourceGroupsFromGUID, guid)
	return svc.client.RequestDecoder("GET", apiPath, nil, response)
}

func (svc *ResourceGroupsService) update(guid string, data interface{}, response interface{}) error {
	if guid == "" {
		return errors.New("specify a resource group guid")
	}

	apiPath := fmt.Sprintf(apiV2ResourceGroupsFromGUID, guid)
	return svc.client.RequestEncoderDecoder("PATCH", apiPath, data, response)
}

type ResourceGroupsService struct {
	client *Client
}

type RGExpression struct {
	Operator string     `json:"operator"`
	Children []*RGChild `json:"children"`
}

type RGChild struct {
	Operator   string     `json:"operator,omitempty"`
	FilterName string     `json:"filterName,omitempty"`
	Children   []*RGChild `json:"children,omitempty"`
}

type RGFilter struct {
	Field     string   `json:"field"`
	Operation string   `json:"operation"`
	Values    []string `json:"values"`
	Key       string   `json:"key,omitempty"`
}

type RGQuery struct {
	Filters    map[string]*RGFilter `json:"filters"`
	Expression *RGExpression        `json:"expression"`
}

// String returns the string representation of a Resource Group type
func (i resourceGroupType) String() string {
	return ResourceGroupTypes[i].resourceGroupType
}

// QueryTemplate returns the resource group type's query template
func (i resourceGroupType) QueryTemplate() string {
	return ResourceGroupTypes[i].queryTemplate
}

// FindResourceGroupType looks up inside the list of available resource group types
// the matching type from the provided string, if none, returns NoneResourceGroup
func FindResourceGroupType(typ string) (resourceGroupType, bool) {
	for i, ctx := range ResourceGroupTypes {
		if typ == ctx.resourceGroupType {
			return i, true
		}
	}
	return NoneResourceGroup, false
}

func (group *ResourceGroupData) ID() string {
	return group.ResourceGroupGuid
}

type ResourceGroupResponse struct {
	Data ResourceGroupData `json:"data"`
}

type ResourceGroupsResponse struct {
	Data []ResourceGroupData `json:"data"`
}

type ResourceGroupData struct {
	Name              string     `json:"name,omitempty"`
	Query             *RGQuery   `json:"query,omitempty"`
	Description       string     `json:"description,omitempty"`
	ResourceGroupGuid string     `json:"resourceGroupGuid,omitempty"`
	CreatedTime       *time.Time `json:"createdTime,omitempty"`
	CreatedBy         string     `json:"createdBy,omitempty"`
	UpdatedTime       *time.Time `json:"updatedTime,omitempty"`
	UpdatedBy         string     `json:"updatedBy,omitempty"`
	IsDefaultBoolean  *bool      `json:"isDefaultBoolean,omitempty"`
	Type              string     `json:"resourceType"`
	Enabled           int        `json:"enabled"`
}