package cloudflare

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"strconv"
	"time"

	"github.com/goccy/go-json"
)

// APIShieldSchema represents a schema stored in API Shield Schema Validation 2.0.
type APIShieldSchema struct {
	// ID represents the ID of the schema
	ID string `json:"schema_id"`
	// Name represents the name of the schema
	Name string `json:"name"`
	// Kind of the schema
	Kind string `json:"kind"`
	// Source is the contents of the schema
	Source string `json:"source,omitempty"`
	// CreatedAt is the time the schema was created
	CreatedAt *time.Time `json:"created_at,omitempty"`
	// ValidationEnabled controls if schema is used for validation
	ValidationEnabled bool `json:"validation_enabled,omitempty"`
}

// CreateAPIShieldSchemaParams represents the parameters to pass when creating a schema in Schema Validation 2.0.
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/user_schemas/methods/create/
type CreateAPIShieldSchemaParams struct {
	// Source is a io.Reader containing the contents of the schema
	Source io.Reader
	// Name represents the name of the schema.
	Name string
	// Kind of the schema. This is always set to openapi_v3.
	Kind string
	// ValidationEnabled controls if schema is used for validation
	ValidationEnabled *bool
}

// GetAPIShieldSchemaParams represents the parameters to pass when retrieving a schema with a given schema ID.
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/user_schemas/methods/get/
type GetAPIShieldSchemaParams struct {
	// SchemaID is the ID of the schema to retrieve
	SchemaID string `url:"-"`

	// OmitSource specifies whether the contents of the schema should be returned in the "Source" field.
	OmitSource *bool `url:"omit_source,omitempty"`
}

// ListAPIShieldSchemasParams represents the parameters to pass when retrieving all schemas.
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/user_schemas/methods/list/
type ListAPIShieldSchemasParams struct {
	// OmitSource specifies whether the contents of the schema should be returned in the "Source" field.
	OmitSource *bool `url:"omit_source,omitempty"`

	// ValidationEnabled specifies whether to return only schemas that have validation enabled.
	ValidationEnabled *bool `url:"validation_enabled,omitempty"`

	// PaginationOptions to apply to the request.
	PaginationOptions
}

// DeleteAPIShieldSchemaParams represents the parameters to pass to delete a schema.
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/user_schemas/methods/delete/
type DeleteAPIShieldSchemaParams struct {
	// SchemaID is the schema to be deleted
	SchemaID string `url:"-"`
}

// UpdateAPIShieldSchemaParams represents the parameters to pass to patch certain fields on an existing schema
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/user_schemas/methods/edit/
type UpdateAPIShieldSchemaParams struct {
	// SchemaID is the schema to be patched
	SchemaID string `json:"-" url:"-"`

	// ValidationEnabled controls if schema is used for validation
	ValidationEnabled *bool `json:"validation_enabled" url:"-"`
}

// APIShieldGetSchemaResponse represents the response from the GET api_gateway/user_schemas/{id} endpoint.
type APIShieldGetSchemaResponse struct {
	Result APIShieldSchema `json:"result"`
	Response
}

// APIShieldListSchemasResponse represents the response from the GET api_gateway/user_schemas endpoint.
type APIShieldListSchemasResponse struct {
	Result     []APIShieldSchema `json:"result"`
	ResultInfo `json:"result_info"`
	Response
}

// APIShieldCreateSchemaResponse represents the response from the POST api_gateway/user_schemas endpoint.
type APIShieldCreateSchemaResponse struct {
	Result APIShieldCreateSchemaResult `json:"result"`
	Response
}

// APIShieldDeleteSchemaResponse represents the response from the DELETE api_gateway/user_schemas/{id} endpoint.
type APIShieldDeleteSchemaResponse struct {
	Result interface{} `json:"result"`
	Response
}

// APIShieldPatchSchemaResponse represents the response from the PATCH api_gateway/user_schemas/{id} endpoint.
type APIShieldPatchSchemaResponse struct {
	Result APIShieldSchema `json:"result"`
	Response
}

// APIShieldCreateSchemaResult represents the successful result of creating a schema in Schema Validation 2.0.
type APIShieldCreateSchemaResult struct {
	// APIShieldSchema is the schema that was created
	Schema APIShieldSchema `json:"schema"`
	// APIShieldCreateSchemaEvents are non-critical event logs that occurred during processing.
	Events APIShieldCreateSchemaEvents `json:"upload_details"`
}

