package crossplane

import (
	"context"
	"fmt"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"

	"github.com/tidbcloud/infra-provider/regional-server/pkg/metrics/objectstorage/enrichment"
)

// This package intentionally uses the Kubernetes dynamic client and unstructured
// objects instead of importing generated Crossplane provider API packages.
//
// The exporter only needs a small, read-only ownership projection from bucket
// CRs: external name, TiDB Cloud metadata labels, and provider tags as a
// fallback. Those fields are stable Kubernetes object data and do not require
// the full provider type surface. Keeping this source unstructured avoids
// coupling the exporter to a specific provider package version and keeps the
// same pattern usable for other clouds' Crossplane bucket/blob/container CRDs.
//
// Future provider enrichers should add ResourceConfig entries for their CRDs and
// keep using this generic source unless they need deep, provider-specific
// spec/status semantics that justify typed Crossplane APIs.
type ResourceConfig struct {
	Name string
	GVR  schema.GroupVersionResource
}

var AWSS3BucketResource = ResourceConfig{
	Name: "aws-s3-bucket",
	GVR:  schema.GroupVersionResource{Group: "s3.aws.upbound.io", Version: "v1beta2", Resource: "buckets"},
}

type Source struct {
	client    dynamic.Interface
	resources []ResourceConfig
}

func NewSource(client dynamic.Interface, resources ...ResourceConfig) *Source {
	if len(resources) == 0 {
		resources = []ResourceConfig{AWSS3BucketResource}
	}
	return &Source{client: client, resources: resources}
}

func (s *Source) Load(ctx context.Context) (map[string]enrichment.BucketLabels, error) {
	result := map[string]enrichment.BucketLabels{}
	for _, resource := range s.resources {
		list, err := s.client.Resource(resource.GVR).List(ctx, metav1.ListOptions{})
		if err != nil {
			return nil, fmt.Errorf("list Crossplane %s: %w", resource.Name, err)
		}
		for i := range list.Items {
			bucketName := externalName(&list.Items[i])
			if bucketName == "" {
				continue
			}
			labels := labelsForBucket(&list.Items[i])
			result[bucketName] = labels
		}
	}
	return result, nil
}

func externalName(obj *unstructured.Unstructured) string {
	annotations := obj.GetAnnotations()
	if annotations != nil && annotations["crossplane.io/external-name"] != "" {
		return annotations["crossplane.io/external-name"]
	}
	return obj.GetName()
}

func labelsForBucket(obj *unstructured.Unstructured) enrichment.BucketLabels {
	metadataLabels := obj.GetLabels()
	labels := enrichment.BucketLabels{
		TenantID:     metadataLabels["tags.tidbcloud.com/tenant"],
		SharedPoolID: metadataLabels["tags.tidbcloud.com/shared-pool"],
	}
	if labels.TenantID != "" && labels.SharedPoolID != "" {
		return labels
	}
	tags, ok, _ := unstructured.NestedStringMap(obj.Object, "spec", "forProvider", "tags")
	if ok {
		if labels.TenantID == "" {
			labels.TenantID = tags["tenant"]
		}
		if labels.SharedPoolID == "" {
			labels.SharedPoolID = tags["shared-pool"]
		}
	}
	return labels
}
