package binding

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"sort"
	"strconv"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/extension/spec"
)

type queryPair struct {
	name          string
	value         string
	allowReserved bool
	order         int
}

func lookupJSONPointer(root any, pointer string) (any, bool, error) {
	tokens, err := parseJSONPointer(pointer)
	if err != nil {
		return nil, false, err
	}
	current := root
	for _, token := range tokens {
		switch node := current.(type) {
		case map[string]any:
			next, ok := node[token]
			if !ok {
				return nil, false, nil
			}
			current = next
		case []any:
			index, err := parseArrayIndex(token, len(node))
			if err != nil {
				return nil, false, err
			}
			current = node[index]
		default:
			return nil, false, nil
		}
	}
	return current, true, nil
}

func parseJSONPointer(pointer string) ([]string, error) {
	if pointer == "" {
		return nil, nil
	}
	if !strings.HasPrefix(pointer, "/") {
		return nil, fmt.Errorf("invalid JSON Pointer %q", pointer)
	}
	raw := strings.Split(pointer[1:], "/")
	out := make([]string, len(raw))
	for i, token := range raw {
		var decoded strings.Builder
		for j := 0; j < len(token); j++ {
			if token[j] != '~' {
				decoded.WriteByte(token[j])
				continue
			}
			if j+1 >= len(token) {
				return nil, fmt.Errorf("invalid JSON Pointer escape in %q", pointer)
			}
			j++
			switch token[j] {
			case '0':
				decoded.WriteByte('~')
			case '1':
				decoded.WriteByte('/')
			default:
				return nil, fmt.Errorf("invalid JSON Pointer escape in %q", pointer)
			}
		}
		out[i] = decoded.String()
	}
	return out, nil
}

func parseArrayIndex(token string, length int) (int, error) {
	if token == "" || (len(token) > 1 && token[0] == '0') {
		return 0, fmt.Errorf("invalid array index %q", token)
	}
	index, err := strconv.Atoi(token)
	if err != nil || index < 0 || index >= length {
		return 0, fmt.Errorf("array index %q is out of range", token)
	}
	return index, nil
}

func serializeSimple(value any, explode bool) (string, error) {
	switch typed := value.(type) {
	case []any:
		parts := make([]string, 0, len(typed))
		for _, item := range typed {
			scalar, err := scalarString(item)
			if err != nil {
				return "", err
			}
			parts = append(parts, scalar)
		}
		return strings.Join(parts, ","), nil
	case map[string]any:
		keys := sortedMapKeys(typed)
		parts := make([]string, 0, len(keys)*2)
		for _, key := range keys {
			scalar, err := scalarString(typed[key])
			if err != nil {
				return "", err
			}
			if explode {
				parts = append(parts, key+"="+scalar)
			} else {
				parts = append(parts, key, scalar)
			}
		}
		return strings.Join(parts, ","), nil
	default:
		return scalarString(value)
	}
}

func serializeForm(name string, value any, explode, allowReserved bool) ([]queryPair, error) {
	switch typed := value.(type) {
	case []any:
		if !explode {
			joined, err := serializeSimple(typed, false)
			if err != nil {
				return nil, err
			}
			return []queryPair{{name: name, value: joined, allowReserved: allowReserved}}, nil
		}
		pairs := make([]queryPair, 0, len(typed))
		for i, item := range typed {
			scalar, err := scalarString(item)
			if err != nil {
				return nil, err
			}
			pairs = append(pairs, queryPair{
				name: name, value: scalar, allowReserved: allowReserved, order: i,
			})
		}
		return pairs, nil
	case map[string]any:
		keys := sortedMapKeys(typed)
		if !explode {
			joined, err := serializeSimple(typed, false)
			if err != nil {
				return nil, err
			}
			return []queryPair{{name: name, value: joined, allowReserved: allowReserved}}, nil
		}
		pairs := make([]queryPair, 0, len(keys))
		for i, key := range keys {
			scalar, err := scalarString(typed[key])
			if err != nil {
				return nil, err
			}
			pairs = append(pairs, queryPair{
				name: key, value: scalar, allowReserved: allowReserved, order: i,
			})
		}
		return pairs, nil
	default:
		scalar, err := scalarString(value)
		if err != nil {
			return nil, err
		}
		return []queryPair{{name: name, value: scalar, allowReserved: allowReserved}}, nil
	}
}

func scalarString(value any) (string, error) {
	switch typed := value.(type) {
	case string:
		return typed, nil
	case json.Number:
		return typed.String(), nil
	case bool:
		return strconv.FormatBool(typed), nil
	default:
		return "", fmt.Errorf("unsupported parameter value type %T", value)
	}
}

