package server

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/pingcap/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"

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

type AgentSpecRequest struct {
	AgentType   string `json:"agentType"`
	AgentName   string `json:"agentName"`
	DesiredSpec string `json:"desiredSpec"`
	MinSpec     string `json:"minSpec"`
	MaxSpec     string `json:"maxSpec"`
}

type AgentActionRequest struct {
	AgentType string `json:"agentType"`
	AgentName string `json:"agentName"`
}

type AgentRestartRequest struct {
	AgentType string `json:"agentType"`
	AgentName string `json:"agentName"`
	Component string `json:"component"` // "vmagent" or "vector"
}

type PodNodeDrainRequest struct {
	AgentType string `json:"agentType"`
	AgentName string `json:"agentName"`
	Namespace string `json:"namespace"`
	PodName   string `json:"podName"`
}

type AgentStorageRequest struct {
	AgentType string `json:"agentType"`
	AgentName string `json:"agentName"`
	Component string `json:"component"` // "vmagent"
	Size      string `json:"size"`      // "50Gi"
}

type AgentStorageResponse struct {
	AgentType string `json:"agentType"`
	AgentName string `json:"agentName"`
	Component string `json:"component"`
	Size      string `json:"size"`
}

type AgentSpecResponse struct {
	AgentType   string `json:"agentType"`
	AgentName   string `json:"agentName"`
	DesiredSpec string `json:"desiredSpec"`
	MinSpec     string `json:"minSpec"`
	MaxSpec     string `json:"maxSpec"`
}

type AgentPauseResponse struct {
	AgentType string `json:"agentType"`
	AgentName string `json:"agentName"`
	IsPaused  bool   `json:"isPaused"`
}

type AgentRestartResponse struct {
	AgentType string `json:"agentType"`
	AgentName string `json:"agentName"`
	Component string `json:"component"`
}

type PodNodeDrainResponse struct {
	AgentType string `json:"agentType"`
	AgentName string `json:"agentName"`
	PodName   string `json:"podName"`
	Namespace string `json:"namespace"`
}

type AgentInfoResponse struct {
	Name                  string `json:"name"`
	AgentType             string `json:"agentType"`
	KubernetesClusterName string `json:"kubernetesClusterName"`
	TenantID              string `json:"tenantId"`
	ApplicationID         string `json:"applicationId"`
	ProjectID             string `json:"projectId,omitempty"`
	SharedPoolID          string `json:"sharedPoolId,omitempty"`
	O11yID                string `json:"o11yId"`
	Phase                 string `json:"phase"`
}

var errAgentTypeAndNameRequired = fmt.Errorf("agentType and agentName are required")

func (s *Server) getAgentParamsFromQuery(r *http.Request) (string, string, error) {
	agentType := r.URL.Query().Get("agentType")
	agentName := r.URL.Query().Get("agentName")
	if agentType == "" || agentName == "" {
		return "", "", errAgentTypeAndNameRequired
	}
	return agentType, agentName, nil
}

func (s *Server) routeGetAgentSpec(w http.ResponseWriter, r *http.Request) {
	agentType, agentName, err := s.getAgentParamsFromQuery(r)
	if err != nil {
		s.respondWithError(w, http.StatusBadRequest, err)
		return
	}

	obj, err := s.getAgentObject(r.Context(), agentType, agentName)
	if err != nil {
		s.respondWithError(w, http.StatusBadRequest, err)
		return
	}

	annotations := obj.GetAnnotations()
	if annotations == nil {
		annotations = make(map[string]string)
	}

	response := AgentSpecResponse{
		AgentType:   agentType,
		AgentName:   agentName,
		DesiredSpec: annotations[consts.AnnotationKeyO11yDesiredSpec],
		MinSpec:     annotations[consts.AnnotationKeyO11yMinSpec],
		MaxSpec:     annotations[consts.AnnotationKeyO11yMaxSpec],
	}
	s.respondWithData(w, http.StatusOK, "success", response)
}

