package tenants

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
)

// dedicatedSelector returns the Kubernetes label-set expression that matches
// Cluster resources carrying any of the dedicated biz types.
func dedicatedSelector(bizTypes []string) string {
	return fmt.Sprintf("%s in (%s)", BizTypeLabel, strings.Join(bizTypes, ","))
}

// KubectlLister abstracts `kubectl get clusters -A -l <selector> -o json`
// so tests inject fakes without spawning kubectl.
//
// Production wires kubectlShellLister, which shells out via runCLI.
type KubectlLister interface {
	List(ctx context.Context, selector string) ([]byte, error)
}

// kubectlClusters is the wire shape of `kubectl get clusters -o json`.
//
// Labels is a map because the key is a slash-bearing annotation; struct tags
// with slashes work fine in encoding/json.
type kubectlClusters struct {
	Items []kubectlClusterItem `json:"items"`
}

type kubectlClusterItem struct {
	Metadata struct {
		Labels map[string]string `json:"labels"`
	} `json:"metadata"`
	Spec struct {
		VictoriaAccount struct {
			AccountID any `json:"accountID"`
			ProjectID any `json:"projectID"`
		} `json:"victoriaAccount"`
	} `json:"spec"`
}

// EntriesFromKubectlJSON parses `kubectl get clusters -A -o json` output into
// a list of tenant Entries.
//
// Filters by the dedicated biz types, deduplicates by tenant ID (first-seen
// wins), and labels every entry with Source=SourceK8s. Items missing the
// bizType label or tenant fields are silently skipped — the k8s API guarantees
// them, but corrupted resources should not abort the discovery loop.
func EntriesFromKubectlJSON(payload []byte, bizTypes []string) ([]Entry, error) {
	var doc kubectlClusters
	if err := json.Unmarshal(payload, &doc); err != nil {
		return nil, fmt.Errorf("parse kubectl json: %w", err)
	}
	allowed := map[string]bool{}
	for _, b := range bizTypes {
		allowed[b] = true
	}
	out := []Entry{}
	seen := map[string]bool{}
	for _, item := range doc.Items {
		bizType, ok := item.Metadata.Labels[BizTypeLabel]
		if !ok {
			continue
		}
		if !allowed[bizType] {
			continue
		}
		tenant := TenantID(
			normalizeID(item.Spec.VictoriaAccount.AccountID),
			normalizeID(item.Spec.VictoriaAccount.ProjectID),
			DefaultProjectID,
		)
		if tenant == "" || seen[tenant] {
			continue
		}
		seen[tenant] = true
		out = append(out, Entry{Tenant: tenant, BizType: bizType, Source: SourceK8s})
	}
	return out, nil
}

// normalizeID stringifies a kubectl JSON value (string or number) into the
// canonical ID form.
//
// Non-string / non-numeric values return "".
func normalizeID(v any) string {
	switch x := v.(type) {
	case string:
		return strings.TrimSpace(x)
	case float64:
		// JSON numbers come through encoding/json as float64. Format as
		// an integer when whole; otherwise fall back to the original
		// representation.
		if x == float64(int64(x)) {
			return fmt.Sprintf("%d", int64(x))
		}
		return fmt.Sprintf("%v", x)
	case nil:
		return ""
	default:
		return ""
	}
}
