package bindingmanage

import (
	"fmt"
	"regexp"
	"strings"
)

const (
	defaultPageLimit       = 50
	maxPageLimit           = 100
	maxIdempotencyKeyBytes = 128
	maxDescriptionBytes    = 64 << 10
)

var bindingKeyPattern = regexp.MustCompile(`^[a-z][a-z0-9_-]{0,63}$`)

func requiredID(name string, value string) (string, error) {
	value = strings.TrimSpace(value)
	if value == "" {
		return "", invalidRequest(name + " is required")
	}
	return value, nil
}

func requiredScope(workspaceID string, bindingID string) (string, string, error) {
	workspaceID, err := requiredID("workspace id", workspaceID)
	if err != nil {
		return "", "", err
	}
	bindingID, err = requiredID("Binding id", bindingID)
	if err != nil {
		return "", "", err
	}
	return workspaceID, bindingID, nil
}

func normalizeActor(actor Actor) (Actor, error) {
	actor.Type = strings.TrimSpace(actor.Type)
	actor.ID = strings.TrimSpace(actor.ID)
	actor.MemberID = strings.TrimSpace(actor.MemberID)
	if actor.Type != ActorTypeHuman && actor.Type != ActorTypeAgent {
		return Actor{}, invalidRequest("actor type must be human or agent")
	}
	if actor.ID == "" || actor.MemberID == "" {
		return Actor{}, invalidRequest("actor id and member id are required")
	}
	return actor, nil
}

func normalizeOwner(owner Owner) (Owner, error) {
	owner.UserID = strings.TrimSpace(owner.UserID)
	owner.MemberID = strings.TrimSpace(owner.MemberID)
	if owner.UserID == "" || owner.MemberID == "" {
		return Owner{}, invalidRequest("Binding owner user id and member id are required")
	}
	return owner, nil
}

func normalizeBindingKey(value string) (string, error) {
	value = strings.TrimSpace(value)
	if !bindingKeyPattern.MatchString(value) {
		return "", invalidRequest("Binding key must match " + bindingKeyPattern.String())
	}
	return value, nil
}

func normalizeDisplayName(value string) (string, error) {
	value = strings.TrimSpace(value)
	if value == "" {
		return "", invalidRequest("display name is required")
	}
	if len([]byte(value)) > 255 {
		return "", invalidRequest("display name must not exceed 255 bytes")
	}
	return value, nil
}

func normalizeDescription(value string) (string, error) {
	value = strings.TrimSpace(value)
	if len([]byte(value)) > maxDescriptionBytes {
		return "", invalidRequest(fmt.Sprintf("description must not exceed %d bytes", maxDescriptionBytes))
	}
	return value, nil
}

func normalizeIdempotencyKey(value string) (string, error) {
	value = strings.TrimSpace(value)
	if value == "" {
		return "", invalidRequest("idempotency key is required")
	}
	if len([]byte(value)) > maxIdempotencyKeyBytes {
		return "", invalidRequest(fmt.Sprintf("idempotency key must not exceed %d bytes", maxIdempotencyKeyBytes))
	}
	for _, character := range value {
		if character < 0x21 || character > 0x7e {
			return "", invalidRequest("idempotency key must contain only visible ASCII characters")
		}
	}
	return value, nil
}

func positiveRevision(name string, value int64) error {
	if value < 1 {
		return invalidRequest(name + " must be a positive integer")
	}
	return nil
}

func normalizeList(input ListInput) (ListInput, error) {
	var err error
	input.WorkspaceID, err = requiredID("workspace id", input.WorkspaceID)
	if err != nil {
		return ListInput{}, err
	}
	input.PlanID = strings.TrimSpace(input.PlanID)
	input.State = strings.TrimSpace(input.State)
	input.Cursor = strings.TrimSpace(input.Cursor)
	if input.State != "" && input.State != string(BindingStateDisabled) && input.State != string(BindingStateEnabled) && input.State != string(BindingStateArchived) {
		return ListInput{}, invalidRequest("state must be disabled, enabled, or archived")
	}
	if input.Limit == 0 {
		input.Limit = defaultPageLimit
	}
	if input.Limit < 1 || input.Limit > maxPageLimit {
		return ListInput{}, invalidRequest(fmt.Sprintf("limit must be between 1 and %d", maxPageLimit))
	}
	return input, nil
}