// APIShieldCreateSchemaEvents are event logs that occurred during processing.
//
// The logs are separated into levels of severity.
type APIShieldCreateSchemaEvents struct {
	Critical *APIShieldCreateSchemaEventWithLocation   `json:"critical,omitempty"`
	Errors   []APIShieldCreateSchemaEventWithLocations `json:"errors,omitempty"`
	Warnings []APIShieldCreateSchemaEventWithLocations `json:"warnings,omitempty"`
}

// APIShieldCreateSchemaEvent is an event log that occurred during processing.
type APIShieldCreateSchemaEvent struct {
	// Code identifies the event that occurred
	Code uint `json:"code"`
	// Message describes the event that occurred
	Message string `json:"message"`
}

// APIShieldCreateSchemaEventWithLocation is an event log that occurred during processing, with the location
// in the schema where the event occurred.
type APIShieldCreateSchemaEventWithLocation struct {
	APIShieldCreateSchemaEvent

	// Location is where the event occurred
	// See https://goessner.net/articles/JsonPath/ for JSONPath specification.
	Location string `json:"location,omitempty"`
}

// APIShieldCreateSchemaEventWithLocations is an event log that occurred during processing, with the location(s)
// in the schema where the event occurred.
type APIShieldCreateSchemaEventWithLocations struct {
	APIShieldCreateSchemaEvent

	// Locations lists JSONPath locations where the event occurred
	// See https://goessner.net/articles/JsonPath/ for JSONPath specification
	Locations []string `json:"locations"`
}

func (cse APIShieldCreateSchemaEventWithLocations) String() string {
	var s string
	s += cse.Message

	if len(cse.Locations) == 0 || (len(cse.Locations) == 1 && cse.Locations[0] == "") {
		// append nothing
	} else if len(cse.Locations) == 1 {
		s += fmt.Sprintf(" (%s)", cse.Locations[0])
	} else {
		s += " (multiple locations)"
	}

	return s
}

// GetAPIShieldSchema retrieves information about a specific schema on a zone
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/user_schemas/methods/get/
func (api *API) GetAPIShieldSchema(ctx context.Context, rc *ResourceContainer, params GetAPIShieldSchemaParams) (*APIShieldSchema, error) {
	if params.SchemaID == "" {
		return nil, fmt.Errorf("schema ID must be provided")
	}

	path := fmt.Sprintf("/zones/%s/api_gateway/user_schemas/%s", rc.Identifier, params.SchemaID)

	uri := buildURI(path, params)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, err
	}

	var asResponse APIShieldGetSchemaResponse
	err = json.Unmarshal(res, &asResponse)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return &asResponse.Result, nil
}