func sortedMapKeys(values map[string]any) []string {
	keys := make([]string, 0, len(values))
	for key := range values {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return keys
}

func bindURL(
	base *url.URL,
	bindingPath string,
	pathValues map[string]string,
	query []queryPair,
) (*url.URL, error) {
	if base == nil || !strings.HasPrefix(bindingPath, "/") {
		return nil, errors.New("bind extension action: binding path must start with /")
	}
	escapedPath := bindingPath
	for name, value := range pathValues {
		placeholder := "{" + name + "}"
		if strings.Count(escapedPath, placeholder) != 1 {
			return nil, fmt.Errorf(
				"bind extension action: path placeholder %q must occur exactly once", name,
			)
		}
		escapedPath = strings.Replace(escapedPath, placeholder, value, 1)
	}
	if strings.ContainsAny(escapedPath, "{}") {
		return nil, errors.New("bind extension action: unbound path placeholder")
	}
	baseEscapedPath := strings.TrimSuffix(base.EscapedPath(), "/")
	escapedPath = baseEscapedPath + escapedPath
	decodedPath, err := url.PathUnescape(escapedPath)
	if err != nil {
		return nil, fmt.Errorf("bind extension action: invalid bound path: %w", err)
	}
	target := *base
	target.Path = decodedPath
	target.RawPath = escapedPath
	target.RawQuery = encodeQuery(query)
	target.ForceQuery = false
	target.Fragment = ""
	return &target, nil
}

func encodeQuery(pairs []queryPair) string {
	sort.SliceStable(pairs, func(i, j int) bool {
		left, right := queryEscapeName(pairs[i].name), queryEscapeName(pairs[j].name)
		if left != right {
			return left < right
		}
		return pairs[i].order < pairs[j].order
	})
	encoded := make([]string, 0, len(pairs))
	for _, pair := range pairs {
		encoded = append(encoded,
			queryEscapeName(pair.name)+"="+queryEscapeValue(pair.value, pair.allowReserved),
		)
	}
	return strings.Join(encoded, "&")
}

func queryEscapeName(value string) string {
	return strings.ReplaceAll(url.QueryEscape(value), "+", "%20")
}

func queryEscapeValue(value string, allowReserved bool) string {
	escaped := queryEscapeName(value)
	if !allowReserved {
		return escaped
	}
	// Preserve RFC 3986 reserved bytes that cannot change query structure.
	// Delimiters '&', '=', and '#' stay escaped so input can never inject a
	// sibling query field or URL fragment.
	for _, item := range []struct{ encoded, raw string }{
		{"%3A", ":"}, {"%2F", "/"}, {"%3F", "?"},
		{"%5B", "["}, {"%5D", "]"}, {"%40", "@"},
		{"%21", "!"}, {"%24", "$"}, {"%27", "'"},
		{"%28", "("}, {"%29", ")"}, {"%2A", "*"},
		{"%2B", "+"}, {"%2C", ","}, {"%3B", ";"},
	} {
		escaped = strings.ReplaceAll(escaped, item.encoded, item.raw)
		escaped = strings.ReplaceAll(escaped, strings.ToLower(item.encoded), item.raw)
	}
	return escaped
}

func bindBody(input any, body *spec.Body) ([]byte, error) {
	if body == nil {
		return nil, nil
	}
	var root any = map[string]any{}
	rootMapped := false
	for _, mapping := range body.Mappings {
		value, found, err := lookupJSONPointer(input, mapping.From)
		if err != nil {
			return nil, fmt.Errorf("bind extension action: body source %q: %w", mapping.From, err)
		}
		if !found {
			continue
		}
		value = cloneJSON(value)
		target, err := parseJSONPointer(mapping.To)
		if err != nil {
			return nil, fmt.Errorf("bind extension action: body target %q: %w", mapping.To, err)
		}
		if len(target) == 0 {
			if rootMapped || len(body.Mappings) != 1 {
				return nil, errors.New("bind extension action: ambiguous root body mapping")
			}
			root = value
			rootMapped = true
			continue
		}
		if rootMapped {
			return nil, errors.New("bind extension action: body target overlaps root mapping")
		}
		if err := setObjectPointer(root, target, value); err != nil {
			return nil, fmt.Errorf("bind extension action: body target %q: %w", mapping.To, err)
		}
	}
	encoded, err := json.Marshal(root)
	if err != nil {
		return nil, fmt.Errorf("bind extension action: encode body: %w", err)
	}
	return encoded, nil
}

func setObjectPointer(root any, tokens []string, value any) error {
	current, ok := root.(map[string]any)
	if !ok {
		return errors.New("body root is not an object")
	}
	for i, token := range tokens {
		if token == "" {
			return errors.New("body target contains an empty object key")
		}
		if i == len(tokens)-1 {
			if _, exists := current[token]; exists {
				return fmt.Errorf("duplicate body target %q", token)
			}
			current[token] = value
			return nil
		}
		next, exists := current[token]
		if !exists {
			child := make(map[string]any)
			current[token] = child
			current = child
			continue
		}
		child, ok := next.(map[string]any)
		if !ok {
			return fmt.Errorf("body target %q overlaps a scalar", token)
		}
		current = child
	}
	return nil
}

func isReservedHeader(name string) bool {
	switch strings.ToLower(name) {
	case "authorization", "proxy-authorization", "cookie", "host",
		"content-length", "transfer-encoding", "connection",
		"proxy-connection", "keep-alive", "te", "trailer", "upgrade":
		return true
	default:
		return false
	}
}

func validHeaderName(name string) bool {
	if name == "" {
		return false
	}
	for i := 0; i < len(name); i++ {
		ch := name[i]
		if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
			(ch >= '0' && ch <= '9') ||
			strings.ContainsRune("!#$%&'*+-.^_`|~", rune(ch)) {
			continue
		}
		return false
	}
	return true
}
