package common

import (
	"context"
	"encoding/base64"
	"fmt"
	"strings"
	"time"

	eksv1beta1 "github.com/crossplane-contrib/provider-aws/apis/eks/v1beta1"
	gkev1beta2 "github.com/crossplane-contrib/provider-gcp/apis/container/v1beta2"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/dataplane"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	o11yapi "github.com/tidbcloud/infra-provider/regional-server/pkg/common/o11ycli/api"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/utils"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/o11yroute/task"
	aksv1beta1 "github.com/upbound/provider-azure/apis/containerservice/v1beta1"
	_ "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

// Cluster call cluster controller to create cluster.
type Cluster struct {
	*task.BaseTask
}

func NewCluster(ctx *task.Context) *Cluster {
	return &Cluster{
		BaseTask: &task.BaseTask{
			Context: ctx,
		},
	}
}

func (t *Cluster) Name() string {
	return "Cluster"
}

func (t *Cluster) Create(ctx context.Context) deploy.Result {
	// call o11y service to create
	req, err := t.buildCreateClusterReq(ctx)
	if err != nil {
		return deploy.Panic(fmt.Errorf("build cluster request: %w", err))
	}
	cluster, err := t.O11yClient.CreateCluster(ctx, req)
	if err != nil {
		return deploy.Panic(fmt.Errorf("create cluster: %w", err)).WithRequeuAfter(30 * time.Second)
	}
	if t.shouldUpdateAgent(*cluster) {
		if err := t.O11yClient.UpdateClusterAgent(ctx, t.buildUpdateAgentReq()); err != nil {
			return deploy.Panic(fmt.Errorf("update cluster agent: %w", err))
		}
	}

	if o11yapi.IsPausing(cluster.Status) && !t.O11yRoute.Spec.Suspend {
		if err := t.O11yClient.ResumeCluster(ctx, o11yapi.GetO11yVendor(t.APPBox.Spec.Provider), t.APPBox.Spec.Region, *t.O11yRoute.Status.O11yID); err != nil {
			return deploy.Panic(fmt.Errorf("resume cluster: %w", err))
		}
	}
	if !o11yapi.IsPausing(cluster.Status) && t.O11yRoute.Spec.Suspend {
		if err := t.O11yClient.PauseCluster(ctx, o11yapi.GetO11yVendor(t.APPBox.Spec.Provider), t.APPBox.Spec.Region, *t.O11yRoute.Status.O11yID); err != nil {
			return deploy.Panic(fmt.Errorf("pause cluster: %w", err))
		}
	}

	t.O11yRoute.Status.O11yID = ptr.To(cluster.ID)
	return deploy.Done()
}

func (t *Cluster) buildCreateClusterReq(ctx context.Context) (*o11yapi.CreateRequest, error) {
	req := &o11yapi.CreateRequest{}
	req.BizType = t.bizType()
	req.ShareMode = o11yapi.Shared
	tenants, projects := utils.GetRelatedTenantProjects(t.O11yRoute.GetLabels())
	if len(tenants) > 0 {
		req.TenantID = tenants[0]
	}
	if len(projects) > 0 {
		req.ProjectID = projects[0]
	}
	req.ApplicationID = t.O11yRoute.Spec.ApplicationID
	req.MuteAlert = false
	req.Region = t.APPBox.Spec.Region
	req.Vendor = o11yapi.GetO11yVendor(t.APPBox.Spec.Provider)

	req.RuleTemplateID = o11yapi.GetAlertTmplByClusterAndEnv(req.ApplicationID, t.Config.Env)
	if req.ApplicationID != o11yapi.ApplicationIDInfra {
		req.Observed = getObservedFromRoute(*t.O11yRoute)
	}
	// if the o11ycluster is k8s-infra and seedClusterID is set, send the seedClusterID
	if req.ApplicationID == o11yapi.ApplicationIDInfra && t.Config.O11ySeedClusterID != "" {
		req.SeedClusterID = t.Config.O11ySeedClusterID
	}

	switch t.APPBox.Spec.Provider {
	case consts.ProviderTypeEKS:
		eks := &eksv1beta1.Cluster{}
		key := client.ObjectKey{Name: t.APPBox.Status.KubernetesClusterName}
		if err := t.Local.Get(ctx, key, eks); err != nil {
			return nil, err
		}

		oidc := strings.TrimPrefix(eks.Status.AtProvider.Identity.OIDC.Issuer, "https://")
		snIds := make([]string, 0, len(t.VPC.Status.NodeSubnets))
		for _, sn := range t.VPC.Status.NodeSubnets {
			snIds = append(snIds, sn.SubnetID)
		}
		req.AgentEnvironment = o11yapi.AgentEnvironment{
			Dataplane: o11yapi.Dataplane{
				ClusterName:     t.APPBox.Status.KubernetesClusterName,
				ClusterEndpoint: eks.Status.AtProvider.Endpoint,
				ClusterCAData:   &eks.Status.AtProvider.CertificateAuthorityData,
			},
			AWS: &o11yapi.AWSAgentEnvironment{
				VpcID:           t.VPC.Status.VPC.VPCID,
				Account:         t.VPC.Spec.AccountRef.AccountID,
				NodeSubnetIDs:   snIds,
				SecurityGroupID: t.VPC.Status.SecurityGroup.SecurityGroupID,
				OIDC:            oidc,
			},
		}
	case consts.ProviderTypeGKE:
		gke := &gkev1beta2.Cluster{}
		key := client.ObjectKey{Name: t.APPBox.Status.KubernetesClusterName}
		if err := t.Local.Get(ctx, key, gke); err != nil {
			return nil, err
		}
		snNames := make([]string, 0, len(t.VPC.Status.NodeSubnets))
		for _, sn := range t.VPC.Status.NodeSubnets {
			snNames = append(snNames, sn.Name)
		}
		req.AgentEnvironment = o11yapi.AgentEnvironment{
			Dataplane: o11yapi.Dataplane{
				ClusterName:     t.APPBox.Status.KubernetesClusterName,
				ClusterEndpoint: gke.Status.AtProvider.Endpoint,
			},
			GCP: &o11yapi.GCPAgentEnvironment{
				GCPProject:  t.VPC.Spec.AccountRef.AccountID,
				NetworkName: t.VPC.Status.VPC.VPCID,
				Subnets:     snNames,
			},
		}
	case consts.ProviderTypeAKS:
		aks := &aksv1beta1.KubernetesCluster{}
		key := client.ObjectKey{Name: t.APPBox.Status.KubernetesClusterName}
		if err := t.Local.Get(ctx, key, aks); err != nil {
			return nil, err
		}
		secretRef := t.APPBox.Status.KubeConfigSecretRef
		kubeconfig, err := dataplane.GetDataplaneKubeConfigFromMetaSecret(ctx, t.Local, secretRef)
		if err != nil {
			return nil, fmt.Errorf("failed to get kubeconfig from secret %s/%s, err: %w", secretRef.Namespace, secretRef.Name, err)
		}
		subnetID := ""
		if t.VPC.Status.NodeSubnets != nil && len(t.VPC.Status.NodeSubnets) > 0 {
			subnetID = t.VPC.Status.NodeSubnets[0].SubnetID
		}
		req.AgentEnvironment = o11yapi.AgentEnvironment{
			Dataplane: o11yapi.Dataplane{
				ClusterName:       t.APPBox.Status.KubernetesClusterName,
				ClusterKubeConfig: ptr.To(base64.StdEncoding.EncodeToString(kubeconfig)),
			},
			Azure: &o11yapi.AzureAgentEnvironment{
				NetworkName:    t.VPC.Status.VPC.VPCID,
				ResourceGroup:  ptr.Deref(aks.Spec.ForProvider.ResourceGroupName, ""),
				SubscriptionID: t.VPC.Spec.AccountRef.AccountID,
				Subnet:         subnetID,
				OIDC:           ptr.Deref(aks.Status.AtProvider.OidcIssuerURL, ""),
			},
		}
	}

	return req, nil
}

func getObservedFromRoute(o v1alpha1.O11yRoute) o11yapi.Observed {
	out := o11yapi.Observed{
		TiDBClusterTopology: &o11yapi.ClusterTopologyType{
			TiDB: o11yapi.TiDB{
				Replicas: o.Spec.Agent.TargetClusterTopology.TiDB.Replicas,
			},
			PD: o11yapi.PD{
				Replicas: o.Spec.Agent.TargetClusterTopology.PD.Replicas,
			},
			TiKV: o11yapi.TiKV{
				Replicas: o.Spec.Agent.TargetClusterTopology.TiKV.Replicas,
			},
		},
		TiDBVersion: o.Spec.Agent.TargetClusterVersion,
	}

	if o.Spec.Agent.TargetClusterTopology.TiFlash != nil {
		out.TiDBClusterTopology.TiFlash = &o11yapi.TiFlash{
			Replicas: o.Spec.Agent.TargetClusterTopology.TiFlash.Replicas,
		}
	}
	return out
}

func (t *Cluster) shouldUpdateAgent(c o11yapi.ObservabilityCluster) bool {
	return t.O11yRoute.Status.O11yID != nil &&
		t.O11yRoute.Status.Phase == v1alpha1.O11yRoutePhaseActive &&
		t.O11yRoute.Spec.ApplicationID != o11yapi.ApplicationIDInfra &&
		o11yapi.ClusterReady(c.Status)
}

func (t *Cluster) buildUpdateAgentReq() *o11yapi.UpdateAgentRequest {
	req := new(o11yapi.UpdateAgentRequest)
	req.ID = *t.O11yRoute.Status.O11yID
	req.Vendor = o11yapi.GetO11yVendor(t.APPBox.Spec.Provider)

	req.Region = t.APPBox.Spec.Region
	req.Observed = getObservedFromRoute(*t.O11yRoute)
	return req

}

func (t *Cluster) Delete(ctx context.Context) deploy.Result {
	//check o11yroute phase
	if t.O11yRoute.Status.Phase != v1alpha1.O11yRoutePhaseDeleting {
		t.O11yRoute.Status.Phase = v1alpha1.O11yRoutePhaseDeleting
	}
	req := &o11yapi.ListRequest{}
	tenants, projects := utils.GetRelatedTenantProjects(t.O11yRoute.GetLabels())
	if len(tenants) > 0 {
		req.TenantID = tenants[0]
	}
	if len(projects) > 0 {
		req.ProjectID = projects[0]
	}
	req.Vendor = o11yapi.GetO11yVendor(t.APPBox.Spec.Provider)
	req.Region = t.APPBox.Spec.Region
	req.BizType = t.bizType()
	req.ClusterID = t.O11yRoute.Spec.ApplicationID

	cs, err := t.O11yClient.ListClusters(ctx, req)
	if err != nil {
		return deploy.Panic(err)
	}

	if len(cs) == 0 {
		return deploy.Done()
	}
	if err = t.O11yClient.DeleteCluster(ctx, o11yapi.GetO11yVendor(t.APPBox.Spec.Provider), t.APPBox.Spec.Region, *t.O11yRoute.Status.O11yID); err != nil {
		return deploy.Panic(err)
	}
	return deploy.Error(fmt.Errorf("O11y Cluster id %s exists", *t.O11yRoute.Status.O11yID))
}

func (t *Cluster) bizType() string {
	if t.Config.O11yBizType != "" {
		return t.Config.O11yBizType
	}
	return o11yapi.GetBizTypeByEnv(t.Config.Env)
}