// ListAPIShieldSchemas retrieves all schemas for a zone
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/user_schemas/methods/list/
func (api *API) ListAPIShieldSchemas(ctx context.Context, rc *ResourceContainer, params ListAPIShieldSchemasParams) ([]APIShieldSchema, ResultInfo, error) {
	path := fmt.Sprintf("/zones/%s/api_gateway/user_schemas", rc.Identifier)

	uri := buildURI(path, params)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, ResultInfo{}, err
	}

	var asResponse APIShieldListSchemasResponse
	err = json.Unmarshal(res, &asResponse)
	if err != nil {
		return nil, ResultInfo{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return asResponse.Result, asResponse.ResultInfo, nil
}

// CreateAPIShieldSchema uploads a schema to a zone
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/user_schemas/methods/create/
func (api *API) CreateAPIShieldSchema(ctx context.Context, rc *ResourceContainer, params CreateAPIShieldSchemaParams) (*APIShieldCreateSchemaResult, error) {
	uri := fmt.Sprintf("/zones/%s/api_gateway/user_schemas", rc.Identifier)

	if params.Name == "" {
		return nil, fmt.Errorf("name must not be empty")
	}

	if params.Source == nil {
		return nil, fmt.Errorf("source must not be nil")
	}

	// Prepare the form to be submitted
	var b bytes.Buffer
	w := multipart.NewWriter(&b)
	// write fields
	if err := w.WriteField("name", params.Name); err != nil {
		return nil, fmt.Errorf("error during multi-part form construction: %w", err)
	}
	if err := w.WriteField("kind", params.Kind); err != nil {
		return nil, fmt.Errorf("error during multi-part form construction: %w", err)
	}

	if params.ValidationEnabled != nil {
		if err := w.WriteField("validation_enabled", strconv.FormatBool(*params.ValidationEnabled)); err != nil {
			return nil, fmt.Errorf("error during multi-part form construction: %w", err)
		}
	}

	// write schema contents
	part, err := w.CreateFormFile("file", params.Name)
	if err != nil {
		return nil, fmt.Errorf("error during multi-part form construction: %w", err)
	}
	if _, err := io.Copy(part, params.Source); err != nil {
		return nil, fmt.Errorf("error during multi-part form construction: %w", err)
	}
	if err := w.Close(); err != nil {
		return nil, fmt.Errorf("error during multi-part form construction: %w", err)
	}

	res, err := api.makeRequestContextWithHeaders(ctx, http.MethodPost, uri, &b, http.Header{
		"Content-Type": []string{w.FormDataContentType()},
	})
	if err != nil {
		return nil, err
	}

	var asResponse APIShieldCreateSchemaResponse
	err = json.Unmarshal(res, &asResponse)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return &asResponse.Result, nil
}

// DeleteAPIShieldSchema deletes a single schema
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/user_schemas/methods/delete/
func (api *API) DeleteAPIShieldSchema(ctx context.Context, rc *ResourceContainer, params DeleteAPIShieldSchemaParams) error {
	if params.SchemaID == "" {
		return fmt.Errorf("schema ID must be provided")
	}

	uri := fmt.Sprintf("/zones/%s/api_gateway/user_schemas/%s", rc.Identifier, params.SchemaID)

	res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	if err != nil {
		return err
	}

	var asResponse APIShieldDeleteSchemaResponse
	err = json.Unmarshal(res, &asResponse)
	if err != nil {
		return fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return nil
}

// UpdateAPIShieldSchema updates certain fields on an existing schema.
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/user_schemas/methods/edit/
func (api *API) UpdateAPIShieldSchema(ctx context.Context, rc *ResourceContainer, params UpdateAPIShieldSchemaParams) (*APIShieldSchema, error) {
	if params.SchemaID == "" {
		return nil, fmt.Errorf("schema ID must be provided")
	}

	uri := fmt.Sprintf("/zones/%s/api_gateway/user_schemas/%s", rc.Identifier, params.SchemaID)

	res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params)
	if err != nil {
		return nil, err
	}

	// Result should be the updated schema that was patched
	var asResponse APIShieldPatchSchemaResponse
	err = json.Unmarshal(res, &asResponse)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return &asResponse.Result, nil
}

// Schema Validation Settings

// APIShieldSchemaValidationSettings represents zone level schema validation settings for
// API Shield Schema Validation 2.0.
type APIShieldSchemaValidationSettings struct {
	// DefaultMitigationAction is the mitigation to apply when there is no operation-level
	// mitigation action defined
	DefaultMitigationAction string `json:"validation_default_mitigation_action" url:"-"`
	// OverrideMitigationAction when set, will apply to all requests regardless of
	// zone-level/operation-level setting
	OverrideMitigationAction *string `json:"validation_override_mitigation_action" url:"-"`
}

// UpdateAPIShieldSchemaValidationSettingsParams represents the parameters to pass to update certain fields
// on schema validation settings on the zone
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/settings/subresources/schema_validation/methods/edit/
type UpdateAPIShieldSchemaValidationSettingsParams struct {
	// DefaultMitigationAction is the mitigation to apply when there is no operation-level
	// mitigation action defined
	//
	// passing a `nil` value will have no effect on this setting
	DefaultMitigationAction *string `json:"validation_default_mitigation_action" url:"-"`

	// OverrideMitigationAction when set, will apply to all requests regardless of
	// zone-level/operation-level setting
	//
	// passing a `nil` value will have no effect on this setting
	OverrideMitigationAction *string `json:"validation_override_mitigation_action" url:"-"`
}

// APIShieldSchemaValidationSettingsResponse represents the response from the GET api_gateway/settings/schema_validation endpoint.
type APIShieldSchemaValidationSettingsResponse struct {
	Result APIShieldSchemaValidationSettings `json:"result"`
	Response
}

// GetAPIShieldSchemaValidationSettings retrieves zone level schema validation settings
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/settings/subresources/schema_validation/methods/get/
func (api *API) GetAPIShieldSchemaValidationSettings(ctx context.Context, rc *ResourceContainer) (*APIShieldSchemaValidationSettings, error) {
	path := fmt.Sprintf("/zones/%s/api_gateway/settings/schema_validation", rc.Identifier)

	uri := buildURI(path, nil)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, err
	}

	var asResponse APIShieldSchemaValidationSettingsResponse
	err = json.Unmarshal(res, &asResponse)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return &asResponse.Result, nil
}

