package regionserver

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

	"github.com/go-resty/resty/v2"
	clusterdedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"

	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (s *regionServerCliImpl) CreateCluster(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.CreateClusterRequest,
) (*regionaldedicatedv1.Cluster, bizerr.BizError) {
	body, err := marshalOptions.Marshal(req.Cluster)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["CreateCluster"],
		func(request *resty.Request) *resty.Request {
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}

	var out regionaldedicatedv1.Cluster
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) GetCluster(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.GetClusterRequest,
) (*clusterdedicatedv1.Cluster, bizerr.BizError) {
	resp, err := s.callRegion(
		ctx,
		region,
		http.MethodGet,
		operationMap["GetCluster"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			return request
		},
	)
	if err != nil {
		return nil, err
	}

	var out clusterdedicatedv1.Cluster
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) ListClusters(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.ListClustersRequest,
) (*clusterdedicatedv1.ListClustersResponse, bizerr.BizError) {
	resp, err := s.callRegion(
		ctx,
		region,
		http.MethodGet,
		operationMap["ListClusters"],
		func(request *resty.Request) *resty.Request {
			queryParams, err := protobufStructToMap(req)
			if err != nil {
				return nil
			}

			for key, value := range queryParams {
				switch val := value.(type) {
				case []any:
					request.SetQueryParamsFromValues(convertArrayFieldValueToValues(key, val))
				default:
					request.SetQueryParam(key, fmt.Sprintf("%v", value))
				}
			}

			return request
		},
	)
	if err != nil {
		return nil, err
	}

	var out clusterdedicatedv1.ListClustersResponse
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) DeleteCluster(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.DeleteClusterRequest,
) bizerr.BizError {
	cluster, bizErr := s.GetCluster(ctx, region, &regionaldedicatedv1.GetClusterRequest{ClusterId: req.GetClusterId()})
	if bizErr != nil {
		return bizErr
	}
	expectRequest := &metadata.ExpectRequest[string]{
		TenantId:   cluster.TenantId,
		ProjectId:  cluster.ProjectId,
		ClusterId:  cluster.Id,
		ResourceId: "", // leave it empty
	}
	if err := metadata.Validate(ctx, expectRequest); err != nil {
		return bizerr.WrapError(err, "validate failed")
	}

	_, err := s.callRegion(
		ctx,
		region,
		http.MethodDelete,
		operationMap["DeleteCluster"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			return request
		},
	)
	if err != nil {
		return err
	}

	return nil
}

func (s *regionServerCliImpl) PauseCluster(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.PauseClusterRequest,
) (*regionaldedicatedv1.PauseClusterResponse, bizerr.BizError) {
	resp, err := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["PauseCluster"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			return request
		},
	)
	if err != nil {
		return nil, err
	}

	var out regionaldedicatedv1.PauseClusterResponse
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) ResumeCluster(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.ResumeClusterRequest,
) (*regionaldedicatedv1.ResumeClusterResponse, bizerr.BizError) {
	resp, err := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["ResumeCluster"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			return request
		},
	)
	if err != nil {
		return nil, err
	}

	var out regionaldedicatedv1.ResumeClusterResponse
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) SetVersionAndConfigurations(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.SetVersionAndConfigurationsRequest,
) (*regionaldedicatedv1.SetVersionAndConfigurationsResponse, bizerr.BizError) {
	body, err := marshalOptions.Marshal(req)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["SetVersionAndConfigurations"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}

	var out regionaldedicatedv1.SetVersionAndConfigurationsResponse
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) GetClusterProxyConfidentialInfo(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.GetClusterProxyConfidentialInfoRequest,
) (*regionaldedicatedv1.GetClusterProxyConfidentialInfoResponse, bizerr.BizError) {
	resp, err := s.callRegion(
		ctx,
		region,
		http.MethodGet,
		operationMap["GetClusterProxyConfidentialInfo"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			return request
		},
	)
	if err != nil {
		return nil, err
	}

	var out regionaldedicatedv1.GetClusterProxyConfidentialInfoResponse
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) GetComponentNodeInfo(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.GetComponentNodeInfosRequest,
) (*regionaldedicatedv1.GetComponentNodeInfosResponse, bizerr.BizError) {
	resp, err := s.callRegion(
		ctx,
		region,
		http.MethodGet,
		operationMap["GetComponentNodeInfos"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			return request
		},
	)
	if err != nil {
		return nil, err
	}

	var out regionaldedicatedv1.GetComponentNodeInfosResponse
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) ScaleComponent(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.ScaleClusterRequest,
) (*regionaldedicatedv1.ScaleClusterResponse, bizerr.BizError) {
	body, err := marshalOptions.Marshal(req)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["ScaleCluster"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}

	var out regionaldedicatedv1.ScaleClusterResponse
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) GetCaCertificate(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.GetCaCertificateRequest,
) (*regionaldedicatedv1.CaCertificate, bizerr.BizError) {
	resp, err := s.callRegion(
		ctx,
		region,
		http.MethodGet,
		operationMap["GetCaCertificate"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetQueryParam("Usage", req.Usage.String())
			return request
		},
	)
	if err != nil {
		return nil, err
	}

	var out regionaldedicatedv1.CaCertificate
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) SetClientCaCertificate(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.SetClientCaCertificateRequest,
) (*regionaldedicatedv1.CaCertificate, bizerr.BizError) {
	resp, err := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["SetClientCaCertificate"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetBody(req.Ca)
			return request
		},
	)
	if err != nil {
		return nil, err
	}

	var out regionaldedicatedv1.CaCertificate
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) GetClusterRole(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.GetClusterRoleRequest,
) (*regionaldedicatedv1.ClusterRole, bizerr.BizError) {
	resp, err := s.callRegion(
		ctx,
		region,
		http.MethodGet,
		operationMap["GetClusterRole"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			return request
		},
	)
	if err != nil {
		return nil, err
	}

	var out regionaldedicatedv1.ClusterRole
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}
	return &out, nil
}

func (s *regionServerCliImpl) ValidateSecurityConfiguration(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.ValidateSecurityConfigurationRequest,
) (*regionaldedicatedv1.ValidateSecurityConfigurationResponse, bizerr.BizError) {
	body, err := marshalOptions.Marshal(req)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["ValidateSecurityConfiguration"],
		func(request *resty.Request) *resty.Request {
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}

	var out regionaldedicatedv1.ValidateSecurityConfigurationResponse
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) CheckClusterPublicLBInboundRulesStatus(
	ctx context.Context,
	region *entity.Region,
	req *clusterdedicatedv1.CheckClusterPublicLBInboundRulesStatusRequest,
) (*clusterdedicatedv1.CheckClusterPublicLBInboundRulesStatusResponse, bizerr.BizError) {
	body, err := marshalOptions.Marshal(req)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap[clusterdedicatedv1.ClusterService_CheckClusterPublicLBInboundRulesStatus_FullMethodName],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}

	var out clusterdedicatedv1.CheckClusterPublicLBInboundRulesStatusResponse
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}
	return &out, nil
}
