package currency

import (
	"errors"
	"fmt"
	"strings"
	"unicode"
)

var (
	errCannotCreatePair       = errors.New("cannot create currency pair")
	errDelimiterNotFound      = errors.New("delimiter not found")
	errDelimiterCannotBeEmpty = errors.New("delimiter cannot be empty")
)

// NewBTCUSDT is a shortcut for NewPair(BTC, USDT)
func NewBTCUSDT() Pair {
	return NewPair(BTC, USDT)
}

// NewBTCUSD is a shortcut for NewPair(BTC, USD)
func NewBTCUSD() Pair {
	return NewPair(BTC, USD)
}

// NewPairDelimiter splits the desired currency string at delimiter, then returns a Pair struct
func NewPairDelimiter(currencyPair, delimiter string) (Pair, error) {
	if currencyPair == "" {
		return EMPTYPAIR, errEmptyPairString
	}
	if delimiter == "" {
		return EMPTYPAIR, errDelimiterCannotBeEmpty
	}
	index := strings.Index(currencyPair, delimiter)
	if index == -1 {
		return EMPTYPAIR,
			fmt.Errorf("supplied pair: [%s] %s %w", currencyPair, delimiter, errDelimiterNotFound)
	}
	return Pair{Delimiter: delimiter, Base: NewCode(currencyPair[:index]), Quote: NewCode(currencyPair[index+1:])}, nil
}

// NewPairFromStrings returns a CurrencyPair without a delimiter
func NewPairFromStrings(base, quote string) (Pair, error) {
	if strings.Contains(base, " ") {
		return EMPTYPAIR,
			fmt.Errorf("cannot create pair, invalid base currency string [%s]",
				base)
	}

	if strings.Contains(quote, " ") {
		return EMPTYPAIR,
			fmt.Errorf("cannot create pair, invalid quote currency string [%s]",
				quote)
	}

	return Pair{Base: NewCode(base), Quote: NewCode(quote)}, nil
}

// NewPair returns a currency pair from currency codes
func NewPair(baseCurrency, quoteCurrency Code) Pair {
	return Pair{Base: baseCurrency, Quote: quoteCurrency}
}

// NewPairWithDelimiter returns a CurrencyPair with a delimiter
func NewPairWithDelimiter(base, quote, delimiter string) Pair {
	return Pair{Base: NewCode(base), Quote: NewCode(quote), Delimiter: delimiter}
}

// NewPairFromString converts currency string into a new CurrencyPair
// with or without delimiter
func NewPairFromString(currencyPair string) (Pair, error) {
	if len(currencyPair) < 3 {
		return EMPTYPAIR, fmt.Errorf("%w from %s string too short to be a currency pair", errCannotCreatePair, currencyPair)
	}

	for x := range currencyPair {
		if unicode.IsPunct(rune(currencyPair[x])) {
			return Pair{
				Base:      NewCode(currencyPair[:x]),
				Delimiter: string(currencyPair[x]),
				Quote:     NewCode(currencyPair[x+1:]),
			}, nil
		}
	}

	return NewPairFromStrings(currencyPair[0:3], currencyPair[3:])
}

// NewPairFromFormattedPairs matches a supplied currency pair to a list of pairs
// with a specific format. This is helpful for exchanges which
// provide currency pairs with no delimiter so we can match it with a list and
// apply the same format
func NewPairFromFormattedPairs(currencyPair string, pairs Pairs, pairFmt PairFormat) (Pair, error) {
	for x := range pairs {
		if strings.EqualFold(pairFmt.Format(pairs[x]), currencyPair) {
			return pairs[x], nil
		}
	}
	return NewPairFromString(currencyPair)
}

// Format formats the given pair as a string
func (f PairFormat) Format(pair Pair) string {
	return pair.Format(f).String()
}

// clone returns a clone of the PairFormat
func (f *PairFormat) clone() *PairFormat {
	if f == nil {
		return nil
	}
	c := *f
	return &c
}

// MatchPairsWithNoDelimiter will move along a predictable index on the provided currencyPair
// it will then split on that index and verify whether that currencypair exists in the
// supplied pairs
// this allows for us to match strange currencies with no delimiter where it is difficult to
// infer where the delimiter is located eg BETHERETH is BETHER ETH
func MatchPairsWithNoDelimiter(currencyPair string, pairs Pairs, pairFmt PairFormat) (Pair, error) {
	for i := range pairs {
		fPair := pairs[i].Format(pairFmt)
		maxLen := min(len(currencyPair), 6)
		for j := 1; j <= maxLen; j++ {
			if fPair.Base.String() == currencyPair[0:j] &&
				fPair.Quote.String() == currencyPair[j:] {
				return fPair, nil
			}
		}
	}
	return EMPTYPAIR, fmt.Errorf("currency %v not found in supplied pairs", currencyPair)
}

// GetFormatting returns the formatting style of a pair
func (p Pair) GetFormatting() (PairFormat, error) {
	if p.Base.isCaseSensitive() && p.Quote.isCaseSensitive() && (p.Base.upperCase != p.Quote.upperCase) {
		return EMPTYFORMAT, fmt.Errorf("%w casing mismatch", errPairFormattingInconsistent)
	}
	return PairFormat{Uppercase: p.Base.upperCase || p.Quote.upperCase, Delimiter: p.Delimiter}, nil
}

func (p Pair) hasFormatDifference(pairFmt PairFormat) bool {
	return p.Delimiter != pairFmt.Delimiter ||
		(p.Base.isCaseSensitive() && p.Base.upperCase != pairFmt.Uppercase) ||
		(p.Quote.isCaseSensitive() && p.Quote.upperCase != pairFmt.Uppercase)
}

func (c Code) isCaseSensitive() bool {
	if c.Item == nil {
		return false
	}
	return c.Item.Symbol != c.Item.Lower
}