// UpdateAPIShieldSchemaValidationSettings updates certain fields on zone level schema validation settings
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/settings/subresources/schema_validation/methods/edit/
func (api *API) UpdateAPIShieldSchemaValidationSettings(ctx context.Context, rc *ResourceContainer, params UpdateAPIShieldSchemaValidationSettingsParams) (*APIShieldSchemaValidationSettings, error) {
	path := fmt.Sprintf("/zones/%s/api_gateway/settings/schema_validation", rc.Identifier)

	uri := buildURI(path, params)

	res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params)
	if err != nil {
		return nil, err
	}

	var asResponse APIShieldSchemaValidationSettingsResponse
	err = json.Unmarshal(res, &asResponse)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return &asResponse.Result, nil
}

// APIShieldOperationSchemaValidationSettings represents operation level schema validation settings for
// API Shield Schema Validation 2.0.
type APIShieldOperationSchemaValidationSettings struct {
	// MitigationAction is the mitigation to apply to the operation
	MitigationAction *string `json:"mitigation_action" url:"-"`
}

// GetAPIShieldOperationSchemaValidationSettingsParams represents the parameters to pass to retrieve
// the schema validation settings set on the operation.
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/operations/subresources/schema_validation/methods/get/
type GetAPIShieldOperationSchemaValidationSettingsParams struct {
	// The Operation ID to apply the mitigation action to
	OperationID string `url:"-"`
}

// UpdateAPIShieldOperationSchemaValidationSettings maps operation IDs to APIShieldOperationSchemaValidationSettings
//
// # This can be used to bulk update operations in one call
//
// Example:
//
//	UpdateAPIShieldOperationSchemaValidationSettings{
//			"99522293-a505-45e5-bbad-bbc339f5dc40": APIShieldOperationSchemaValidationSettings{ MitigationAction: nil },
//	}
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/operations/subresources/schema_validation/methods/edit/
type UpdateAPIShieldOperationSchemaValidationSettings map[string]APIShieldOperationSchemaValidationSettings

// APIShieldOperationSchemaValidationSettingsResponse represents the response from the GET api_gateway/operation/{operationID}/schema_validation endpoint.
type APIShieldOperationSchemaValidationSettingsResponse struct {
	Result APIShieldOperationSchemaValidationSettings `json:"result"`
	Response
}

// UpdateAPIShieldOperationSchemaValidationSettingsResponse represents the response from the PATCH api_gateway/operations/schema_validation endpoint.
type UpdateAPIShieldOperationSchemaValidationSettingsResponse struct {
	Result UpdateAPIShieldOperationSchemaValidationSettings `json:"result"`
	Response
}

// GetAPIShieldOperationSchemaValidationSettings retrieves operation level schema validation settings
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/operations/subresources/schema_validation/methods/get/
func (api *API) GetAPIShieldOperationSchemaValidationSettings(ctx context.Context, rc *ResourceContainer, params GetAPIShieldOperationSchemaValidationSettingsParams) (*APIShieldOperationSchemaValidationSettings, error) {
	if params.OperationID == "" {
		return nil, fmt.Errorf("operation ID must be provided")
	}

	path := fmt.Sprintf("/zones/%s/api_gateway/operations/%s/schema_validation", rc.Identifier, params.OperationID)

	uri := buildURI(path, nil)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, params)
	if err != nil {
		return nil, err
	}

	var asResponse APIShieldOperationSchemaValidationSettingsResponse
	err = json.Unmarshal(res, &asResponse)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return &asResponse.Result, nil
}

// UpdateAPIShieldOperationSchemaValidationSettings update multiple operation level schema validation settings
//
// API documentation: https://developers.cloudflare.com/api/resources/api_gateway/subresources/operations/subresources/schema_validation/methods/edit/
func (api *API) UpdateAPIShieldOperationSchemaValidationSettings(ctx context.Context, rc *ResourceContainer, params UpdateAPIShieldOperationSchemaValidationSettings) (*UpdateAPIShieldOperationSchemaValidationSettings, error) {
	path := fmt.Sprintf("/zones/%s/api_gateway/operations/schema_validation", rc.Identifier)

	uri := buildURI(path, nil)

	res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params)
	if err != nil {
		return nil, err
	}

	var asResponse UpdateAPIShieldOperationSchemaValidationSettingsResponse
	err = json.Unmarshal(res, &asResponse)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return &asResponse.Result, nil
}