func (s *Server) routePostAgentSpec(w http.ResponseWriter, r *http.Request) {
	var req AgentSpecRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		s.respondWithError(w, http.StatusBadRequest, err)
		return
	}
	if req.AgentType == "" || req.AgentName == "" {
		s.respondWithError(w, http.StatusBadRequest, errAgentTypeAndNameRequired)
		return
	}
	if req.DesiredSpec == "" && req.MinSpec == "" && req.MaxSpec == "" {
		s.respond(w, http.StatusBadRequest, "desiredSpec, minSpec or maxSpec is required")
		return
	}

	agentType := req.AgentType
	agentName := req.AgentName
	obj, err := s.getAgentObject(r.Context(), agentType, agentName)
	if err != nil {
		s.respond(w, http.StatusBadRequest, err.Error())
		return
	}

	annotations := obj.GetAnnotations()
	if annotations == nil {
		annotations = make(map[string]string)
	}

	if req.DesiredSpec != "" {
		if agentType == consts.AgentO11yK8s || agentType == consts.AgentO11yTidb {
			currentSpec := annotations[consts.AnnotationKeyO11yDesiredSpec]
			if currentSpec == "" {
				currentSpec = "default"
			}
			annotations[consts.AnnotationKeyO11yPreviousSpec] = currentSpec
			annotations[consts.AnnotationKeyO11ySpecScaleStatus] = consts.SpecScaleStatusPendingScale
		}
		annotations[consts.AnnotationKeyO11yDesiredSpec] = req.DesiredSpec
	}

	if req.MinSpec != "" {
		annotations[consts.AnnotationKeyO11yMinSpec] = req.MinSpec
	}
	if req.MaxSpec != "" {
		annotations[consts.AnnotationKeyO11yMaxSpec] = req.MaxSpec
	}

	obj.SetAnnotations(annotations)
	if err := s.client.Update(r.Context(), obj); err != nil {
		err = errors.Wrapf(err, "update %s/%s spec failed", agentType, agentName)
		s.respondWithError(w, http.StatusInternalServerError, err)
		return
	}

	response := AgentSpecResponse{
		AgentType:   agentType,
		AgentName:   agentName,
		DesiredSpec: annotations[consts.AnnotationKeyO11yDesiredSpec],
		MinSpec:     annotations[consts.AnnotationKeyO11yMinSpec],
		MaxSpec:     annotations[consts.AnnotationKeyO11yMaxSpec],
	}
	s.respondWithData(w, http.StatusOK, "success", response)
}

func (s *Server) routePostPauseAgent(w http.ResponseWriter, r *http.Request) {
	s.setPauseState(w, r, true)
}

func (s *Server) routePostResumeAgent(w http.ResponseWriter, r *http.Request) {
	s.setPauseState(w, r, false)
}

func (s *Server) setPauseState(w http.ResponseWriter, r *http.Request, paused bool) {
	var req AgentActionRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		s.respondWithError(w, http.StatusBadRequest, err)
		return
	}
	if req.AgentType == "" || req.AgentName == "" {
		s.respondWithError(w, http.StatusBadRequest, errAgentTypeAndNameRequired)
		return
	}

	agentType := req.AgentType
	agentName := req.AgentName
	obj, err := s.getAgentObject(r.Context(), agentType, agentName)
	if err != nil {
		s.respondWithError(w, http.StatusBadRequest, err)
		return
	}

	annotations := obj.GetAnnotations()
	if annotations == nil {
		annotations = make(map[string]string)
	}

	if paused {
		annotations[consts.AnnotationKeyPauseReconcile] = "true"
	} else {
		delete(annotations, consts.AnnotationKeyPauseReconcile)
	}

	obj.SetAnnotations(annotations)
	if err := s.client.Update(r.Context(), obj); err != nil {
		err = errors.Wrapf(err, "paused=%v %s/%s failed", paused, agentType, agentName)
		s.respondWithError(w, http.StatusInternalServerError, err)
		return
	}

	s.respondWithData(w, http.StatusOK, "success", AgentPauseResponse{
		AgentType: agentType,
		AgentName: agentName,
		IsPaused:  paused,
	})
}

