// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package ui

import (
	"errors"
	"fmt"
	"io"

	"github.com/fatih/color"
	"github.com/mitchellh/cli"
)

// LogUI is an implementation of the cli.Ui interface which can be used for
// logging outputs. It differs from cli.BasicUi in the only fact that it does
// not add a newline after each UI write.
type LogUI struct {
	reader      io.Reader
	writer      io.Writer
	errorWriter io.Writer

	// underlyingUI stores the basic UI that was used to create this logUI. It
	// allows us to call the ask functions and not implement them again.
	underlyingUI cli.Ui

	isColor     bool
	outputColor cli.UiColor
	infoColor   cli.UiColor
	errorColor  cli.UiColor
	warnColor   cli.UiColor
}

// NewLogUI generates a new cli.Ui that can be used for commands that write log
// lines to the terminal. The caller is required to pass a cli.BasicUi so we
// have access to the underlying writers.
//
// Currently, the passed ui needs to be either *cli.ColoredUi or *cli.BasicUi
// to work correctly. If more are needed, please add them.
func NewLogUI(ui cli.Ui) (cli.Ui, error) {

	var found bool

	logUI := LogUI{}

	if coloredUI, ok := ui.(*cli.ColoredUi); ok {

		logUI.isColor = true
		logUI.outputColor = coloredUI.OutputColor
		logUI.infoColor = coloredUI.InfoColor
		logUI.errorColor = coloredUI.ErrorColor
		logUI.warnColor = coloredUI.WarnColor
		logUI.underlyingUI = coloredUI.Ui

		if basicUI, ok := coloredUI.Ui.(*cli.BasicUi); ok {
			logUI.reader = basicUI.Reader
			logUI.writer = basicUI.Writer
			logUI.errorWriter = basicUI.ErrorWriter
			found = true
		}
	} else if basicUI, ok := ui.(*cli.BasicUi); ok && !found {
		logUI.reader = basicUI.Reader
		logUI.writer = basicUI.Writer
		logUI.errorWriter = basicUI.ErrorWriter
		logUI.underlyingUI = basicUI
		found = true
	}

	if !found {
		return nil, errors.New("failed to generate logging UI")
	}

	return &logUI, nil
}

// Ask implements the Ask function of the cli.Ui interface.
func (l *LogUI) Ask(query string) (string, error) {
	return l.underlyingUI.Ask(l.colorize(query, l.outputColor))
}

// AskSecret implements the AskSecret function of the cli.Ui interface.
func (l *LogUI) AskSecret(query string) (string, error) {
	return l.underlyingUI.AskSecret(l.colorize(query, l.outputColor))
}

// Output implements the Output function of the cli.Ui interface.
func (l *LogUI) Output(message string) {
	_, _ = fmt.Fprint(l.writer, l.colorize(message, l.outputColor))
}

// Info implements the Info function of the cli.Ui interface.
func (l *LogUI) Info(message string) { l.Output(l.colorize(message, l.infoColor)) }

// Error implements the Error function of the cli.Ui interface.
func (l *LogUI) Error(message string) {
	w := l.writer
	if l.errorWriter != nil {
		w = l.errorWriter
	}
	_, _ = fmt.Fprint(w, l.colorize(message, l.errorColor))
}

// Warn implements the Warn function of the cli.Ui interface.
func (l *LogUI) Warn(message string) { l.Error(l.colorize(message, l.warnColor)) }

func (l *LogUI) colorize(message string, uc cli.UiColor) string {
	if !l.isColor {
		return message
	}

	attr := []color.Attribute{color.Attribute(uc.Code)}
	if uc.Bold {
		attr = append(attr, color.Bold)
	}

	return color.New(attr...).SprintFunc()(message)
}
