package implement

import (
	"context"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	agentv2 "github.com/tidbcloud/observability/agent-control-server/api/v2"
	"github.com/tidbcloud/observability/agent-control-server/pkg/agentcontrol/api"
)

const (
	o11yNamespace     = "observability"
	k8sAgentClusterID = "k8s-infra"
)

var _ api.AgentControlAPI = &AgentControlImpl{}

type AgentControlImpl struct {
	client    client.Client
	env       string
	vendor    string
	region    string
	bizType   string
	namespace string
}

type AgentControlConfig struct {
	Namespace string
	BizType   string
}

func NewAgentControlImpl(client client.Client, env, vendor, region string, agentNamespace ...string) *AgentControlImpl {
	config := AgentControlConfig{}
	if len(agentNamespace) > 0 && agentNamespace[0] != "" {
		config.Namespace = agentNamespace[0]
	}
	return NewAgentControlImplWithConfig(client, env, vendor, region, config)
}

func NewAgentControlImplWithConfig(client client.Client, env, vendor, region string, config AgentControlConfig) *AgentControlImpl {
	if config.Namespace == "" {
		config.Namespace = o11yNamespace
	}
	if config.BizType == "" {
		config.BizType = getBizTypeByEnv(env)
	}
	return &AgentControlImpl{
		client:    client,
		env:       env,
		vendor:    vendor,
		region:    region,
		bizType:   config.BizType,
		namespace: config.Namespace,
	}
}