func (s *Server) routePostRestartAgent(w http.ResponseWriter, r *http.Request) {
	var req AgentRestartRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		s.respondWithError(w, http.StatusBadRequest, err)
		return
	}
	if req.AgentType == "" || req.AgentName == "" {
		s.respondWithError(w, http.StatusBadRequest, errAgentTypeAndNameRequired)
		return
	}

	var annotationKey string
	switch req.Component {
	case consts.ComponentVMAgent:
		annotationKey = consts.AnnotationKeyO11yVMAgentRestart
	case consts.ComponentVector:
		annotationKey = consts.AnnotationKeyO11yVectorRestart
	default:
		s.respondWithError(w, http.StatusBadRequest, fmt.Errorf("invalid component: %s (must be 'vmagent' or 'vector')", req.Component))
		return
	}

	agentType := req.AgentType
	agentName := req.AgentName
	obj, err := s.getAgentObject(r.Context(), agentType, agentName)
	if err != nil {
		s.respondWithError(w, http.StatusBadRequest, err)
		return
	}

	annotations := obj.GetAnnotations()
	if annotations == nil {
		annotations = make(map[string]string)
	}
	annotations[annotationKey] = "true"

	obj.SetAnnotations(annotations)
	if err := s.client.Update(r.Context(), obj); err != nil {
		err = errors.Wrapf(err, "restart %s component of %s/%s failed", req.Component, agentType, agentName)
		s.respondWithError(w, http.StatusInternalServerError, err)
		return
	}

	response := AgentRestartResponse{
		AgentType: agentType,
		AgentName: agentName,
		Component: req.Component,
	}
	s.respondWithData(w, http.StatusOK, "success", response)
}

func (s *Server) getAgentObject(ctx context.Context, agentType, agentName string) (client.Object, error) {
	switch agentType {
	case consts.AgentKubernetes:
		var agents agentv2.KubernetesAgentList
		if err := s.client.List(ctx, &agents, s.listOptions()...); err != nil {
			return nil, err
		}
		for _, agent := range agents.Items {
			if agent.Name == agentName {
				return &agent, nil
			}
		}
	case consts.AgentO11yK8s:
		var agents agentv2.O11yK8sAgentList
		if err := s.client.List(ctx, &agents, s.listOptions()...); err != nil {
			return nil, err
		}
		for _, agent := range agents.Items {
			if agent.Name == agentName {
				return &agent, nil
			}
		}
	case consts.AgentO11yTidb:
		var agents agentv2.O11yTiDBAgentList
		if err := s.client.List(ctx, &agents, s.listOptions()...); err != nil {
			return nil, err
		}
		for _, agent := range agents.Items {
			if agent.Name == agentName {
				return &agent, nil
			}
		}
	case consts.AgentSharedPool:
		var agents agentv2.SharedPoolAgentList
		if err := s.client.List(ctx, &agents, s.listOptions()...); err != nil {
			return nil, err
		}
		for _, agent := range agents.Items {
			if agent.Name == agentName {
				return &agent, nil
			}
		}
	case consts.AgentTidbGroup:
		var agents agentv2.TiDBGroupAgentList
		if err := s.client.List(ctx, &agents, s.listOptions()...); err != nil {
			return nil, err
		}
		for _, agent := range agents.Items {
			if agent.Name == agentName {
				return &agent, nil
			}
		}
	}
	return nil, fmt.Errorf("%s/%s not found", agentType, agentName)
}

type agentFilter struct {
	AgentName             string
	TenantID              string
	KubernetesClusterName string
	O11yID                string
}

