package cli

import (
	"flag"
	"fmt"
	"strconv"
	"strings"
	"time"
	"unicode"

	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func OutputStep(format string, args ...interface{}) {
	fmt.Printf("+ "+format+"\n", args...)
}

func OutputLine() {
	fmt.Print(strings.Repeat("-", 100) + "\n")
}

func OutputInfo(format string, args ...interface{}) {
	fmt.Printf("  "+format+"\n", args...)
}

func OutputNotice(format string, args ...interface{}) {
	OutputInfo("- "+format, args...)
}

func OutputError(err error) {
	if err != nil {
		OutputNotice("ERROR: %s", err)
	}
}

func OutputResponseInText(response proto.Message, err error) {
	OutputError(err)
	OutputInfo("%+v\n", response)
}

func OutputResponseInJSON(response proto.Message, err error) {
	OutputError(err)
	option := protojson.MarshalOptions{
		Indent:        "  ",
		UseProtoNames: true,
	}
	output, err := option.Marshal(response)
	if err != nil {
		panic(err)
	}

	for _, line := range strings.Split(string(output), "\n") {
		OutputInfo(line)
	}
}

var outputFormat = "text"

func OutputResponse(response proto.Message, err error) {
	switch outputFormat {
	case "text":
		OutputResponseInText(response, err)

	case "json":
		OutputResponseInJSON(response, err)

	default:
		panic(fmt.Sprintf("unknown output format '%s'", outputFormat))
	}
}

func makeSimpleUsage(set *flag.FlagSet, format string, args ...interface{}) {
	set.Usage = func() {
		OutputLine()
		fmt.Printf(format+"\n", args...)
		set.PrintDefaults()
	}
}

func SuppportedCommandList() []string {
	result := make([]string, 0, len(entryMap))
	for cmd := range entryMap {
		result = append(result, cmd)
	}
	return result
}

func WordDistance(a string, b string) (int, string) {
	ra, rb := []rune(a), []rune(b)
	la, lb := len(ra), len(rb)

	score, pattern := 0, make([]rune, lb)
	i, j := 0, 0
	for i < la && j < lb {
		ca, cb := ra[i], rb[j]
		if ca == cb {
			i++
			pattern[j] = ca
			score += 0
		} else if unicode.ToUpper(ca) == unicode.ToUpper(cb) {
			i++
			pattern[j] = ca
			score += 1
		} else {
			pattern[j] = ' '
			score += 2
		}

		j++
	}

	if i < la {
		return -1, ""
	}

	return score, string(pattern)
}

type MatchResult struct {
	Score    int
	Adjusted string
	Matched  string
}

func FindMatchCommand(input string, probables []string) (MatchResult, []MatchResult) {
	results := []MatchResult{}
	most := MatchResult{Score: -1}
	for _, probable := range probables {
		score, pattern := WordDistance(input, probable)
		result := MatchResult{
			Score:    score,
			Adjusted: pattern,
			Matched:  probable,
		}

		if score < 0 {
			continue
		}

		results = append(results, result)
		if score >= 0 && (score < most.Score || most.Score < 0) {
			most = result
		}
	}

	return most, results
}

type IDList []uint64

func (l *IDList) Set(value string) error {
	id, err := strconv.ParseUint(value, 10, 64)
	if err != nil {
		return err
	}

	*l = append(*l, id)
	return nil
}

func (l *IDList) String() string {
	return fmt.Sprintf("%+v", *l)
}

type StringList []string

func (l *StringList) Set(value string) error {
	*l = append(*l, value)
	return nil
}

func (l *StringList) String() string {
	return fmt.Sprintf("%+v", *l)
}

type Time time.Time

func (t *Time) Set(s string) error {
	value, err := time.Parse(time.RFC3339, s)
	if err != nil {
		return err
	}

	*t = Time(value)
	return nil
}

func (t *Time) String() string {
	return time.Time(*t).Format(time.RFC3339)
}

func (t *Time) ToProto() *timestamppb.Timestamp {
	return timestamppb.New(time.Time(*t))
}
