package merger

import (
	"context"
	"fmt"

	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

// CRDSchemaResolver reads the served version's OpenAPI schema from the live
// CustomResourceDefinition. Resolving at execution time keeps the merge
// behavior aligned with the CRD installed in that dataplane rather than with a
// schema compiled into capacity-planner.
type CRDSchemaResolver struct {
	reader client.Reader
	mapper meta.RESTMapper
}

var _ SchemaResolver = (*CRDSchemaResolver)(nil)

// NewCRDSchemaResolver creates a resolver backed by the Kubernetes API.
func NewCRDSchemaResolver(reader client.Reader, mapper meta.RESTMapper) *CRDSchemaResolver {
	return &CRDSchemaResolver{reader: reader, mapper: mapper}
}

// Resolve returns the OpenAPI schema for gvk from its CRD.
func (r *CRDSchemaResolver) Resolve(
	ctx context.Context,
	gvk schema.GroupVersionKind,
) (*apiextensionsv1.JSONSchemaProps, error) {
	if r == nil || r.reader == nil {
		return nil, fmt.Errorf("CRD schema resolver: reader is required")
	}
	if r.mapper == nil {
		return nil, fmt.Errorf("CRD schema resolver: REST mapper is required")
	}
	if gvk.Group == "" {
		return nil, fmt.Errorf("CRD schema resolver: %s is not a custom-resource GVK", gvk)
	}

	mapping, err := r.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
	if err != nil {
		return nil, fmt.Errorf("map %s to resource: %w", gvk, err)
	}
	crdName := mapping.Resource.Resource + "." + mapping.Resource.Group
	var crd apiextensionsv1.CustomResourceDefinition
	if err := r.reader.Get(ctx, client.ObjectKey{Name: crdName}, &crd); err != nil {
		return nil, fmt.Errorf("get CustomResourceDefinition %q: %w", crdName, err)
	}
	if crd.Spec.Group != gvk.Group || crd.Spec.Names.Kind != gvk.Kind {
		return nil, fmt.Errorf("CustomResourceDefinition %q serves %s/%s, not %s",
			crdName, crd.Spec.Group, crd.Spec.Names.Kind, gvk)
	}

	for i := range crd.Spec.Versions {
		version := &crd.Spec.Versions[i]
		if version.Name != gvk.Version {
			continue
		}
		if !version.Served {
			return nil, fmt.Errorf("CustomResourceDefinition %q version %q is not served", crdName, gvk.Version)
		}
		if version.Schema == nil || version.Schema.OpenAPIV3Schema == nil {
			return nil, fmt.Errorf("CustomResourceDefinition %q version %q has no OpenAPI schema", crdName, gvk.Version)
		}
		return version.Schema.OpenAPIV3Schema.DeepCopy(), nil
	}
	return nil, fmt.Errorf("CustomResourceDefinition %q does not define version %q", crdName, gvk.Version)
}
