package stub

import (
	"fmt"
	"strings"
	"text/template"
)

// GenerateRegionalAPIProxyStub generates a local HTTP stub for the Premium
// regional API surface consumed by nextgen-global-service. The source
// cluster-ng-server can still run for persona coverage, but this local API keeps
// Premium Reserved dogfood independent from its internal executor dependencies.
func GenerateRegionalAPIProxyStub(cfg *Config) (map[string]string, error) {
	httpPort := cfg.HTTPPort
	if httpPort == 0 {
		httpPort = DefaultHTTPPort
	}

	data := regionalAPIProxyTemplateData{
		Service:  cfg.Service,
		HTTPPort: httpPort,
	}

	var b strings.Builder
	if err := regionalAPIProxyMainTmpl.Execute(&b, data); err != nil {
		return nil, fmt.Errorf("execute regional-api-proxy template: %w", err)
	}
	return map[string]string{"main.go": b.String()}, nil
}

type regionalAPIProxyTemplateData struct {
	Service  string
	HTTPPort int
}

var regionalAPIProxyMainTmpl = template.Must(template.New("regional-api-proxy-main").Parse(`package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
	"sync"
)

const (
	premiumPrefix = "/api/premium/v1"
)

const localPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" +
	"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzfaN9znsZU0/y7ENnjGF\n" +
	"WIzLijh4xe/LiEOOQaE4enFIc1Sk5ss878gJb/AcOvPNMeiiNbe4tZxlu1pxJfKs\n" +
	"kUnSjSJHPTzSXHW4AcsjCrMuDPXRRa6yc0UEucYwwgGWT6swZP8QN7in08fagWqg\n" +
	"p0dCyKEgSOyBhi8sU6IMlTwHaqAHSxnjLA2ooLLfzGclsxA2ciYRdAdNoZ+MXvFS\n" +
	"6VSydJMIGmzg20dL5MVQ0CsH6wxX5b/6vqRRYH6VcQRRsLo7WEnqeKE8kJJJIwlD\n" +
	"XA+DYZJ2EgMrlR/HlyO+bW/ADctIP8Q9cgO5f4IIedOyR42ozOIB6KBl9MJnr7Nc\n" +
	"nwIDAQAB\n" +
	"-----END PUBLIC KEY-----"

type server struct {
	mu       sync.Mutex
	clusters map[string]map[string]any
	groups   map[string]map[string]any
}

func main() {
	s := &server{
		clusters: map[string]map[string]any{},
		groups:   map[string]map[string]any{},
	}

	mux := http.NewServeMux()
	mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, "{\"status\":\"ok\"}")
	})
	mux.HandleFunc("/", s.serve)

	addr := fmt.Sprintf(":%d", {{.HTTPPort}})
	log.Printf("{{.Service}} regional-api-proxy listening on http=%s", addr)
	if err := http.ListenAndServe(addr, mux); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

func (s *server) serve(w http.ResponseWriter, r *http.Request) {
	if strings.HasPrefix(r.URL.Path, "/dataplane/proxy/") {
		s.serveDataplaneProxy(w, r)
		return
	}
	if !strings.HasPrefix(r.URL.Path, premiumPrefix) {
		writeJSON(w, http.StatusNotFound, map[string]any{"error": "not found"})
		return
	}

	parts := premiumPathParts(r.URL.Path)
	if len(parts) == 0 {
		writeJSON(w, http.StatusOK, map[string]any{"status": "ok"})
		return
	}

	if parts[0] != "clusters" {
		writeJSON(w, http.StatusOK, map[string]any{})
		return
	}

	if len(parts) == 1 {
		switch r.Method {
		case http.MethodPost:
			s.serveCreateCluster(w, r)
		case http.MethodGet:
			s.serveListClusters(w)
		default:
			writeJSON(w, http.StatusOK, map[string]any{})
		}
		return
	}

	clusterID := strings.TrimSuffix(parts[1], ":forceUpdateGatewayIdentity")
	clusterID = strings.TrimSuffix(clusterID, ":setAuditLogSetting")
	if clusterID == "" {
		clusterID = "100"
	}
	if strings.Contains(parts[1], ":") {
		writeJSON(w, http.StatusOK, s.cluster(clusterID, nil))
		return
	}

	if len(parts) == 2 {
		switch r.Method {
		case http.MethodGet:
			writeJSON(w, http.StatusOK, s.cluster(clusterID, nil))
		case http.MethodPut:
			writeJSON(w, http.StatusOK, s.cluster(clusterID, readBodyMap(r)))
		case http.MethodDelete:
			s.mu.Lock()
			delete(s.clusters, clusterID)
			s.mu.Unlock()
			writeJSON(w, http.StatusOK, map[string]any{})
		default:
			writeJSON(w, http.StatusOK, map[string]any{})
		}
		return
	}

	if parts[2] != "tidbGroups" {
		writeJSON(w, http.StatusOK, map[string]any{})
		return
	}

	if len(parts) == 3 {
		switch r.Method {
		case http.MethodPost:
			s.serveCreateTidbGroup(w, r, clusterID)
		case http.MethodGet:
			writeJSON(w, http.StatusOK, map[string]any{"tidbGroups": []any{}})
		default:
			writeJSON(w, http.StatusOK, map[string]any{})
		}
		return
	}

	groupID := strings.TrimSuffix(parts[3], ":scale")
	groupID = strings.TrimSuffix(groupID, ":apply")
	if strings.Contains(parts[3], ":") {
		writeJSON(w, http.StatusOK, s.tidbGroup(clusterID, groupID, nil))
		return
	}

	if len(parts) == 4 {
		switch r.Method {
		case http.MethodGet:
			writeJSON(w, http.StatusOK, s.tidbGroup(clusterID, groupID, nil))
		case http.MethodPut:
			writeJSON(w, http.StatusOK, s.tidbGroup(clusterID, groupID, readBodyMap(r)))
		case http.MethodDelete:
			s.mu.Lock()
			delete(s.groups, groupKey(clusterID, groupID))
			s.mu.Unlock()
			writeJSON(w, http.StatusOK, map[string]any{})
		default:
			writeJSON(w, http.StatusOK, map[string]any{})
		}
		return
	}

	if len(parts) >= 5 && parts[4] == "privateLinkServices" {
		s.servePrivateLink(w, r, clusterID, groupID, parts[5:])
		return
	}

	writeJSON(w, http.StatusOK, map[string]any{})
}

func (s *server) serveCreateCluster(w http.ResponseWriter, r *http.Request) {
	body := readBodyMap(r)
	id := stringValue(body, "id", "100")
	writeJSON(w, http.StatusOK, s.cluster(id, body))
}

func (s *server) serveListClusters(w http.ResponseWriter) {
	s.mu.Lock()
	defer s.mu.Unlock()
	clusters := make([]map[string]any, 0, len(s.clusters))
	for _, cluster := range s.clusters {
		clusters = append(clusters, cluster)
	}
	writeJSON(w, http.StatusOK, map[string]any{"clusters": clusters})
}

func (s *server) serveCreateTidbGroup(w http.ResponseWriter, r *http.Request, clusterID string) {
	body := readBodyMap(r)
	id := stringValue(body, "id", "200")
	writeJSON(w, http.StatusOK, s.tidbGroup(clusterID, id, body))
}

func (s *server) servePrivateLink(w http.ResponseWriter, r *http.Request, clusterID, groupID string, rest []string) {
	id := "300"
	if len(rest) > 0 && rest[0] != "" {
		id = rest[0]
	}
	if r.Method == http.MethodPost {
		body := readBodyMap(r)
		id = stringValue(body, "id", id)
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"id":             id,
		"tidbGroupId":    groupID,
		"sharedPoolId":   stringValue(s.cluster(clusterID, nil), "sharedPoolId", "1"),
		"serviceDnsName": "cp9-private-link.local",
		"aws": map[string]any{
			"azIds":       []string{"usw2-az1"},
			"serviceName": "com.amazonaws.vpce.us-west-2.cp9-local",
		},
		"status":      "ACTIVE",
		"connections": []any{},
	})
}

func (s *server) serveDataplaneProxy(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet && strings.HasSuffix(r.URL.Path, "/api/v1/public_key") {
		writeJSON(w, http.StatusOK, map[string]any{
			"pem": base64.StdEncoding.EncodeToString([]byte(localPublicKeyPEM)),
		})
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{})
}

func (s *server) cluster(id string, patch map[string]any) map[string]any {
	s.mu.Lock()
	defer s.mu.Unlock()
	cluster, ok := s.clusters[id]
	if !ok {
		cluster = map[string]any{
			"id":              id,
			"tenantId":        "100",
			"sharedPoolId":    "1",
			"requestUnit":     map[string]any{"min": 5000, "max": 20000},
			"ruVersion":       1,
			"gatewayIdentity": "cp9-local",
			"version":         "cp9-local",
			"phase":           "AVAILABLE",
		}
		s.clusters[id] = cluster
	}
	mergeKnown(cluster, patch, "id", "tenantId", "sharedPoolId", "requestUnit", "ruVersion", "gatewayIdentity")
	if cluster["phase"] == nil || cluster["phase"] == "" {
		cluster["phase"] = "AVAILABLE"
	}
	return cloneMap(cluster)
}

func (s *server) tidbGroup(clusterID, groupID string, patch map[string]any) map[string]any {
	if groupID == "" {
		groupID = "200"
	}
	cluster := s.cluster(clusterID, nil)
	key := groupKey(clusterID, groupID)

	s.mu.Lock()
	defer s.mu.Unlock()
	group, ok := s.groups[key]
	if !ok {
		group = map[string]any{
			"id":            groupID,
			"clusterId":     clusterID,
			"tenantId":      stringValue(cluster, "tenantId", "100"),
			"sharedPoolId":  stringValue(cluster, "sharedPoolId", "1"),
			"phase":         "AVAILABLE",
			"version":       "cp9-local",
			"dataplaneName": "cp9-local-dataplane",
			"sqlEndpoint": map[string]any{
				"publicLb": map[string]any{
					"enabled":     true,
					"host":        "cp9-public.local",
					"port":        4000,
					"ipAllowList": []any{},
				},
				"privateLb": map[string]any{
					"enabled":     true,
					"host":        "cp9-private.local",
					"port":        4000,
					"ipAllowList": []any{},
				},
			},
		}
		s.groups[key] = group
	}
	mergeKnown(group, patch, "id", "clusterId", "tenantId", "sharedPoolId")
	mergeSQLEndpoint(group, patch)
	if group["phase"] == nil || group["phase"] == "" {
		group["phase"] = "AVAILABLE"
	}
	return cloneMap(group)
}

func premiumPathParts(path string) []string {
	rest := strings.TrimPrefix(path, premiumPrefix)
	rest = strings.Trim(rest, "/")
	if rest == "" {
		return nil
	}
	return strings.Split(rest, "/")
}

func groupKey(clusterID, groupID string) string {
	return clusterID + "/" + groupID
}

func readBodyMap(r *http.Request) map[string]any {
	if r.Body == nil {
		return nil
	}
	defer r.Body.Close()
	data, err := io.ReadAll(r.Body)
	if err != nil || len(data) == 0 {
		return nil
	}
	var out map[string]any
	if err := json.Unmarshal(data, &out); err != nil {
		log.Printf("ignoring invalid JSON body on %s %s: %v", r.Method, r.URL.Path, err)
		return nil
	}
	return out
}

func mergeKnown(dst map[string]any, src map[string]any, keys ...string) {
	if src == nil {
		return
	}
	for _, key := range keys {
		if value, ok := src[key]; ok && value != nil {
			dst[key] = value
		}
	}
}

func mergeSQLEndpoint(group map[string]any, patch map[string]any) {
	if patch == nil {
		return
	}
	nextRaw, ok := patch["sqlEndpoint"]
	if !ok || nextRaw == nil {
		return
	}
	next, ok := nextRaw.(map[string]any)
	if !ok {
		return
	}
	current, ok := group["sqlEndpoint"].(map[string]any)
	if !ok {
		group["sqlEndpoint"] = next
		ensureLoadBalancerHost(next, "publicLb", "cp9-public.local")
		ensureLoadBalancerHost(next, "privateLb", "cp9-private.local")
		return
	}
	for _, key := range []string{"dnsIdentity", "disablePublicDnsIdentity"} {
		if value, ok := next[key]; ok && value != nil {
			current[key] = value
		}
	}
	mergeLoadBalancer(current, next, "publicLb", "cp9-public.local")
	mergeLoadBalancer(current, next, "privateLb", "cp9-private.local")
}

func mergeLoadBalancer(current, next map[string]any, key, fallbackHost string) {
	nextLB, ok := next[key].(map[string]any)
	if !ok {
		return
	}
	currentLB, ok := current[key].(map[string]any)
	if !ok || currentLB == nil {
		currentLB = map[string]any{}
		current[key] = currentLB
	}
	for k, value := range nextLB {
		if value != nil {
			currentLB[k] = value
		}
	}
	ensureLoadBalancerHost(current, key, fallbackHost)
}

func ensureLoadBalancerHost(endpoint map[string]any, key, fallbackHost string) {
	lb, ok := endpoint[key].(map[string]any)
	if !ok {
		return
	}
	if stringValue(lb, "host", "") == "" {
		lb["host"] = fallbackHost
	}
	if _, ok := lb["port"]; !ok {
		lb["port"] = 4000
	}
}

func stringValue(values map[string]any, key, fallback string) string {
	if values == nil {
		return fallback
	}
	value, ok := values[key]
	if !ok || value == nil {
		return fallback
	}
	switch typed := value.(type) {
	case string:
		if typed == "" {
			return fallback
		}
		return typed
	case float64:
		return fmt.Sprintf("%.0f", typed)
	default:
		return fmt.Sprint(typed)
	}
}

func cloneMap(in map[string]any) map[string]any {
	out := make(map[string]any, len(in))
	for key, value := range in {
		out[key] = value
	}
	return out
}

func writeJSON(w http.ResponseWriter, status int, body any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	if err := json.NewEncoder(w).Encode(body); err != nil {
		log.Printf("write response failed: %v", err)
	}
}
`))
