package server

import (
	"context"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/yaml"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	gkestatus "github.com/tidbcloud/infra-provider/pkg/gkeheterogeneous/status"
)

const sensitiveDataMask = "****"

const (
	scaleStatusConfigMapNamePrefix = "tidb-cluster-scale-status-"
	scaleStatusDataKey             = "current-scale-status"
)

// resourceProvider defines the interface for each resource type.
// To add a new resource, implement this interface and register it in resourceProviders.
type resourceProvider interface {
	// Key returns the unique identifier for this resource.
	Key() string
	// IsEnabled checks if this resource is available for the given cluster.
	IsEnabled(ctx context.Context, s *ClusterServer, cluster *cloudv1.Cluster) bool
	// GetMeta returns metadata for UI rendering.
	GetMeta() *apiv1.ClusterResourceMeta
	// GetContent fetches the actual resource content.
	GetContent(ctx context.Context, s *ClusterServer, cluster *cloudv1.Cluster) (string, error)
}

type clusterCRProvider struct{}

func (p *clusterCRProvider) Key() string { return "cluster_cr" }

func (p *clusterCRProvider) IsEnabled(_ context.Context, _ *ClusterServer, _ *cloudv1.Cluster) bool {
	return true
}

func (p *clusterCRProvider) GetMeta() *apiv1.ClusterResourceMeta {
	return &apiv1.ClusterResourceMeta{
		Key:         p.Key(),
		DisplayName: "Cluster CR",
		Description: "Current Cluster CR for the cluster.",
		Type:        apiv1.Type_RESOURCE_TYPE_YAML,
	}
}

func (p *clusterCRProvider) GetContent(_ context.Context, _ *ClusterServer, cluster *cloudv1.Cluster) (string, error) {
	if cluster == nil {
		return "", status.Errorf(codes.InvalidArgument, "cluster is required")
	}
	filteredCluster := filterSensitiveData(cluster)
	filteredCluster.ObjectMeta.ManagedFields = nil
	data, err := yaml.Marshal(filteredCluster)
	if err != nil {
		return "", status.Errorf(codes.Internal, "failed to marshal cluster cr: %v", err)
	}
	return string(data), nil
}

func filterSensitiveData(cluster *cloudv1.Cluster) *cloudv1.Cluster {
	filtered := cluster.DeepCopy()
	if len(filtered.Spec.Kubernetes.CAData) > 0 {
		filtered.Spec.Kubernetes.CAData = []byte(sensitiveDataMask)
	}

	if filtered.Spec.Cluster.TiDB.Audit != nil {
		if filtered.Spec.Cluster.TiDB.Audit.Azure != nil && filtered.Spec.Cluster.TiDB.Audit.Azure.SASToken != "" {
			filtered.Spec.Cluster.TiDB.Audit.Azure.SASToken = sensitiveDataMask
		}
	}

	if filtered.Spec.Cluster.TiDB.PrecheckAudit != nil {
		if filtered.Spec.Cluster.TiDB.PrecheckAudit.Azure != nil && filtered.Spec.Cluster.TiDB.PrecheckAudit.Azure.SASToken != "" {
			filtered.Spec.Cluster.TiDB.PrecheckAudit.Azure.SASToken = sensitiveDataMask
		}
	}

	if filtered.Status.SqlEndpoints.PublicEndpoint != nil && filtered.Status.SqlEndpoints.PublicEndpoint.Token != "" {
		filtered.Status.SqlEndpoints.PublicEndpoint.Token = sensitiveDataMask
	}
	if filtered.Status.SqlEndpoints.PrivateEndpoint != nil && filtered.Status.SqlEndpoints.PrivateEndpoint.Token != "" {
		filtered.Status.SqlEndpoints.PrivateEndpoint.Token = sensitiveDataMask
	}

	for i := range filtered.Status.ExtraEndpoints {
		if filtered.Status.ExtraEndpoints[i].SqlEndpointsStatus.PublicEndpoint != nil &&
			filtered.Status.ExtraEndpoints[i].SqlEndpointsStatus.PublicEndpoint.Token != "" {
			filtered.Status.ExtraEndpoints[i].SqlEndpointsStatus.PublicEndpoint.Token = sensitiveDataMask
		}
		if filtered.Status.ExtraEndpoints[i].SqlEndpointsStatus.PrivateEndpoint != nil &&
			filtered.Status.ExtraEndpoints[i].SqlEndpointsStatus.PrivateEndpoint.Token != "" {
			filtered.Status.ExtraEndpoints[i].SqlEndpointsStatus.PrivateEndpoint.Token = sensitiveDataMask
		}
	}

	if filtered.Status.Metrics != nil && filtered.Status.Metrics.CredentialConfig != "" {
		filtered.Status.Metrics.CredentialConfig = sensitiveDataMask
	}

	if filtered.Status.Proxy != nil && filtered.Status.Proxy.CredentialConfig != "" {
		filtered.Status.Proxy.CredentialConfig = sensitiveDataMask
	}
	return filtered
}

type scaleStatusCMProvider struct{}

func (p *scaleStatusCMProvider) Key() string { return "scale_status_cm" }

func (p *scaleStatusCMProvider) IsEnabled(_ context.Context, _ *ClusterServer, cluster *cloudv1.Cluster) bool {
	return cluster.FeatureEnabled(consts.ClusterFeatureScaleStatusTrack)
}

func (p *scaleStatusCMProvider) GetMeta() *apiv1.ClusterResourceMeta {
	return &apiv1.ClusterResourceMeta{
		Key:         p.Key(),
		DisplayName: "Scale Status ConfigMap",
		Description: `The scale status config map mainly records the cluster scaling status. It includes:
- a_cluster_msg: A brief description of the cluster current condition and the reason why it is not in a normal state.
- component_member_status: Information about components that are not in a normal state and their abnormal members.
- auto_scaling_group_list_status: Information about the node groups (auto scaling groups) in the current cluster.`,
		Type: apiv1.Type_RESOURCE_TYPE_YAML,
	}
}

