package cosmosdb

import (
	"fmt"
	"net"

	"github.com/Azure/open-service-broker-azure/pkg/ptr"
	"github.com/Azure/open-service-broker-azure/pkg/schemas"
	"github.com/Azure/open-service-broker-azure/pkg/service"
)

func generateUpdatingParamsSchema() service.InputParametersSchema {
	const maxStalenessPrefixMin = 1
	const maxStalenessPrefixMax = 2147483647
	const maxIntervalInSecondsMin = 5
	const maxIntervalInSecondsMax = 86400
	return service.InputParametersSchema{
		PropertySchemas: map[string]service.PropertySchema{
			"tags": &service.ObjectPropertySchema{
				Title: "Tags",
				Description: "Tags to be applied to new resources," +
					" specified as key/value pairs.",
				Additional: &service.StringPropertySchema{},
			},
			"readRegions": &service.ArrayPropertySchema{
				Title: "Read Regions",
				Description: "Read regions to be created, your data" +
					" will be synchronized across these regions.",
				DefaultValue:            []interface{}{},
				CustomPropertyValidator: readLocationsValidator,
				ItemsSchema: &service.StringPropertySchema{
					OneOf: allowedReadLocations(),
				},
			},
			"autoFailoverEnabled": &service.StringPropertySchema{
				Title: "Auto failover enabled",
				Description: "Specifies if you want Cosmos DB to perform" +
					" automatic failover of the write region to one of" +
					" the read regions in the rare event of a data center outage.",
				DefaultValue: "disabled",
				OneOf:        schemas.EnabledDisabledValues(),
			},
			"ipFilters": &service.ObjectPropertySchema{
				Title:       "IP filters",
				Description: "IP Range Filter to be applied to new CosmosDB account",
				PropertySchemas: map[string]service.PropertySchema{
					"allowAccessFromAzure": &service.StringPropertySchema{
						Title: "Allow access from Azure",
						Description: "Specifies if Azure Services should be able to access" +
							" the CosmosDB account.",
						OneOf:        schemas.EnabledDisabledValues(),
						DefaultValue: schemas.EnabledParamString,
					},
					"allowAccessFromPortal": &service.StringPropertySchema{
						Title: "Allow access From Portal",
						Description: "Specifies if the Azure Portal should be able to" +
							" access the CosmosDB account. If `allowAccessFromAzure` is" +
							" set to enabled, this value is ignored.",
						OneOf:        schemas.EnabledDisabledValues(),
						DefaultValue: schemas.EnabledParamString,
					},
					"allowedIPRanges": &service.ArrayPropertySchema{
						Title: "Allowed IP ranges",
						Description: "Values to include in IP Filter. " +
							"Can be an IP Address or CIDR range.",
						ItemsSchema: &service.StringPropertySchema{
							Description:             "Must be a valid IP address or CIDR",
							CustomPropertyValidator: ipRangeValidator,
						},
					},
				},
				DefaultValue: map[string]interface{}{
					"allowAccessFromAzure": schemas.EnabledParamString,
				},
			},
			"consistencyPolicy": &service.ObjectPropertySchema{
				Title:       "Consistency policy",
				Description: "The consistency policy for the Cosmos DB account.",
				RequiredProperties: []string{
					"defaultConsistencyLevel",
				},
				PropertySchemas: map[string]service.PropertySchema{
					"defaultConsistencyLevel": &service.StringPropertySchema{
						Title: "Default consistency level",
						Description: "The default consistency level and" +
							" configuration settings of the Cosmos DB account.",
						OneOf: []service.EnumValue{
							{Value: "Eventual", Title: "Eventual"},
							{Value: "Session", Title: "Session"},
							{Value: "BoundedStaleness", Title: "Bounded staleness"},
							{Value: "Strong", Title: "Strong"},
							{Value: "ConsistentPrefix", Title: "Consistent prefix"},
						},
					},
					"boundedStaleness": &service.ObjectPropertySchema{
						Title: "Bounded staleness",
						Description: "The staleness settings when using " +
							"BoundedStaleness consistency.  Required when " +
							"using BoundedStaleness",
						RequiredProperties: []string{
							"maxStalenessPrefix",
							"maxIntervalInSeconds",
						},
						PropertySchemas: map[string]service.PropertySchema{
							"maxStalenessPrefix": &service.IntPropertySchema{
								Title: "Maximum staleness prefix",
								Description: "When used with the Bounded Staleness " +
									"consistency level, this value represents the number of " +
									"stale requests tolerated" +
									"Required when defaultConsistencyPolicy is set to " +
									" 'BoundedStaleness'.",
								MinValue: ptr.ToInt64(maxStalenessPrefixMin),
								MaxValue: ptr.ToInt64(maxStalenessPrefixMax),
							},
							"maxIntervalInSeconds": &service.IntPropertySchema{
								Title: "Maximum interval in seconds",
								Description: "When used with the Bounded Staleness " +
									"consistency level, this value represents the time " +
									"amount of staleness (in seconds) tolerated. " +
									"Required when defaultConsistencyPolicy is set to " +
									" 'BoundedStaleness'.",
								MinValue: ptr.ToInt64(maxIntervalInSecondsMin),
								MaxValue: ptr.ToInt64(maxIntervalInSecondsMax),
							},
						},
					},
				},
				CustomPropertyValidator: consistencyPolicyValidator,
			},
		},
	}
}