func (s *Server) routeListAgents(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query()
	agentType := query.Get("agentType")

	filter := agentFilter{
		AgentName:             query.Get("agentName"),
		TenantID:              query.Get("tenantId"),
		KubernetesClusterName: query.Get("kubernetesClusterName"),
		O11yID:                query.Get("o11yId"),
	}

	agentTypes := []string{
		consts.AgentKubernetes,
		consts.AgentO11yK8s,
		consts.AgentO11yTidb,
		consts.AgentSharedPool,
		consts.AgentTidbGroup,
	}
	if agentType != "" {
		agentTypes = []string{agentType}
	}

	ret := make([]AgentInfoResponse, 0)
	for _, at := range agentTypes {
		agents, err := s.listAgents(r.Context(), at, filter)
		if err != nil {
			s.respondWithError(w, http.StatusBadRequest, err)
			return
		}
		ret = append(ret, agents...)
	}
	s.respondWithData(w, http.StatusOK, "success", ret)
}

func matchKubernetesAgent(agent agentv2.KubernetesAgent, filter agentFilter) *AgentInfoResponse {
	spec := agent.Spec
	if filter.AgentName != "" && agent.Name != filter.AgentName {
		return nil
	}
	if filter.KubernetesClusterName != "" && spec.KubernetesClusterName != filter.KubernetesClusterName {
		return nil
	}
	if filter.TenantID != "" && spec.O11y.TenantID != filter.TenantID {
		return nil
	}
	if filter.O11yID != "" && agent.Annotations[consts.AnnotationKeyO11yID] != filter.O11yID {
		return nil
	}

	return &AgentInfoResponse{
		Name:                  agent.Name,
		AgentType:             consts.AgentKubernetes,
		KubernetesClusterName: spec.KubernetesClusterName,
		TenantID:              spec.O11y.TenantID,
		ApplicationID:         spec.O11y.ApplicationID,
		O11yID:                agent.Annotations[consts.AnnotationKeyO11yID],
		Phase:                 agent.Status.Phase,
	}
}

func matchO11yK8sAgent(agent agentv2.O11yK8sAgent, filter agentFilter) *AgentInfoResponse {
	spec := agent.Spec
	if filter.AgentName != "" && agent.Name != filter.AgentName {
		return nil
	}
	if filter.KubernetesClusterName != "" && spec.KubernetesClusterName != filter.KubernetesClusterName {
		return nil
	}
	if filter.TenantID != "" && spec.O11y.TenantID != filter.TenantID {
		return nil
	}
	if filter.O11yID != "" && agent.Annotations[consts.AnnotationKeyO11yID] != filter.O11yID {
		return nil
	}

	return &AgentInfoResponse{
		Name:                  agent.Name,
		AgentType:             consts.AgentO11yK8s,
		KubernetesClusterName: spec.KubernetesClusterName,
		TenantID:              spec.O11y.TenantID,
		ApplicationID:         spec.O11y.ApplicationID,
		ProjectID:             spec.O11y.ProjectID,
		O11yID:                agent.Annotations[consts.AnnotationKeyO11yID],
		Phase:                 agent.Status.Phase,
	}
}

func matchO11yTiDBAgent(agent agentv2.O11yTiDBAgent, filter agentFilter) *AgentInfoResponse {
	spec := agent.Spec
	if filter.AgentName != "" && agent.Name != filter.AgentName {
		return nil
	}
	if filter.KubernetesClusterName != "" && spec.KubernetesClusterName != filter.KubernetesClusterName {
		return nil
	}
	if filter.TenantID != "" && spec.O11y.TenantID != filter.TenantID {
		return nil
	}
	if filter.O11yID != "" && agent.Annotations[consts.AnnotationKeyO11yID] != filter.O11yID {
		return nil
	}

	return &AgentInfoResponse{
		Name:                  agent.Name,
		AgentType:             consts.AgentO11yTidb,
		KubernetesClusterName: spec.KubernetesClusterName,
		TenantID:              spec.O11y.TenantID,
		ApplicationID:         spec.O11y.ApplicationID,
		ProjectID:             spec.O11y.ProjectID,
		O11yID:                agent.Annotations[consts.AnnotationKeyO11yID],
		Phase:                 agent.Status.Phase,
	}
}

