package resources

import (
	"encoding/json"
	"strconv"

	"github.com/prometheus/client_golang/prometheus"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"

	eksv1alpha1 "github.com/tidbcloud/eks-provider/api/v1alpha1"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/util"

	"github.com/tidbcloud/eks-provider/infra-exporter/utils"
)

var (
	networkGVR = schema.GroupVersionResource{
		Group:    "cloud.pingcap.com",
		Version:  "v1",
		Resource: "networks",
	}

	networkInfo = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "exporter", "network"),
		"Information about Network CR.",
		[]string{
			"name",
			"namespace",
			"dataplane",
			"bucket_name",
			"tenant_id",
			"project_id",
			"generation",
			"region",
			"provider",
			"cidr",
			"account",
			"vpc",
			"status",
			"paused",
		}, nil)
)

type network struct {
	defaultResource
}

var _ Interface = &network{}

func (n network) HasDedicatedLabel() bool {
	return true
}

func (n network) GVR() schema.GroupVersionResource {
	return networkGVR
}

func (n network) Desc() *prometheus.Desc {
	return networkInfo
}

func (n network) FilterOut(u *unstructured.Unstructured) bool {
	return false
}

func (n network) Extract(obj *unstructured.Unstructured) prometheus.Metric {
	bytes, _ := obj.MarshalJSON()

	var networkCR cloudv1.Network
	_ = json.Unmarshal(bytes, &networkCR)

	var (
		dataplane, bucketName, account, vpc string
	)

	if networkCR.Spec.AccountRef != nil {
		account = networkCR.Spec.AccountRef.Name
	}

	paused := false
	if networkCR.Spec.Paused != nil {
		paused = *networkCR.Spec.Paused
	}

	if isGardenerArch(&networkCR) {
		status, err := cloudv1.GardenerNetworkStatusFromNetwork(&networkCR)
		if err != nil {
			utils.Logger.Error(err, "failed to get gardener network status")
		} else {
			dataplane, bucketName = status.ShootName, status.BucketName

			switch networkCR.Spec.Provider {
			case cloudv1.ProviderTypeAWS:
				if status.AWS.VpcID != nil {
					vpc = *status.AWS.VpcID
				}
			case cloudv1.ProviderTypeGCP:
				if status.GCP.NetworkName != nil {
					vpc = *status.GCP.NetworkName
				}
			}
		}
	} else {
		dataplane = util.GetAnnotation(&networkCR, consts.AnnotationKeyDataplaneName)
		providerType := util.GetLabel(&networkCR, cloudv1.ProviderTypeLabel)
		switch providerType {
		case consts.ProviderTypeEKS:
			spec, err := eksv1alpha1.EKSNetworkSpecForNetwork(&networkCR)
			if err != nil {
				utils.Logger.Error(err, "failed to get eks network spec")
			} else {
				vpc = spec.VpcId
			}

			status, err := eksv1alpha1.EKSNetworkStatusFromNetwork(&networkCR)
			if err != nil {
				utils.Logger.Error(err, "failed to get eks network status")
			} else {
				bucketName = status.BucketName
			}
		case consts.ProviderTypeGKE:
			spec, err := eksv1alpha1.GKENetworkSpecForNetwork(&networkCR)
			if err != nil {
				utils.Logger.Error(err, "failed to get gke network spec")
			} else {
				vpc = spec.Network
			}

			status, err := eksv1alpha1.GKENetworkStatusFromNetwork(&networkCR)
			if err != nil {
				utils.Logger.Error(err, "failed to get gke network status")
			} else {
				bucketName = status.BucketName
			}
		}
	}

	return prometheus.MustNewConstMetric(
		networkInfo,
		prometheus.GaugeValue,
		1.0,
		networkCR.Name,
		networkCR.Namespace,
		dataplane,
		bucketName,
		networkCR.Labels[consts.TenantLabelKey],
		networkCR.Labels[consts.ProjectLabelKey],
		strconv.FormatInt(networkCR.Generation, 10),
		networkCR.Spec.Region,
		networkCR.Spec.Provider,
		networkCR.Spec.CIDR,
		account,
		vpc,
		networkCR.Status.Phase,
		strconv.FormatBool(paused),
	)
}

func isGardenerArch(network *cloudv1.Network) bool {
	providerType := util.GetLabel(network, cloudv1.ProviderTypeLabel)
	return providerType == cloudv1.ProviderTypeAWS || providerType == cloudv1.ProviderTypeGCP
}
