package jenkins

import (
	"fmt"
	"net/url"
	"strconv"
	"strings"
	"unicode"
	"unicode/utf8"
)

const (
	maxJobPathSegments = 16
	maxJobSegmentBytes = 256
	maxJobPathBytes    = 4096
)

func jobPath(segments []string, suffix string, allowRoot bool) (string, error) {
	if len(segments) == 0 && !allowRoot {
		return "", fmt.Errorf("Jenkins jobPath is required")
	}
	if len(segments) > maxJobPathSegments {
		return "", fmt.Errorf("Jenkins jobPath exceeds the segment limit")
	}
	if suffix != "" && !strings.HasPrefix(suffix, "/") {
		return "", fmt.Errorf("Jenkins job path suffix is invalid")
	}
	var builder strings.Builder
	for _, segment := range segments {
		if err := validateJobSegment(segment); err != nil {
			return "", err
		}
		builder.WriteString("/job/")
		builder.WriteString(url.PathEscape(segment))
	}
	builder.WriteString(suffix)
	result := builder.String()
	if result == "" {
		result = "/"
	}
	if len(result) > maxJobPathBytes {
		return "", fmt.Errorf("Jenkins jobPath exceeds the path budget")
	}
	return result, nil
}

func validateJobSegment(segment string) error {
	if !utf8.ValidString(segment) || len(segment) < 1 ||
		len(segment) > maxJobSegmentBytes {
		return fmt.Errorf("Jenkins jobPath contains an invalid segment")
	}
	if segment == "." || segment == ".." ||
		strings.ContainsAny(segment, `/\`) {
		return fmt.Errorf("Jenkins jobPath contains an invalid segment")
	}
	for _, value := range segment {
		if unicode.IsControl(value) || value == '\x7f' {
			return fmt.Errorf("Jenkins jobPath contains an invalid segment")
		}
	}
	return nil
}

func parseQueueLocation(baseURL *url.URL, rawLocation string) (int64, error) {
	if baseURL == nil || rawLocation == "" {
		return 0, fmt.Errorf("Jenkins returned an invalid queue location")
	}
	location, err := url.Parse(rawLocation)
	if err != nil || location.User != nil ||
		location.RawQuery != "" || location.Fragment != "" {
		return 0, fmt.Errorf("Jenkins returned an invalid queue location")
	}
	resolved := baseURL.ResolveReference(location)
	if !sameHTTPSOrigin(baseURL, resolved) {
		return 0, fmt.Errorf("Jenkins returned an invalid queue location")
	}
	basePath := strings.TrimSuffix(baseURL.EscapedPath(), "/")
	queuePrefix := basePath + "/queue/item/"
	escapedPath := resolved.EscapedPath()
	if !strings.HasPrefix(escapedPath, queuePrefix) ||
		strings.Contains(escapedPath[len(queuePrefix):], "%") {
		return 0, fmt.Errorf("Jenkins returned an invalid queue location")
	}
	queueIDText := strings.TrimSuffix(escapedPath[len(queuePrefix):], "/")
	if queueIDText == "" || strings.Contains(queueIDText, "/") ||
		queueIDText[0] == '0' {
		return 0, fmt.Errorf("Jenkins returned an invalid queue location")
	}
	for index := 0; index < len(queueIDText); index++ {
		if queueIDText[index] < '0' || queueIDText[index] > '9' {
			return 0, fmt.Errorf("Jenkins returned an invalid queue location")
		}
	}
	queueID, err := strconv.ParseInt(queueIDText, 10, 64)
	if err != nil || queueID <= 0 || queueID > maxSafeInteger {
		return 0, fmt.Errorf("Jenkins returned an invalid queue location")
	}
	return queueID, nil
}

func sameHTTPSOrigin(first, second *url.URL) bool {
	if first == nil || second == nil ||
		first.Scheme != "https" || second.Scheme != "https" ||
		!strings.EqualFold(first.Hostname(), second.Hostname()) {
		return false
	}
	return effectiveHTTPSPort(first) == effectiveHTTPSPort(second)
}

func effectiveHTTPSPort(value *url.URL) string {
	port := value.Port()
	if port == "" {
		return "443"
	}
	if _, err := strconv.Atoi(port); err != nil {
		return ""
	}
	return port
}