func matchSharedPoolAgent(agent agentv2.SharedPoolAgent, filter agentFilter) *AgentInfoResponse {
	spec := agent.Spec
	if filter.AgentName != "" && agent.Name != filter.AgentName {
		return nil
	}
	if filter.KubernetesClusterName != "" && spec.KubernetesClusterName != filter.KubernetesClusterName {
		return nil
	}
	if filter.TenantID != "" && spec.O11y.TenantID != filter.TenantID {
		return nil
	}
	if filter.O11yID != "" && agent.Annotations[consts.AnnotationKeyO11yID] != filter.O11yID {
		return nil
	}

	return &AgentInfoResponse{
		Name:                  agent.Name,
		AgentType:             consts.AgentSharedPool,
		KubernetesClusterName: spec.KubernetesClusterName,
		TenantID:              spec.O11y.TenantID,
		ApplicationID:         spec.O11y.ApplicationID,
		O11yID:                agent.Annotations[consts.AnnotationKeyO11yID],
		SharedPoolID:          spec.SharedPoolID,
		Phase:                 agent.Status.Phase,
	}
}

func matchTidbGroupAgent(agent agentv2.TiDBGroupAgent, filter agentFilter) *AgentInfoResponse {
	spec := agent.Spec
	if filter.AgentName != "" && agent.Name != filter.AgentName {
		return nil
	}
	if filter.KubernetesClusterName != "" && spec.KubernetesClusterName != filter.KubernetesClusterName {
		return nil
	}
	if filter.TenantID != "" && spec.O11y.TenantID != filter.TenantID {
		return nil
	}
	if filter.O11yID != "" && agent.Annotations[consts.AnnotationKeyO11yID] != filter.O11yID {
		return nil
	}

	return &AgentInfoResponse{
		Name:                  agent.Name,
		AgentType:             consts.AgentTidbGroup,
		KubernetesClusterName: spec.KubernetesClusterName,
		TenantID:              spec.O11y.TenantID,
		ApplicationID:         spec.O11y.ApplicationID,
		SharedPoolID:          spec.SharedPoolID,
		O11yID:                agent.Annotations[consts.AnnotationKeyO11yID],
		Phase:                 agent.Status.Phase,
	}
}

func (s *Server) listAgents(ctx context.Context, agentType string, filter agentFilter) ([]AgentInfoResponse, error) {
	agents := make([]AgentInfoResponse, 0)
	switch agentType {
	case consts.AgentKubernetes:
		var list agentv2.KubernetesAgentList
		if err := s.client.List(ctx, &list, s.listOptions()...); err != nil {
			return nil, err
		}
		for _, item := range list.Items {
			if info := matchKubernetesAgent(item, filter); info != nil {
				agents = append(agents, *info)
			}
		}
	case consts.AgentO11yK8s:
		var list agentv2.O11yK8sAgentList
		if err := s.client.List(ctx, &list, s.listOptions()...); err != nil {
			return nil, err
		}
		for _, item := range list.Items {
			if info := matchO11yK8sAgent(item, filter); info != nil {
				agents = append(agents, *info)
			}
		}
	case consts.AgentO11yTidb:
		var list agentv2.O11yTiDBAgentList
		if err := s.client.List(ctx, &list, s.listOptions()...); err != nil {
			return nil, err
		}
		for _, item := range list.Items {
			if info := matchO11yTiDBAgent(item, filter); info != nil {
				agents = append(agents, *info)
			}
		}
	case consts.AgentSharedPool:
		var list agentv2.SharedPoolAgentList
		if err := s.client.List(ctx, &list, s.listOptions()...); err != nil {
			return nil, err
		}
		for _, item := range list.Items {
			if info := matchSharedPoolAgent(item, filter); info != nil {
				agents = append(agents, *info)
			}
		}
	case consts.AgentTidbGroup:
		var list agentv2.TiDBGroupAgentList
		if err := s.client.List(ctx, &list, s.listOptions()...); err != nil {
			return nil, err
		}
		for _, item := range list.Items {
			if info := matchTidbGroupAgent(item, filter); info != nil {
				agents = append(agents, *info)
			}
		}
	default:
		return nil, fmt.Errorf("invalid agentType: %s", agentType)
	}

	return agents, nil
}

