package spec3

import (
	"math/rand"
	"strings"

	fuzz "github.com/google/gofuzz"

	"k8s.io/kube-openapi/pkg/validation/spec"
)

// refChance is the chance that a particular component will use a $ref
// instead of fuzzed. Expressed as a fraction 1/n, currently there is
// a 1/3 chance that a ref will be used.
const refChance = 3

const alphaNumChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func randAlphanumString() string {
	arr := make([]string, rand.Intn(10)+5)
	for i := 0; i < len(arr); i++ {
		arr[i] = string(alphaNumChars[rand.Intn(len(alphaNumChars))])
	}
	return strings.Join(arr, "")
}

var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
	func(s *string, c fuzz.Continue) {
		// All OpenAPI V3 map keys must follow the corresponding
		// regex. Note that this restricts the range for all other
		// string values as well.
		str := randAlphanumString()
		*s = str
	},
	func(o *OpenAPI, c fuzz.Continue) {
		c.FuzzNoCustom(o)
		o.Version = "3.0.0"
		for i, val := range o.SecurityRequirement {
			if val == nil {
				o.SecurityRequirement[i] = make(map[string][]string)
			}

			for k, v := range val {
				if v == nil {
					val[k] = make([]string, 0)
				}
			}
		}

	},
	func(r *interface{}, c fuzz.Continue) {
		switch c.Intn(3) {
		case 0:
			*r = nil
		case 1:
			n := c.RandString() + "x"
			*r = n
		case 2:
			n := c.Float64()
			*r = n
		}
	},
	func(v **spec.Info, c fuzz.Continue) {
		// Info is never nil
		*v = &spec.Info{}
		c.FuzzNoCustom(*v)
		(*v).Title = c.RandString() + "x"
	},
	func(v *Paths, c fuzz.Continue) {
		c.Fuzz(&v.VendorExtensible)
		num := c.Intn(5)
		if num > 0 {
			v.Paths = make(map[string]*Path)
		}
		for i := 0; i < num; i++ {
			val := Path{}
			c.Fuzz(&val)
			v.Paths["/"+c.RandString()] = &val
		}
	},
	func(v *SecurityScheme, c fuzz.Continue) {
		if c.Intn(refChance) == 0 {
			c.Fuzz(&v.Refable)
			return
		}
		switch c.Intn(4) {
		case 0:
			v.Type = "apiKey"
			v.Name = c.RandString() + "x"
			switch c.Intn(3) {
			case 0:
				v.In = "query"
			case 1:
				v.In = "header"
			case 2:
				v.In = "cookie"
			}
		case 1:
			v.Type = "http"
		case 2:
			v.Type = "oauth2"
			v.Flows = make(map[string]*OAuthFlow)
			flow := OAuthFlow{}
			flow.AuthorizationUrl = c.RandString() + "x"
			v.Flows["implicit"] = &flow
			flow.Scopes = make(map[string]string)
			flow.Scopes["foo"] = "bar"
		case 3:
			v.Type = "openIdConnect"
			v.OpenIdConnectUrl = "https://" + c.RandString()
		}
		v.Scheme = "basic"
	},
	func(v *spec.Ref, c fuzz.Continue) {
		switch c.Intn(7) {
		case 0:
			*v = spec.MustCreateRef("#/components/schemas/" + randAlphanumString())
		case 1:
			*v = spec.MustCreateRef("#/components/responses/" + randAlphanumString())
		case 2:
			*v = spec.MustCreateRef("#/components/headers/" + randAlphanumString())
		case 3:
			*v = spec.MustCreateRef("#/components/securitySchemes/" + randAlphanumString())
		case 5:
			*v = spec.MustCreateRef("#/components/parameters/" + randAlphanumString())
		case 6:
			*v = spec.MustCreateRef("#/components/requestBodies/" + randAlphanumString())
		}
	},
	func(v *Parameter, c fuzz.Continue) {
		if c.Intn(refChance) == 0 {
			c.Fuzz(&v.Refable)
			return
		}
		c.Fuzz(&v.ParameterProps)
		c.Fuzz(&v.VendorExtensible)

		switch c.Intn(3) {
		case 0:
			// Header param
			v.In = "query"
		case 1:
			v.In = "header"
		case 2:
			v.In = "cookie"
		}
	},
	func(v *RequestBody, c fuzz.Continue) {
		if c.Intn(refChance) == 0 {
			c.Fuzz(&v.Refable)
			return
		}
		c.Fuzz(&v.RequestBodyProps)
		c.Fuzz(&v.VendorExtensible)
	},
	func(v *Header, c fuzz.Continue) {
		if c.Intn(refChance) == 0 {
			c.Fuzz(&v.Refable)
			return
		}
		c.Fuzz(&v.HeaderProps)
		c.Fuzz(&v.VendorExtensible)
	},
	func(v *ResponsesProps, c fuzz.Continue) {
		c.Fuzz(&v.Default)
		n := c.Intn(5)
		for i := 0; i < n; i++ {
			r2 := Response{}
			c.Fuzz(&r2)
			// HTTP Status code in 100-599 Range
			code := c.Intn(500) + 100
			v.StatusCodeResponses = make(map[int]*Response)
			v.StatusCodeResponses[code] = &r2
		}
	},
	func(v *Response, c fuzz.Continue) {
		if c.Intn(refChance) == 0 {
			c.Fuzz(&v.Refable)
			return
		}
		c.Fuzz(&v.ResponseProps)
		c.Fuzz(&v.VendorExtensible)
	},
	func(v *Operation, c fuzz.Continue) {
		c.FuzzNoCustom(v)
		// Do not fuzz null values into the array.
		for i, val := range v.SecurityRequirement {
			if val == nil {
				v.SecurityRequirement[i] = make(map[string][]string)
			}

			for k, v := range val {
				if v == nil {
					val[k] = make([]string, 0)
				}
			}
		}
	},
	func(v *spec.Extensions, c fuzz.Continue) {
		numChildren := c.Intn(5)
		for i := 0; i < numChildren; i++ {
			if *v == nil {
				*v = spec.Extensions{}
			}
			(*v)["x-"+c.RandString()] = c.RandString()
		}
	},
	func(v *spec.ExternalDocumentation, c fuzz.Continue) {
		c.Fuzz(&v.Description)
		v.URL = "https://" + randAlphanumString()
	},
	func(v *spec.SchemaURL, c fuzz.Continue) {
		*v = spec.SchemaURL("https://" + randAlphanumString())
	},
	func(v *spec.SchemaOrBool, c fuzz.Continue) {
		*v = spec.SchemaOrBool{}

		if c.RandBool() {
			v.Allows = c.RandBool()
		} else {
			v.Schema = &spec.Schema{}
			v.Allows = true
			c.Fuzz(&v.Schema)
		}
	},
	func(v *spec.SchemaOrArray, c fuzz.Continue) {
		*v = spec.SchemaOrArray{}
		if c.RandBool() {
			schema := spec.Schema{}
			c.Fuzz(&schema)
			v.Schema = &schema
		} else {
			v.Schemas = []spec.Schema{}
			numChildren := c.Intn(5)
			for i := 0; i < numChildren; i++ {
				schema := spec.Schema{}
				c.Fuzz(&schema)
				v.Schemas = append(v.Schemas, schema)
			}

		}

	},
	func(v *spec.SchemaOrStringArray, c fuzz.Continue) {
		if c.RandBool() {
			*v = spec.SchemaOrStringArray{}
			if c.RandBool() {
				c.Fuzz(&v.Property)
			} else {
				c.Fuzz(&v.Schema)
			}
		}
	},
	func(v *spec.Schema, c fuzz.Continue) {
		if c.Intn(refChance) == 0 {
			c.Fuzz(&v.Ref)
			return
		}
		if c.RandBool() {
			// file schema
			c.Fuzz(&v.Default)
			c.Fuzz(&v.Description)
			c.Fuzz(&v.Example)
			c.Fuzz(&v.ExternalDocs)

			c.Fuzz(&v.Format)
			c.Fuzz(&v.ReadOnly)
			c.Fuzz(&v.Required)
			c.Fuzz(&v.Title)
			v.Type = spec.StringOrArray{"file"}

		} else {
			// normal schema
			c.Fuzz(&v.SchemaProps)
			c.Fuzz(&v.SwaggerSchemaProps)
			c.Fuzz(&v.VendorExtensible)
			c.Fuzz(&v.ExtraProps)
		}

	},
}
