// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.

package utils

import (
	"context"
	"encoding/base64"
	"fmt"
	"net/http"
	"reflect"
	"strings"
)

const (
	securityTagKey = "security"
)

type securityTag struct {
	Option  bool
	Scheme  bool
	Name    string
	Type    string
	SubType string
	Env     string
}

func PopulateSecurity(ctx context.Context, req *http.Request, securitySource func(context.Context) (interface{}, error)) error {
	if securitySource == nil {
		return nil
	}

	security, err := securitySource(ctx)
	if err != nil {
		return err
	}

	headers := make(map[string]string)
	queryParams := make(map[string]string)

	securityValType := trueReflectValue(reflect.ValueOf(security))
	securityStructType := securityValType.Type()

	if isNil(securityStructType, securityValType) {
		return nil
	}

	if securityStructType.Kind() == reflect.Ptr {
		securityStructType = securityStructType.Elem()
		securityValType = securityValType.Elem()
	}

	for i := 0; i < securityStructType.NumField(); i++ {
		fieldType := securityStructType.Field(i)
		valType := securityValType.Field(i)

		kind := valType.Kind()

		if isNil(fieldType.Type, valType) {
			continue
		}

		if fieldType.Type.Kind() == reflect.Pointer {
			kind = valType.Elem().Kind()
		}

		secTag := parseSecurityTag(fieldType)
		if secTag != nil {
			if secTag.Option {
				handleSecurityOption(headers, queryParams, valType.Interface())
			} else if secTag.Scheme {
				// Special case for basic auth which could be a flattened struct
				if secTag.SubType == "basic" && kind != reflect.Struct {
					parseSecurityScheme(headers, queryParams, secTag, security)
				} else {
					parseSecurityScheme(headers, queryParams, secTag, valType.Interface())
				}
			}
		}
	}

	for key, value := range headers {
		req.Header.Add(key, value)
	}

	query := req.URL.Query()
	for key, value := range queryParams {
		query.Add(key, value)
	}
	req.URL.RawQuery = query.Encode()

	return nil
}

func handleSecurityOption(headers, queryParams map[string]string, option interface{}) {
	optionValType := trueReflectValue(reflect.ValueOf(option))
	optionStructType := optionValType.Type()

	if isNil(optionStructType, optionValType) {
		return
	}

	for i := 0; i < optionStructType.NumField(); i++ {
		fieldType := optionStructType.Field(i)
		valType := optionValType.Field(i)

		secTag := parseSecurityTag(fieldType)
		if secTag != nil && secTag.Scheme {
			parseSecurityScheme(headers, queryParams, secTag, valType.Interface())
		}
	}
}

func parseSecurityScheme(headers, queryParams map[string]string, schemeTag *securityTag, scheme interface{}) {
	schemeVal := trueReflectValue(reflect.ValueOf(scheme))
	schemeType := schemeVal.Type()

	if isNil(schemeType, schemeVal) {
		return
	}

	if schemeType.Kind() == reflect.Struct {
		if schemeTag.Type == "http" {
			switch schemeTag.SubType {
			case "basic":
				handleBasicAuthScheme(headers, schemeVal.Interface())
				return
			case "custom":
				return
			}
		}

		for i := 0; i < schemeType.NumField(); i++ {
			fieldType := schemeType.Field(i)
			valType := schemeVal.Field(i)

			if isNil(fieldType.Type, valType) {
				continue
			}

			if fieldType.Type.Kind() == reflect.Ptr {
				valType = valType.Elem()
			}

			secTag := parseSecurityTag(fieldType)
			if secTag == nil || secTag.Name == "" {
				return
			}

			parseSecuritySchemeValue(headers, queryParams, schemeTag, secTag, valType.Interface())
		}
	} else {
		parseSecuritySchemeValue(headers, queryParams, schemeTag, schemeTag, schemeVal.Interface())
	}
}

func parseSecuritySchemeValue(headers, queryParams map[string]string, schemeTag *securityTag, secTag *securityTag, val interface{}) {
	switch schemeTag.Type {
	case "apiKey":
		switch schemeTag.SubType {
		case "header":
			headers[secTag.Name] = valToString(val)
		case "query":
			queryParams[secTag.Name] = valToString(val)
		case "cookie":
			headers["Cookie"] = fmt.Sprintf("%s=%s", secTag.Name, valToString(val))
		default:
			panic("not supported")
		}
	case "openIdConnect":
		headers[secTag.Name] = prefixBearer(valToString(val))
	case "oauth2":
		if schemeTag.SubType != "client_credentials" {
			headers[secTag.Name] = prefixBearer(valToString(val))
		}
	case "http":
		switch schemeTag.SubType {
		case "bearer":
			headers[secTag.Name] = prefixBearer(valToString(val))
		case "custom":
		default:
			panic("not supported")
		}
	default:
		panic("not supported")
	}
}

func prefixBearer(authHeaderValue string) string {
	if strings.HasPrefix(strings.ToLower(authHeaderValue), "bearer ") {
		return authHeaderValue
	}

	return fmt.Sprintf("Bearer %s", authHeaderValue)
}

func handleBasicAuthScheme(headers map[string]string, scheme interface{}) {
	schemeStructType := reflect.TypeOf(scheme)
	schemeValType := reflect.ValueOf(scheme)

	var username, password string

	for i := 0; i < schemeStructType.NumField(); i++ {
		fieldType := schemeStructType.Field(i)
		valType := schemeValType.Field(i)

		if fieldType.Type.Kind() == reflect.Ptr {
			valType = valType.Elem()
		}

		secTag := parseSecurityTag(fieldType)
		if secTag == nil || secTag.Name == "" {
			continue
		}

		switch secTag.Name {
		case "username":
			username = valType.String()
		case "password":
			password = valType.String()
		}
	}

	headers["Authorization"] = fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password))))
}

func parseSecurityTag(field reflect.StructField) *securityTag {
	tag := field.Tag.Get(securityTagKey)
	if tag == "" {
		return nil
	}

	option := false
	scheme := false
	name := ""
	securityType := ""
	securitySubType := ""
	env := ""

	options := strings.Split(tag, ",")
	for _, optionConf := range options {
		parts := strings.Split(optionConf, "=")
		if len(parts) < 1 || len(parts) > 2 {
			continue
		}

		switch parts[0] {
		case "name":
			name = parts[1]
		case "type":
			securityType = parts[1]
		case "subtype":
			securitySubType = parts[1]
		case "option":
			option = true
		case "scheme":
			scheme = true
		case "env":
			env = parts[1]
		}
	}

	// TODO: validate tag?

	return &securityTag{
		Option:  option,
		Scheme:  scheme,
		Name:    name,
		Type:    securityType,
		SubType: securitySubType,
		Env:     env,
	}
}

func trueReflectValue(val reflect.Value) reflect.Value {
	kind := val.Type().Kind()
	for kind == reflect.Interface || kind == reflect.Ptr {
		innerVal := val.Elem()
		if !innerVal.IsValid() {
			break
		}
		val = innerVal
		kind = val.Type().Kind()
	}
	return val
}