func (s *Server) routePostDrainNode(w http.ResponseWriter, r *http.Request) {
	var req PodNodeDrainRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		s.respondWithError(w, http.StatusBadRequest, err)
		return
	}

	if req.AgentType == "" || req.AgentName == "" {
		s.respondWithError(w, http.StatusBadRequest, errAgentTypeAndNameRequired)
		return
	}
	if req.Namespace == "" || req.PodName == "" {
		s.respond(w, http.StatusBadRequest, "namespace and podName are required")
		return
	}

	agentType := req.AgentType
	agentName := req.AgentName
	obj, err := s.getAgentObject(r.Context(), agentType, agentName)
	if err != nil {
		s.respondWithError(w, http.StatusBadRequest, err)
		return
	}

	annotations := obj.GetAnnotations()
	if annotations == nil {
		annotations = make(map[string]string)
	}
	annotations[consts.AnnotationKeyO11yDrainNode] = fmt.Sprintf("%s/%s", req.Namespace, req.PodName)
	obj.SetAnnotations(annotations)

	if err := s.client.Update(r.Context(), obj); err != nil {
		err = errors.Wrapf(err, "failed to set drain annotation with pod %s/%s", req.Namespace, req.PodName)
		s.respondWithError(w, http.StatusInternalServerError, err)
		return
	}

	response := PodNodeDrainResponse{
		AgentType: agentType,
		AgentName: agentName,
		PodName:   req.PodName,
		Namespace: req.Namespace,
	}
	s.respondWithData(w, http.StatusOK, "drain node task initiated", response)
}

func (s *Server) routePostAgentStorage(w http.ResponseWriter, r *http.Request) {
	var req AgentStorageRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		s.respondWithError(w, http.StatusBadRequest, err)
		return
	}

	if req.AgentType == "" || req.AgentName == "" {
		s.respondWithError(w, http.StatusBadRequest, errAgentTypeAndNameRequired)
		return
	}
	if req.Component == "" || req.Size == "" {
		s.respond(w, http.StatusBadRequest, "component and size are required")
		return
	}

	agentType := req.AgentType
	agentName := req.AgentName
	obj, err := s.getAgentObject(r.Context(), agentType, agentName)
	if err != nil {
		s.respondWithError(w, http.StatusBadRequest, err)
		return
	}

	annotations := obj.GetAnnotations()
	if annotations == nil {
		annotations = make(map[string]string)
	}

	var annotationKey string
	switch req.Component {
	case consts.ComponentVMAgent:
		annotationKey = consts.AnnotationKeyO11yVMAgentStorageSize
	default:
		s.respondWithError(w, http.StatusBadRequest, fmt.Errorf("unsupported component: %s", req.Component))
		return
	}

	annotations[annotationKey] = req.Size
	obj.SetAnnotations(annotations)
	if err := s.client.Update(r.Context(), obj); err != nil {
		err = errors.Wrapf(err, "update %s/%s storage failed", agentType, agentName)
		s.respondWithError(w, http.StatusInternalServerError, err)
		return
	}

	response := AgentStorageResponse{
		AgentType: agentType,
		AgentName: agentName,
		Component: req.Component,
		Size:      req.Size,
	}
	s.respondWithData(w, http.StatusOK, "success", response)
}
