package utils

import (
	"errors"
	"fmt"
	"sort"
	"strconv"
	"strings"

	"golang.org/x/exp/constraints"
)

type IntRanges[T constraints.Integer] []Range[T]

var errIntRanges = errors.New("intRanges error")

func newIntRanges[T constraints.Integer](expected string, parseFn func(string) (T, error)) (IntRanges[T], error) {
	// example: 200 or 200/302 or 200-400 or 200/204/401-429/501-503
	expected = strings.TrimSpace(expected)
	if len(expected) == 0 || expected == "*" {
		return nil, nil
	}

	// support: 200,302 or 200,204,401-429,501-503
	expected = strings.ReplaceAll(expected, ",", "/")
	list := strings.Split(expected, "/")
	if len(list) > 28 {
		return nil, fmt.Errorf("%w, too many ranges to use, maximum support 28 ranges", errIntRanges)
	}

	return newIntRangesFromList[T](list, parseFn)
}

func newIntRangesFromList[T constraints.Integer](list []string, parseFn func(string) (T, error)) (IntRanges[T], error) {
	var ranges IntRanges[T]
	for _, s := range list {
		if s == "" {
			continue
		}

		status := strings.Split(s, "-")
		statusLen := len(status)
		if statusLen > 2 {
			return nil, errIntRanges
		}

		start, err := parseFn(strings.Trim(status[0], "[ ]"))
		if err != nil {
			return nil, errIntRanges
		}

		switch statusLen {
		case 1: // Port range
			ranges = append(ranges, NewRange(T(start), T(start)))
		case 2: // Single port
			end, err := parseFn(strings.Trim(status[1], "[ ]"))
			if err != nil {
				return nil, errIntRanges
			}

			ranges = append(ranges, NewRange(T(start), T(end)))
		}
	}

	return ranges, nil
}

func parseUnsigned[T constraints.Unsigned](s string) (T, error) {
	if val, err := strconv.ParseUint(s, 10, 64); err == nil {
		return T(val), nil
	} else {
		return 0, err
	}
}

func NewUnsignedRanges[T constraints.Unsigned](expected string) (IntRanges[T], error) {
	return newIntRanges(expected, parseUnsigned[T])
}

func NewUnsignedRangesFromList[T constraints.Unsigned](list []string) (IntRanges[T], error) {
	return newIntRangesFromList(list, parseUnsigned[T])
}

func parseSigned[T constraints.Signed](s string) (T, error) {
	if val, err := strconv.ParseInt(s, 10, 64); err == nil {
		return T(val), nil
	} else {
		return 0, err
	}
}

func NewSignedRanges[T constraints.Signed](expected string) (IntRanges[T], error) {
	return newIntRanges(expected, parseSigned[T])
}

func NewSignedRangesFromList[T constraints.Signed](list []string) (IntRanges[T], error) {
	return newIntRangesFromList(list, parseSigned[T])
}

func (ranges IntRanges[T]) Check(status T) bool {
	if len(ranges) == 0 {
		return true
	}

	for _, segment := range ranges {
		if segment.Contains(status) {
			return true
		}
	}

	return false
}

func (ranges IntRanges[T]) String() string {
	if len(ranges) == 0 {
		return "*"
	}

	terms := make([]string, len(ranges))
	for i, r := range ranges {
		start := r.Start()
		end := r.End()

		var term string
		if start == end {
			term = strconv.Itoa(int(start))
		} else {
			term = strconv.Itoa(int(start)) + "-" + strconv.Itoa(int(end))
		}

		terms[i] = term
	}

	return strings.Join(terms, "/")
}

func (ranges IntRanges[T]) Range(f func(t T) bool) {
	if len(ranges) == 0 {
		return
	}

	for _, r := range ranges {
		for i := r.Start(); i <= r.End() && i >= r.Start(); i++ {
			if !f(i) {
				return
			}
			if i+1 < i { // integer overflow
				break
			}
		}
	}
}

func (ranges IntRanges[T]) Merge() (mergedRanges IntRanges[T]) {
	if len(ranges) == 0 {
		return
	}
	sort.Slice(ranges, func(i, j int) bool {
		return ranges[i].Start() < ranges[j].Start()
	})
	mergedRanges = ranges[:1]
	var rangeIndex int
	for _, r := range ranges[1:] {
		if mergedRanges[rangeIndex].End()+1 > mergedRanges[rangeIndex].End() && // integer overflow
			r.Start() > mergedRanges[rangeIndex].End()+1 {
			mergedRanges = append(mergedRanges, r)
			rangeIndex++
		} else if r.End() > mergedRanges[rangeIndex].End() {
			mergedRanges[rangeIndex].end = r.End()
		}
	}
	return
}
