package duo

import (
	"context"
	"errors"
	"strings"

	"github.com/hashicorp/vault/logical"
	"github.com/hashicorp/vault/logical/framework"
)

func pathDuoConfig() *framework.Path {
	return &framework.Path{
		Pattern: `duo/config`,
		Fields: map[string]*framework.FieldSchema{
			"user_agent": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "User agent to connect to Duo (default \"\")",
			},
			"username_format": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "Format string given auth method username as argument to create Duo username (default '%s')",
			},
			"push_info": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "A string of URL-encoded key/value pairs that provides additional context about the authentication attempt in the Duo Mobile app",
			},
		},

		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.UpdateOperation: pathDuoConfigWrite,
			logical.ReadOperation:   pathDuoConfigRead,
		},

		HelpSynopsis:    pathDuoConfigHelpSyn,
		HelpDescription: pathDuoConfigHelpDesc,
	}
}

func GetDuoConfig(ctx context.Context, req *logical.Request) (*DuoConfig, error) {
	var result DuoConfig
	// all config parameters are optional, so path need not exist
	entry, err := req.Storage.Get(ctx, "duo/config")
	if err == nil && entry != nil {
		if err := entry.DecodeJSON(&result); err != nil {
			return nil, err
		}
	}
	if result.UsernameFormat == "" {
		result.UsernameFormat = "%s"
	}
	return &result, nil
}

func pathDuoConfigWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
	username_format := d.Get("username_format").(string)
	if username_format == "" {
		username_format = "%s"
	}
	if !strings.Contains(username_format, "%s") {
		return nil, errors.New("username_format must include username ('%s')")
	}
	entry, err := logical.StorageEntryJSON("duo/config", DuoConfig{
		UsernameFormat: username_format,
		UserAgent:      d.Get("user_agent").(string),
		PushInfo:       d.Get("push_info").(string),
	})
	if err != nil {
		return nil, err
	}

	if err := req.Storage.Put(ctx, entry); err != nil {
		return nil, err
	}

	return nil, nil
}

func pathDuoConfigRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
	config, err := GetDuoConfig(ctx, req)
	if err != nil {
		return nil, err
	}
	if config == nil {
		return nil, nil
	}

	return &logical.Response{
		Data: map[string]interface{}{
			"username_format": config.UsernameFormat,
			"user_agent":      config.UserAgent,
			"push_info":       config.PushInfo,
		},
	}, nil
}

type DuoConfig struct {
	UsernameFormat string `json:"username_format"`
	UserAgent      string `json:"user_agent"`
	PushInfo       string `json:"push_info"`
}

const pathDuoConfigHelpSyn = `
Configure Duo second factor behavior.
`

const pathDuoConfigHelpDesc = `
This endpoint allows you to configure how the original auth method username maps to
the Duo username by providing a template format string.
`
