package timeext

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"
)

var durationShortStringMap = map[string]time.Duration{
	"ns":          time.Nanosecond,
	"nanosecond":  time.Nanosecond,
	"nanoseconds": time.Nanosecond,

	"us":           time.Microsecond,
	"microsecond":  time.Microsecond,
	"microseconds": time.Microsecond,

	"ms":           time.Millisecond,
	"millisecond":  time.Millisecond,
	"milliseconds": time.Millisecond,

	"s":       time.Second,
	"sec":     time.Second,
	"second":  time.Second,
	"seconds": time.Second,

	"m":       time.Minute,
	"min":     time.Minute,
	"minute":  time.Minute,
	"minutes": time.Minute,

	"h":     time.Hour,
	"hour":  time.Hour,
	"hours": time.Hour,

	"d":    24 * time.Hour,
	"day":  24 * time.Hour,
	"days": 24 * time.Hour,

	"w":     7 * 24 * time.Hour,
	"wk":    7 * 24 * time.Hour,
	"week":  7 * 24 * time.Hour,
	"weeks": 7 * 24 * time.Hour,
}

// ParseDurationShortString parses a duration in string format to a time.Duration
// Examples for allowed formats:
//   - '10m'
//   - '10min'
//   - '1minute'
//   - '10minutes'
//   - '10.5minutes'
//   - '50s'
//   - '50sec'
//   - '1second'
//   - '50seconds'
//   - '100ms'
//   - '100millisseconds'
//   - '1h'
//   - '1hour'
//   - '2hours'
//   - '1d'
//   - '1day'
//   - '10days'
//   - '1d10m'
//   - '1d10m200sec'
//   - '1d:10m'
//   - '1d 10m'
//   - '1d,10m'
func ParseDurationShortString(s string) (time.Duration, error) {
	s = strings.ToLower(s)

	segments := make([]string, 0)
	collector := ""

	prevWasNum := true
	for _, chr := range s {
		if chr >= '0' && chr <= '9' || chr == '.' {
			if prevWasNum {
				collector += string(chr)
			} else {
				segments = append(segments, collector)
				prevWasNum = true
				collector = string(chr)
			}
		} else if chr == ' ' || chr == ':' || chr == ',' {
			continue
		} else if chr >= 'a' && chr <= 'z' {
			prevWasNum = false
			collector += string(chr)
		} else {
			return 0, errors.New("unexpected character: " + string(chr))
		}
	}
	if !prevWasNum {
		segments = append(segments, collector)
	}

	result := time.Duration(0)
	for _, seg := range segments {
		segDur, err := parseDurationShortStringSegment(seg)
		if err != nil {
			return 0, err
		}
		result += segDur
	}
	return result, nil
}

func parseDurationShortStringSegment(segment string) (time.Duration, error) {
	num := ""
	unit := ""

	part0 := true
	for _, chr := range segment {
		if part0 {

			if chr >= 'a' && chr <= 'z' {
				part0 = false
				unit += string(chr)
			} else if chr >= '0' && chr <= '9' || chr == '.' {
				num += string(chr)
			} else {
				return 0, errors.New(fmt.Sprintf("Unexpected character '%s' in segment [%s]", string(chr), segment))
			}

		} else {

			if chr >= 'a' && chr <= 'z' {
				unit += string(chr)
			} else if chr >= '0' && chr <= '9' || chr == '.' {
				return 0, errors.New(fmt.Sprintf("Unexpected number '%s' in segment [%s]", string(chr), segment))
			} else {
				return 0, errors.New(fmt.Sprintf("Unexpected character '%s' in segment [%s]", string(chr), segment))
			}

		}
	}

	fpnum, err := strconv.ParseFloat(num, 64)
	if err != nil {
		return 0, errors.New(fmt.Sprintf("Failed to parse floating-point number '%s' in segment [%s]", num, segment))
	}

	if mult, ok := durationShortStringMap[unit]; ok {
		return time.Duration(int64(fpnum * float64(mult))), nil
	} else {
		return 0, errors.New(fmt.Sprintf("Unknown unit '%s' in segment [%s]", unit, segment))
	}
}
