package executionpool

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"net/url"
	"strings"
	"time"
)

func QueueName(poolID string) string {
	poolID = strings.TrimSpace(poolID)
	if poolID == "" {
		return "gateway_tasks"
	}
	digest := sha256.Sum256([]byte(poolID))
	return "gateway_pool_" + fmt.Sprintf("%x", digest[:8])
}

func RouteProfileKey(provider, protocol, endpointHost, proxyMode, configRevision string) string {
	value := strings.Join([]string{
		strings.ToLower(strings.TrimSpace(provider)),
		strings.ToLower(strings.TrimSpace(protocol)),
		strings.ToLower(strings.TrimSpace(endpointHost)),
		strings.ToLower(strings.TrimSpace(proxyMode)),
		strings.TrimSpace(configRevision),
	}, "\x00")
	digest := sha256.Sum256([]byte(value))
	return fmt.Sprintf("route_%x", digest[:16])
}

func EndpointHost(rawURL string) string {
	parsed, err := url.Parse(strings.TrimSpace(rawURL))
	if err != nil {
		return ""
	}
	return strings.ToLower(parsed.Hostname())
}

func ValidateAdvertisedEndpoint(rawURL string, allowedSuffixes []string, allowPrivate bool) error {
	parsed, err := url.Parse(strings.TrimSpace(rawURL))
	if err != nil || parsed.Host == "" || parsed.User != nil {
		return errors.New("worker endpoint must be an absolute URL without user info")
	}
	if parsed.Scheme != "https" && parsed.Scheme != "http" {
		return errors.New("worker endpoint scheme must be http or https")
	}
	host := strings.ToLower(parsed.Hostname())
	if host == "" {
		return errors.New("worker endpoint host is required")
	}
	if ip := net.ParseIP(host); ip != nil {
		if allowPrivate && (ip.IsPrivate() || ip.IsLoopback()) {
			return nil
		}
		return errors.New("worker endpoint IP is outside the configured trust boundary")
	}
	for _, suffix := range allowedSuffixes {
		suffix = strings.ToLower(strings.TrimSpace(suffix))
		if suffix != "" && (host == strings.TrimPrefix(suffix, ".") || strings.HasSuffix(host, "."+strings.TrimPrefix(suffix, "."))) {
			return nil
		}
	}
	return errors.New("worker endpoint host is outside the configured trust boundary")
}

type ExecutionClaims struct {
	Audience  string `json:"aud"`
	TaskID    string `json:"task_id"`
	PoolID    string `json:"pool_id"`
	WorkerID  string `json:"worker_id"`
	Nonce     string `json:"nonce"`
	ExpiresAt int64  `json:"exp"`
}

type TokenSigner struct {
	Secret []byte
	Now    func() time.Time
}

func (s TokenSigner) Sign(claims ExecutionClaims) (string, error) {
	if len(s.Secret) < 32 {
		return "", errors.New("execution token secret must be at least 32 bytes")
	}
	if claims.Audience == "" || claims.TaskID == "" || claims.PoolID == "" || claims.WorkerID == "" || claims.Nonce == "" || claims.ExpiresAt <= 0 {
		return "", errors.New("execution token claims are incomplete")
	}
	payload, err := json.Marshal(claims)
	if err != nil {
		return "", err
	}
	encoded := base64.RawURLEncoding.EncodeToString(payload)
	mac := hmac.New(sha256.New, s.Secret)
	_, _ = mac.Write([]byte(encoded))
	signature := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
	return encoded + "." + signature, nil
}

func (s TokenSigner) Verify(token, audience string) (ExecutionClaims, error) {
	var claims ExecutionClaims
	if len(s.Secret) < 32 {
		return claims, errors.New("execution token secret must be at least 32 bytes")
	}
	parts := strings.Split(token, ".")
	if len(parts) != 2 {
		return claims, errors.New("invalid execution token")
	}
	signature, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil {
		return claims, errors.New("invalid execution token signature")
	}
	mac := hmac.New(sha256.New, s.Secret)
	_, _ = mac.Write([]byte(parts[0]))
	if !hmac.Equal(signature, mac.Sum(nil)) {
		return claims, errors.New("invalid execution token signature")
	}
	payload, err := base64.RawURLEncoding.DecodeString(parts[0])
	if err != nil || json.Unmarshal(payload, &claims) != nil {
		return ExecutionClaims{}, errors.New("invalid execution token payload")
	}
	now := time.Now()
	if s.Now != nil {
		now = s.Now()
	}
	if claims.ExpiresAt <= now.Unix() {
		return ExecutionClaims{}, errors.New("execution token expired")
	}
	if claims.Audience != audience {
		return ExecutionClaims{}, errors.New("execution token audience mismatch")
	}
	return claims, nil
}