func generateProvisioningParamsSchema() service.InputParametersSchema {
	propertySchemas := map[string]service.PropertySchema{
		"resourceGroup": schemas.GetResourceGroupSchema(),
		"location":      schemas.GetLocationSchema(),
		"multipleWriteRegionsEnabled": &service.StringPropertySchema{
			Title: "Multiple write regions enabled",
			Description: "Specifies if you want  the account to write " +
				"in multiple regions.",
			OneOf:        schemas.EnabledDisabledValues(),
			DefaultValue: schemas.DisabledParamString,
		},
	}
	sharedSchema := generateUpdatingParamsSchema()
	for k, v := range sharedSchema.PropertySchemas {
		propertySchemas[k] = v
	}
	return service.InputParametersSchema{
		RequiredProperties: []string{"location", "resourceGroup"},
		PropertySchemas:    propertySchemas,
	}
}

func generateMongoDBProvisionParamsSchema() service.InputParametersSchema {
	sharedProvisioningSchema := generateProvisioningParamsSchema()
	sharedProvisioningSchema.PropertySchemas["version"] = &service.StringPropertySchema{ // nolint: lll
		Title: "The version of MongoDB api",
		Description: "Specifies the version you want to use " +
			"in created MongoDB acccount.",
		AllowedValues: []string{mongoDBVersion32, mongoDBVersion36},
		DefaultValue:  mongoDBVersion32,
	}
	return sharedProvisioningSchema
}

func ipRangeValidator(context, value string) error {
	ip := net.ParseIP(value)
	if ip == nil {
		cidr, _, _ := net.ParseCIDR(value)
		if cidr == nil {
			return service.NewValidationError(
				context,
				fmt.Sprintf(`"%s" is neither a valid IP address or CIDR range`, value),
			)
		}
	}
	return nil
}

// consistencyPolicyValidator is used to validate that the boundedStaleness
// object has been included in the parameters if "BoundedStaleness" was the
// selected default consistency policy. No further validation of the
// boundedStaleness (other than checking that it exists) is carried out here
// because the schema-based validations have all the rest of that covered.
func consistencyPolicyValidator(
	context string,
	valMap map[string]interface{},
) error {
	defaultConsistencyLevel := valMap["defaultConsistencyLevel"].(string)
	if defaultConsistencyLevel == "BoundedStaleness" {
		_, ok := valMap["boundedStaleness"].(map[string]interface{})
		if !ok {
			return service.NewValidationError(
				fmt.Sprintf("%s.boundedStaleness", context),
				"field is required",
			)
		}
	}
	return nil
}

func readLocationsValidator(
	context string,
	value []interface{},
) error {
	locations := make([]string, len(value))
	for i := range value {
		locations[i] = value[i].(string)
	}
	return validateReadLocations(context, locations)
}
