package authmethod

import (
	"bytes"
	"encoding/json"
	"fmt"

	"github.com/hashicorp/consul/api"
)

const (
	PrettyFormat string = "pretty"
	JSONFormat   string = "json"
)

// Formatter defines methods provided by authmethod command output formatter
type Formatter interface {
	FormatAuthMethod(method *api.ACLAuthMethod) (string, error)
	FormatAuthMethodList(methods []*api.ACLAuthMethodListEntry) (string, error)
}

// GetSupportedFormats returns supported formats
func GetSupportedFormats() []string {
	return []string{PrettyFormat, JSONFormat}
}

// NewFormatter returns Formatter implementation
func NewFormatter(format string, showMeta bool) (formatter Formatter, err error) {
	switch format {
	case PrettyFormat:
		formatter = newPrettyFormatter(showMeta)
	case JSONFormat:
		formatter = newJSONFormatter(showMeta)
	default:
		err = fmt.Errorf("Unknown format: %s", format)
	}

	return formatter, err
}

func newPrettyFormatter(showMeta bool) Formatter {
	return &prettyFormatter{showMeta}
}

type prettyFormatter struct {
	showMeta bool
}

func (f *prettyFormatter) FormatAuthMethod(method *api.ACLAuthMethod) (string, error) {
	var buffer bytes.Buffer

	buffer.WriteString(fmt.Sprintf("Name:          %s\n", method.Name))
	buffer.WriteString(fmt.Sprintf("Type:          %s\n", method.Type))
	if method.Namespace != "" {
		buffer.WriteString(fmt.Sprintf("Namespace:     %s\n", method.Namespace))
	}
	if method.DisplayName != "" {
		buffer.WriteString(fmt.Sprintf("DisplayName:   %s\n", method.DisplayName))
	}
	buffer.WriteString(fmt.Sprintf("Description:   %s\n", method.Description))
	if method.MaxTokenTTL > 0 {
		buffer.WriteString(fmt.Sprintf("MaxTokenTTL:   %s\n", method.MaxTokenTTL))
	}
	if method.TokenLocality != "" {
		buffer.WriteString(fmt.Sprintf("TokenLocality: %s\n", method.TokenLocality))
	}
	if len(method.NamespaceRules) > 0 {
		buffer.WriteString(fmt.Sprintln("NamespaceRules:"))
		for _, rule := range method.NamespaceRules {
			buffer.WriteString(fmt.Sprintf("   Selector:      %s\n", rule.Selector))
			buffer.WriteString(fmt.Sprintf("   BindNamespace: %s\n", rule.BindNamespace))
		}
	}
	if f.showMeta {
		buffer.WriteString(fmt.Sprintf("Create Index:  %d\n", method.CreateIndex))
		buffer.WriteString(fmt.Sprintf("Modify Index:  %d\n", method.ModifyIndex))
	}
	buffer.WriteString(fmt.Sprintln("Config:"))
	output, err := json.MarshalIndent(method.Config, "", "  ")
	if err != nil {
		return "", fmt.Errorf("Error formatting auth method configuration: %s", err)
	}
	buffer.WriteString(string(output))

	return buffer.String(), nil
}

func (f *prettyFormatter) FormatAuthMethodList(methods []*api.ACLAuthMethodListEntry) (string, error) {
	var buffer bytes.Buffer

	for _, method := range methods {
		buffer.WriteString(f.formatAuthMethodListEntry(method))
	}

	return buffer.String(), nil
}

func (f *prettyFormatter) formatAuthMethodListEntry(method *api.ACLAuthMethodListEntry) string {
	var buffer bytes.Buffer

	buffer.WriteString(fmt.Sprintf("%s:\n", method.Name))
	buffer.WriteString(fmt.Sprintf("   Type:         %s\n", method.Type))
	if method.Namespace != "" {
		buffer.WriteString(fmt.Sprintf("   Namespace:    %s\n", method.Namespace))
	}
	if method.DisplayName != "" {
		buffer.WriteString(fmt.Sprintf("   DisplayName:  %s\n", method.DisplayName))
	}
	buffer.WriteString(fmt.Sprintf("   Description:  %s\n", method.Description))
	if f.showMeta {
		buffer.WriteString(fmt.Sprintf("   Create Index: %d\n", method.CreateIndex))
		buffer.WriteString(fmt.Sprintf("   Modify Index: %d\n", method.ModifyIndex))
	}

	return buffer.String()
}

func newJSONFormatter(showMeta bool) Formatter {
	return &jsonFormatter{showMeta}
}

type jsonFormatter struct {
	showMeta bool
}

func (f *jsonFormatter) FormatAuthMethod(method *api.ACLAuthMethod) (string, error) {
	b, err := json.MarshalIndent(method, "", "    ")
	if err != nil {
		return "", fmt.Errorf("Failed to marshal authmethod:, %v", err)
	}
	return string(b), nil
}

func (f *jsonFormatter) FormatAuthMethodList(methods []*api.ACLAuthMethodListEntry) (string, error) {
	b, err := json.MarshalIndent(methods, "", "    ")
	if err != nil {
		return "", fmt.Errorf("Failed to marshal authmethods:, %v", err)
	}
	return string(b), nil
}