func (a *AgentControlImpl) CreateK8sAgent(ctx context.Context, req *api.CreateK8sAgentRequest) (*api.CreateK8sAgentResponse, error) {
	agent := &agentv2.O11yK8sAgent{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: a.namespace,
			Name:      req.ProjectID,
		},
	}
	_, err := controllerutil.CreateOrUpdate(ctx, a.client, agent, func() error {
		agent.Spec = agentv2.O11yK8sAgentSpec{
			KubernetesClusterName: req.Dataplane.ClusterName,
			O11y: agentv2.O11yConfig{
				BizType:       a.bizType,
				TenantID:      req.TenantID,
				ProjectID:     req.ProjectID,
				ApplicationID: k8sAgentClusterID,
			},
		}
		switch {
		case req.Dataplane.AWS != nil:
			agent.Spec.EKS = &agentv2.EKSConfig{
				VpcID:            req.Dataplane.AWS.VpcID,
				AccountID:        req.Dataplane.AWS.AccountID,
				SecurityGroupID:  req.Dataplane.AWS.SecurityGroupID,
				NodeSubnetIDs:    req.Dataplane.AWS.NodeSubnetIDs,
				AssumeRoleArn:    req.Dataplane.AWS.AssumeRoleArn,
				AssumeExternalID: "123",
				ProviderConfigReference: &xpv1.Reference{
					Name: req.Dataplane.AWS.ProviderConfigName,
				},
			}
		case req.Dataplane.GCP != nil:
			agent.Spec.GKE = &agentv2.GKEConfig{
				Network: req.Dataplane.GCP.Network,
				Project: req.Dataplane.GCP.Project,
				Subnets: req.Dataplane.GCP.Subnets,
			}
		case req.Dataplane.Azure != nil:
			agent.Spec.AKS = &agentv2.AKSConfig{
				Network:        req.Dataplane.Azure.Network,
				SubscriptionID: req.Dataplane.Azure.SubscriptionID,
				SubnetID:       req.Dataplane.Azure.SubnetID,
				PodSubnetID:    req.Dataplane.Azure.PodSubnetID,
				ResourceGroup:  req.Dataplane.Azure.ResourceGroup,
				ProviderConfigReference: &xpv1.Reference{
					Name: req.Dataplane.Azure.ProviderConfigName,
				},
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return &api.CreateK8sAgentResponse{}, nil
}

func (a *AgentControlImpl) DeleteK8sAgent(ctx context.Context, req *api.DeleteK8sAgentRequest) (*api.DeleteK8sAgentResponse, error) {
	agent := &agentv2.O11yK8sAgent{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: a.namespace,
			Name:      req.ProjectID,
		},
	}
	if err := a.client.Delete(ctx, agent); client.IgnoreNotFound(err) != nil {
		return nil, err
	}
	return &api.DeleteK8sAgentResponse{}, nil
}

func (a *AgentControlImpl) ListK8sAgents(ctx context.Context, req *api.ListK8sAgentsRequest) (*api.ListK8sAgentsResponse, error) {
	return &api.ListK8sAgentsResponse{}, nil
}

func (a *AgentControlImpl) GetK8sAgent(ctx context.Context, req *api.GetK8sAgentRequest) (*api.GetK8sAgentResponse, error) {
	agent := &agentv2.O11yK8sAgent{}
	if err := a.client.Get(ctx, client.ObjectKey{Namespace: a.namespace, Name: req.ProjectID}, agent); err != nil {
		if client.IgnoreNotFound(err) != nil {
			return nil, err
		}
		return nil, api.ErrNotFound
	}
	return &api.GetK8sAgentResponse{
		ProjectID:             agent.Spec.O11y.ProjectID,
		TenantID:              agent.Spec.O11y.TenantID,
		KubernetesClusterName: agent.Spec.KubernetesClusterName,
		Phase:                 agent.Status.Phase,
		Deleting:              !agent.DeletionTimestamp.IsZero(),
	}, nil
}

func (a *AgentControlImpl) CreateTiDBAgent(ctx context.Context, req *api.CreateTiDBAgentRequest) (*api.CreateTiDBAgentResponse, error) {
	agent := &agentv2.O11yTiDBAgent{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: a.namespace,
			Name:      req.ClusterID,
		},
	}
	_, err := controllerutil.CreateOrUpdate(ctx, a.client, agent, func() error {
		agent.Spec = agentv2.O11yTiDBAgentSpec{
			Pause:                 agent.Spec.Pause,
			KubernetesClusterName: req.Dataplane.ClusterName,
			O11y: agentv2.O11yConfig{
				BizType:       a.bizType,
				TenantID:      req.TenantID,
				ApplicationID: req.ClusterID,
				ProjectID:     req.ProjectID,
			},
			ClusterTopology: agentv2.ClusterTopology{
				Version: req.TiDB.Version,
				TiDB: agentv2.TiDB{
					Replicas: req.TiDB.Topology.TiDB.Replicas,
				},
				PD: agentv2.PD{
					Replicas: req.TiDB.Topology.PD.Replicas,
				},
				TiKV: agentv2.TiKV{
					Replicas: req.TiDB.Topology.TiKV.Replicas,
				},
				TiFlash: agentv2.TiFlash{
					Replicas: req.TiDB.Topology.TiFlash.Replicas,
				},
			},
		}
		switch {
		case req.Dataplane.AWS != nil:
			agent.Spec.EKS = &agentv2.EKSConfig{
				VpcID:           req.Dataplane.AWS.VpcID,
				AccountID:       req.Dataplane.AWS.AccountID,
				SecurityGroupID: req.Dataplane.AWS.SecurityGroupID,
				NodeSubnetIDs:   req.Dataplane.AWS.NodeSubnetIDs,
				AssumeRoleArn:   req.Dataplane.AWS.AssumeRoleArn,
				ProviderConfigReference: &xpv1.Reference{
					Name: req.Dataplane.AWS.ProviderConfigName,
				},
			}
		case req.Dataplane.GCP != nil:
			agent.Spec.GKE = &agentv2.GKEConfig{
				Network: req.Dataplane.GCP.Network,
				Project: req.Dataplane.GCP.Project,
				Subnets: req.Dataplane.GCP.Subnets,
			}
		case req.Dataplane.Azure != nil:
			agent.Spec.AKS = &agentv2.AKSConfig{
				Network:        req.Dataplane.Azure.Network,
				SubscriptionID: req.Dataplane.Azure.SubscriptionID,
				SubnetID:       req.Dataplane.Azure.SubnetID,
				PodSubnetID:    req.Dataplane.Azure.PodSubnetID,
				ResourceGroup:  req.Dataplane.Azure.ResourceGroup,
				ProviderConfigReference: &xpv1.Reference{
					Name: req.Dataplane.Azure.ProviderConfigName,
				},
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return &api.CreateTiDBAgentResponse{}, nil
}

func (a *AgentControlImpl) DeleteTiDBAgent(ctx context.Context, req *api.DeleteTiDBAgentRequest) (*api.DeleteTiDBAgentResponse, error) {
	agent := &agentv2.O11yTiDBAgent{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: a.namespace,
			Name:      req.ClusterID,
		},
	}
	if err := a.client.Delete(ctx, agent); client.IgnoreNotFound(err) != nil {
		return nil, err
	}
	return &api.DeleteTiDBAgentResponse{}, nil
}

func (a *AgentControlImpl) ListTiDBAgents(ctx context.Context, req *api.ListTiDBAgentsRequest) (*api.ListTiDBAgentsResponse, error) {
	return &api.ListTiDBAgentsResponse{}, nil
}

func (a *AgentControlImpl) GetTiDBAgent(ctx context.Context, req *api.GetTiDBAgentRequest) (*api.GetTiDBAgentResponse, error) {
	return &api.GetTiDBAgentResponse{}, nil
}

func (a *AgentControlImpl) PauseTiDBAgent(ctx context.Context, req *api.PauseTiDBAgentRequest) (*api.PauseTiDBAgentResponse, error) {
	err := a.updateTiDBAgent(ctx, req.ClusterID, func(agent *agentv2.O11yTiDBAgent) {
		agent.Spec.Pause = true
	})
	if err != nil {
		return nil, err
	}
	return &api.PauseTiDBAgentResponse{}, nil
}

func (a *AgentControlImpl) ResumeTiDBAgent(ctx context.Context, req *api.ResumeTiDBAgentRequest) (*api.ResumeTiDBAgentResponse, error) {
	err := a.updateTiDBAgent(ctx, req.ClusterID, func(agent *agentv2.O11yTiDBAgent) {
		agent.Spec.Pause = false
	})
	if err != nil {
		return nil, err
	}
	return &api.ResumeTiDBAgentResponse{}, nil
}

func (a *AgentControlImpl) ScaleTiDBAgent(ctx context.Context, req *api.ScaleTiDBAgentRequest) (*api.ScaleTiDBAgentResponse, error) {
	err := a.updateTiDBAgent(ctx, req.ClusterID, func(agent *agentv2.O11yTiDBAgent) {
		if agent.Annotations == nil {
			agent.Annotations = make(map[string]string)
		}
		agent.Annotations["o11y.cloud.pingcap.com/pending-scale"] = "true"
	})
	if err != nil {
		return nil, err
	}
	return &api.ScaleTiDBAgentResponse{}, nil
}

func (a *AgentControlImpl) updateTiDBAgent(ctx context.Context, id string, mut func(*agentv2.O11yTiDBAgent)) error {
	if id == "" {
		return nil
	}
	agent := &agentv2.O11yTiDBAgent{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: a.namespace,
			Name:      id,
		},
	}
	if err := a.client.Get(ctx, client.ObjectKeyFromObject(agent), agent); err != nil {
		if client.IgnoreNotFound(err) != nil {
			return err
		}
		return nil
	}
	mut(agent)
	return a.client.Update(ctx, agent)
}