func (p *scaleStatusCMProvider) GetContent(ctx context.Context, s *ClusterServer, cluster *cloudv1.Cluster) (string, error) {
	cm := &corev1.ConfigMap{}
	cmKey := client.ObjectKey{
		Namespace: cluster.Namespace,
		Name:      scaleStatusConfigMapNamePrefix + cluster.Name,
	}
	if err := s.KubeClient.Get(ctx, cmKey, cm); err != nil {
		if apierrors.IsNotFound(err) {
			return "", nil // ConfigMap not found, return empty
		}
		return "", status.Errorf(codes.Internal, "failed to get scale status config map: %v", err)
	}
	if cm.Data != nil {
		return cm.Data[scaleStatusDataKey], nil
	}
	return "", nil
}

type gkeHeterogeneousStatusCMProvider struct{}

func (p *gkeHeterogeneousStatusCMProvider) Key() string { return "gke_heterogeneous_status_cm" }

func (p *gkeHeterogeneousStatusCMProvider) IsEnabled(ctx context.Context, s *ClusterServer, cluster *cloudv1.Cluster) bool {
	if cluster == nil || cluster.GetProviderType() != cloudv1.ProviderTypeGKE {
		return false
	}
	return p.statusConfigMapExists(ctx, s, cluster)
}

func (p *gkeHeterogeneousStatusCMProvider) statusConfigMapExists(ctx context.Context, s *ClusterServer, cluster *cloudv1.Cluster) bool {
	if s == nil || s.KubeClient == nil || cluster == nil {
		return false
	}
	cm := &corev1.ConfigMap{}
	if err := s.KubeClient.Get(ctx, gkeHeterogeneousStatusConfigMapKey(cluster), cm); err != nil {
		return false
	}
	return true
}

func (p *gkeHeterogeneousStatusCMProvider) GetMeta() *apiv1.ClusterResourceMeta {
	return &apiv1.ClusterResourceMeta{
		Key:         p.Key(),
		DisplayName: "GKE Heterogeneous Status ConfigMap",
		Description: `The GKE heterogeneous status config map records the heterogeneous placement snapshot. It includes:
	- probeStatus: Current placement probe status.
	- validIntent: Whether the heterogeneous deploy intent is valid.
	- components: Component pod placement and node usage details.
	- scaleWorkflow: Related heterogeneous scale workflow state.`,
		Type: apiv1.Type_RESOURCE_TYPE_YAML,
	}
}

func (p *gkeHeterogeneousStatusCMProvider) GetContent(ctx context.Context, s *ClusterServer, cluster *cloudv1.Cluster) (string, error) {
	cm := &corev1.ConfigMap{}
	cmKey := client.ObjectKey{
		Namespace: cluster.Namespace,
		Name:      gkestatus.ConfigMapNamePrefix + cluster.Name,
	}
	if err := s.KubeClient.Get(ctx, cmKey, cm); err != nil {
		if apierrors.IsNotFound(err) {
			return "", nil
		}
		tilog.WithContextL(ctx).Warn("failed to get gke heterogeneous status config map",
			zap.String("namespace", cmKey.Namespace),
			zap.String("name", cmKey.Name),
			zap.Error(err),
		)
		return "", nil
	}
	if cm.Data != nil {
		return cm.Data[gkestatus.ConfigMapDataKey], nil
	}
	return "", nil
}

func gkeHeterogeneousStatusConfigMapKey(cluster *cloudv1.Cluster) client.ObjectKey {
	return client.ObjectKey{
		Namespace: cluster.Namespace,
		Name:      gkestatus.ConfigMapNamePrefix + cluster.Name,
	}
}

var resourceProviders = []resourceProvider{
	&scaleStatusCMProvider{},
	&gkeHeterogeneousStatusCMProvider{},
	&clusterCRProvider{},
}

func (s *ClusterServer) ListClusterResources(ctx context.Context, req *apiv1.ListClusterResourcesRequest) (*apiv1.ListClusterResourcesResponse, error) {
	logger := tilog.WithContextL(ctx).With(zap.String("cluster", req.ClusterId))
	cluster, err := s.getClusterCR(ctx, req.ClusterId)
	if err != nil {
		return nil, err // getClusterCR already returns proper gRPC status error
	}

	resources := make(map[string]*apiv1.ClusterResource)
	for _, provider := range resourceProviders {
		if !provider.IsEnabled(ctx, s, cluster) {
			continue
		}
		content, err := provider.GetContent(ctx, s, cluster)
		if err != nil {
			logger.Error("failed to get resource content", zap.String("key", provider.Key()), zap.Error(err))
			return nil, err
		}
		if content != "" {
			resources[provider.Key()] = &apiv1.ClusterResource{Content: content}
		}
	}
	return &apiv1.ListClusterResourcesResponse{Resources: resources}, nil
}

func (s *ClusterServer) ListClusterResourceMetas(ctx context.Context, req *apiv1.ListClusterResourceMetasRequest) (*apiv1.ListClusterResourceMetasResponse, error) {
	cluster, err := s.getClusterCR(ctx, req.ClusterId)
	if err != nil {
		return nil, err // getClusterCR already returns proper gRPC status error
	}

	metas := make([]*apiv1.ClusterResourceMeta, 0, len(resourceProviders))
	for _, provider := range resourceProviders {
		if provider.IsEnabled(ctx, s, cluster) {
			metas = append(metas, provider.GetMeta())
		}
	}
	return &apiv1.ListClusterResourceMetasResponse{Metas: metas}, nil
}
