package e2e

import (
	"fmt"
	"strings"

	"github.com/gertd/go-pluralize"
	"github.com/pingcap/log"
	ctlproxy "github.com/tidbcloud/infra-provider/regional-server/test/api/dataplane-manager/client/control_plane_k8s_proxy_service"
	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

// ResourceStatus holds the checked status of a specific Kubernetes resource.
type ResourceStatus struct {
	Name       string `json:"name"`
	Group      string `json:"group"`
	Kind       string `json:"kind"`
	APIVersion string `json:"apiVersion"`
	IsReady    bool   `json:"isReady"`
	IsSynced   bool   `json:"isSynced"`
	// CheckError reports on the checker's operation (e.g., success or a specific failure).
	CheckError string `json:"checkError"`
	// ResourceError contains the error message from the CR's status conditions if it's in a failed state.
	ResourceError string `json:"resourceError,omitempty"`
}

type UpboundCRD struct {
	Group      string `json:"group"`
	Kind       string `json:"kind"`
	APIVersion string `json:"apiVersion"`
}

// ConditionType defines the type of a condition in a resource's status.
type ConditionType string

const (
	ReadyCondition  ConditionType = "Ready"
	SyncedCondition ConditionType = "Synced"
)

// ConditionStatus defines the value of a condition status.
type ConditionStatus string

const (
	StatusTrue  ConditionStatus = "True"
	StatusFalse ConditionStatus = "False"
)

// Checker encapsulates the logic for checking Upbound resource statuses.
type Checker struct {
	client ctlproxy.ClientService

	pluralizeClient *pluralize.Client
}

// NewChecker creates a new Checker with a Kubernetes dynamic client.
func NewChecker(client ctlproxy.ClientService) *Checker {
	return &Checker{
		client:          client,
		pluralizeClient: pluralize.NewClient(),
	}
}

// CheckOwnedResources inspects ProviderConfigUsages and checks the status of the resources that own them.
func (c *Checker) CheckOwnedResources(pcuGVR schema.GroupVersionResource, labelSelector string) ([]ResourceStatus, error) {
	listParams := ctlproxy.ControlPlaneK8sProxyServiceListResources3Params{
		Group:          pcuGVR.Group,
		ResourcePlural: pcuGVR.Resource,
		Version:        pcuGVR.Version,
	}
	resp, err := c.client.ControlPlaneK8sProxyServiceListResources3(&listParams)
	if err != nil {
		return nil, fmt.Errorf("failed to list %s: %w", pcuGVR.Resource, err)
	}

	pcuList := &unstructured.Unstructured{Object: resp.Payload.(map[string]interface{})}
	var crdMap = make(map[UpboundCRD]struct{})
	_ = pcuList.EachListItem(func(object runtime.Object) error {
		pcu := object.(*unstructured.Unstructured)
		crds := c.getCRDFromOwner(pcu)
		for _, item := range crds {
			crdMap[item] = struct{}{}
		}
		return nil
	})
	ret := make([]ResourceStatus, 0)
	for key, _ := range crdMap {
		statuses, err := c.getResourceStatus(key, labelSelector)
		if err != nil {
			return nil, err
		}
		ret = append(ret, statuses...)
	}
	return ret, err
}

// checkResourceFromOwner extracts the owner reference and checks its status.
func (c *Checker) getCRDFromOwner(pcu *unstructured.Unstructured) []UpboundCRD {
	ownerRefs := pcu.GetOwnerReferences()

	var ret = make([]UpboundCRD, 0)
	for _, owner := range ownerRefs {
		apiVersion := owner.APIVersion
		kind := owner.Kind
		groupVersion := strings.Split(apiVersion, "/")
		group := groupVersion[0]
		version := groupVersion[1]
		ret = append(ret, UpboundCRD{
			Group:      group,
			Kind:       kind,
			APIVersion: version,
		})
	}
	return ret
}

func (c *Checker) getResourceStatus(crd UpboundCRD, labelSelector string) ([]ResourceStatus, error) {
	var labelSelectorPtr *string
	if labelSelector != "" {
		labelSelectorPtr = &labelSelector
	}
	listParams := ctlproxy.ControlPlaneK8sProxyServiceListResources3Params{
		Group:          crd.Group,
		ResourcePlural: strings.ToLower(c.pluralizeClient.Plural(crd.Kind)),
		Version:        crd.APIVersion,
		LabelSelector:  labelSelectorPtr,
	}
	resp, err := c.client.ControlPlaneK8sProxyServiceListResources3(&listParams)
	if err != nil {
		return nil, fmt.Errorf("failed to list %s: %w", crd.Kind, err)
	}
	ret := make([]ResourceStatus, 0)
	crList := &unstructured.Unstructured{Object: resp.Payload.(map[string]interface{})}
	_ = crList.EachListItem(func(object runtime.Object) error {
		upboundCr := object.(*unstructured.Unstructured)
		isReady, isSynced, resourceErr := evaluateConditions(upboundCr)
		ret = append(ret, ResourceStatus{
			Name:          upboundCr.GetName(),
			Group:         crd.Group,
			Kind:          upboundCr.GetKind(),
			APIVersion:    upboundCr.GetAPIVersion(),
			IsReady:       isReady,
			IsSynced:      isSynced,
			ResourceError: resourceErr,
		})
		return nil
	})
	return ret, nil
}

// evaluateConditions parses the .status.conditions field, returning status and any error message.
func evaluateConditions(cr *unstructured.Unstructured) (isReady bool, isSynced bool, resourceError string) {
	conditions, found, err := unstructured.NestedSlice(cr.Object, "status", "conditions")
	if err != nil || !found {
		return false, false, "status.conditions not found or failed to parse"
	}

	var readyError, syncedError string
	for _, c := range conditions {
		cond, ok := c.(map[string]interface{})
		if !ok {
			continue
		}
		condType, _ := cond["type"].(string)
		condStatus, _ := cond["status"].(string)
		condMessage, _ := cond["message"].(string)

		if ConditionType(condType) == SyncedCondition {
			if ConditionStatus(condStatus) == StatusTrue {
				isSynced = true
			} else if ConditionStatus(condStatus) == StatusFalse && condMessage != "" {
				syncedError = condMessage
			}
		}
		if ConditionType(condType) == ReadyCondition {
			if ConditionStatus(condStatus) == StatusTrue {
				isReady = true
			} else if ConditionStatus(condStatus) == StatusFalse && condMessage != "" {
				readyError = condMessage
			}
		}
	}

	if syncedError != "" {
		resourceError = syncedError
	} else {
		resourceError = readyError
	}
	return isReady, isSynced, resourceError
}

func IsAllUpboundCRReady(provider string, client ctlproxy.ClientService, labelSelector string) bool {
	checker := NewChecker(client)
	pcuGVR := schema.GroupVersionResource{
		Group:    "aws.upbound.io",
		Version:  "v1beta1",
		Resource: "providerconfigusages",
	}
	status, err := checker.CheckOwnedResources(pcuGVR, labelSelector)
	if err != nil {
		log.Error("check resource failed", zap.Error(err))
		return false
	}
	if provider == "alicloud" {
		// alicloud uses both aws upbound provider and alibabacloud crossplane provider
		gvr := schema.GroupVersionResource{
			Group:    "alibabacloud.crossplane.io",
			Version:  "v1beta1",
			Resource: "providerconfigusages",
		}
		alicloudStatus, err := checker.CheckOwnedResources(gvr, labelSelector)
		if err != nil {
			log.Error("check resource failed", zap.Error(err))
			return false
		}
		status = append(status, alicloudStatus...)
	}
	ready := len(status) > 0
	if len(status) == 0 {
		log.Error("no Upbound resources found", zap.String("provider", provider))
	}
	for _, item := range status {
		if !item.IsReady || !item.IsSynced {
			ready = false
			log.Info("Upbound CR not ready",
				zap.String("name", item.Name),
				zap.String("kind", item.Kind),
				zap.String("apiVersion", item.APIVersion),
				zap.String("checkError", item.CheckError),
				zap.String("resourceErr", item.ResourceError))
		}
	}

	return ready
}

func getLabelSelector(label, value string) string {
	return fmt.Sprintf("%s=%s", label, value)
}
