package httpclient

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"

	pb "github.com/tidbcloud/config-center/proto/config_center"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/types/known/emptypb"
)

type ConfigCenterHTTPClient struct {
	HTTPClient *http.Client
	BaseURL    string
}

type ConfigCenterRegionRequest struct {
	URL     string
	Method  string
	Headers map[string]string
}

func (c *ConfigCenterHTTPClient) GetClusterMeta(ctx context.Context, req *pb.GetClusterMetaRequest) (*pb.ClusterMeta, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/clusters/%s/meta?mainComponent=%s", c.BaseURL, req.GetClusterType(), req.GetClusterId(), req.GetMainComponent())
	resp, err := c.HTTPClient.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get cluster meta: %s, response: %s", resp.Status, string(respBody))
	}

	var meta pb.ClusterMeta
	if err := protojson.Unmarshal(respBody, &meta); err != nil {
		return nil, err
	}
	return &meta, nil
}

func (c *ConfigCenterHTTPClient) DeleteClusterMeta(ctx context.Context, req *pb.DeleteClusterMetaRequest) (*emptypb.Empty, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/clusters/%s/meta", c.BaseURL, req.GetClusterType(), req.GetClusterId())
	reqHTTP, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, nil)
	if err != nil {
		return nil, err
	}

	resp, err := c.HTTPClient.Do(reqHTTP)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
		return nil, fmt.Errorf("failed to delete cluster meta: %s, response: %s", resp.Status, string(respBody))
	}
	return &emptypb.Empty{}, nil
}

func (c *ConfigCenterHTTPClient) BatchDeleteClusterMeta(ctx context.Context, req *pb.BatchDeleteClusterMetaRequest) (*emptypb.Empty, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/clusters/meta:batchDelete", c.BaseURL, req.GetClusterType())
	body, err := protojson.Marshal(req)
	if err != nil {
		return nil, err
	}

	reqHTTP, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	reqHTTP.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTPClient.Do(reqHTTP)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
		return nil, fmt.Errorf("failed to batch delete cluster meta: %s, response: %s", resp.Status, string(respBody))
	}
	return &emptypb.Empty{}, nil
}

func (c *ConfigCenterHTTPClient) UpdateClusterMeta(ctx context.Context, req *pb.UpdateClusterMetaRequest) (*pb.UpdateClusterMetaResponse, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/clusters/%s/meta", c.BaseURL, req.GetClusterType(), req.GetClusterId())
	body, err := protojson.Marshal(req)
	if err != nil {
		return nil, err
	}

	reqHTTP, err := http.NewRequestWithContext(ctx, http.MethodPatch, url, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	reqHTTP.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTPClient.Do(reqHTTP)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to update cluster meta: %s, response: %s", resp.Status, string(respBody))
	}

	var response pb.UpdateClusterMetaResponse
	if err := protojson.Unmarshal(respBody, &response); err != nil {
		return nil, err
	}
	return &response, nil
}

func (c *ConfigCenterHTTPClient) AutoUpdateClusterMeta(ctx context.Context, req *pb.AutoUpdateClusterMetaRequest) (*pb.AutoUpdateClusterMetaResponse, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/clusters/%s/meta:autoUpdate", c.BaseURL, req.GetClusterType(), req.GetClusterId())
	body, err := protojson.Marshal(req)
	if err != nil {
		return nil, err
	}

	reqHTTP, err := http.NewRequestWithContext(ctx, http.MethodPatch, url, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	reqHTTP.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTPClient.Do(reqHTTP)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to auto-update cluster meta: %s, response: %s", resp.Status, string(respBody))
	}

	var response pb.AutoUpdateClusterMetaResponse
	if err := protojson.Unmarshal(respBody, &response); err != nil {
		return nil, err
	}
	return &response, nil
}

func (c *ConfigCenterHTTPClient) SearchClusterComponents(ctx context.Context, req *pb.SearchClusterComponentsRequest) (*pb.SearchClusterComponentsResponse, error) {
	values := url.Values{}
	if req.GetComponentName() != "" {
		values.Set("componentName", req.GetComponentName())
	}
	for _, clusterID := range req.GetClusterIds() {
		values.Add("clusterIds", clusterID)
	}
	if req.GetComponentVersion() != "" {
		values.Set("componentVersion", req.GetComponentVersion())
	}
	if req.GetImageTag() != "" {
		values.Set("imageTag", req.GetImageTag())
	}
	if req.GetPageSize() != 0 {
		values.Set("pageSize", strconv.FormatInt(int64(req.GetPageSize()), 10))
	}
	if req.GetPage() != 0 {
		values.Set("page", strconv.FormatInt(int64(req.GetPage()), 10))
	}
	if req.GetShowDeleted() {
		values.Set("showDeleted", strconv.FormatBool(req.GetShowDeleted()))
	}

	url := fmt.Sprintf("%s/config-center/cluster-types/%s/cluster-components:search", c.BaseURL, req.GetClusterType())
	if encoded := values.Encode(); encoded != "" {
		url += "?" + encoded
	}

	resp, err := c.HTTPClient.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to search cluster components: %s, response: %s", resp.Status, string(respBody))
	}

	var response pb.SearchClusterComponentsResponse
	if err := protojson.Unmarshal(respBody, &response); err != nil {
		return nil, err
	}
	return &response, nil
}

func (c *ConfigCenterHTTPClient) GetDefaultVersion(ctx context.Context, req *pb.GetDefaultVersionRequest) (*pb.DefaultVersion, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/default-version", c.BaseURL, req.GetClusterType())
	resp, err := c.HTTPClient.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get default version: %s, response: %s", resp.Status, string(respBody))
	}

	var version pb.DefaultVersion
	if err := protojson.Unmarshal(respBody, &version); err != nil {
		return nil, err
	}
	return &version, nil
}

func (c *ConfigCenterHTTPClient) UpdateDefaultVersion(ctx context.Context, req *pb.UpdateDefaultVersionRequest) (*pb.DefaultVersion, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/default-version", c.BaseURL, req.GetClusterType())
	body, err := protojson.Marshal(req)
	if err != nil {
		return nil, err
	}

	reqHTTP, err := http.NewRequestWithContext(ctx, http.MethodPut, url, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	reqHTTP.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTPClient.Do(reqHTTP)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to update default version: %s, response: %s", resp.Status, string(respBody))
	}

	var version pb.DefaultVersion
	if err := protojson.Unmarshal(respBody, &version); err != nil {
		return nil, err
	}
	return &version, nil
}

func (c *ConfigCenterHTTPClient) DeleteDefaultVersion(ctx context.Context, req *pb.DeleteDefaultVersionRequest) (*emptypb.Empty, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/default-version", c.BaseURL, req.GetClusterType())
	reqHTTP, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, nil)
	if err != nil {
		return nil, err
	}

	resp, err := c.HTTPClient.Do(reqHTTP)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
		return nil, fmt.Errorf("failed to delete default version: %s, response: %s", resp.Status, string(respBody))
	}
	return nil, nil
}

func (c *ConfigCenterHTTPClient) ListDefaultVersionRules(ctx context.Context, req *pb.ListDefaultVersionRulesRequest) (*pb.ListDefaultVersionRulesResponse, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/default-version-rules", c.BaseURL, req.GetClusterType())
	resp, err := c.HTTPClient.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to list default version rules: %s, response: %s", resp.Status, string(respBody))
	}

	var rules pb.ListDefaultVersionRulesResponse
	if err := protojson.Unmarshal(respBody, &rules); err != nil {
		return nil, err
	}
	return &rules, nil
}

func (c *ConfigCenterHTTPClient) CreateConfigurationTemplate(ctx context.Context, req *pb.CreateConfigurationTemplateRequest) (*pb.ConfigurationTemplate, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/templates", c.BaseURL, req.GetClusterType())
	body, err := protojson.Marshal(req)
	if err != nil {
		return nil, err
	}

	reqHTTP, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	reqHTTP.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTPClient.Do(reqHTTP)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to create configuration template: %s, response: %s", resp.Status, string(respBody))
	}

	var template pb.ConfigurationTemplate
	if err := protojson.Unmarshal(respBody, &template); err != nil {
		return nil, err
	}
	return &template, nil
}

func (c *ConfigCenterHTTPClient) GetConfigurationTemplate(ctx context.Context, req *pb.GetConfigurationTemplateRequest) (*pb.ConfigurationTemplate, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/templates/%d", c.BaseURL, req.GetClusterType(), req.GetTemplateId())
	resp, err := c.HTTPClient.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get configuration template: %s, response: %s", resp.Status, string(respBody))
	}

	var template pb.ConfigurationTemplate
	if err := protojson.Unmarshal(respBody, &template); err != nil {
		return nil, err
	}
	return &template, nil
}

func (c *ConfigCenterHTTPClient) UpdateDefaultConfigurationTemplate(ctx context.Context, req *pb.UpdateDefaultConfigurationTemplateRequest) (*pb.ConfigurationTemplate, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/templates/%d/default", c.BaseURL, req.GetClusterType(), req.GetTemplateId())
	body, err := protojson.Marshal(req)
	if err != nil {
		return nil, err
	}

	reqHTTP, err := http.NewRequestWithContext(ctx, http.MethodPut, url, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	reqHTTP.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTPClient.Do(reqHTTP)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to update default configuration template: %s, response: %s", resp.Status, string(respBody))
	}

	var template pb.ConfigurationTemplate
	if err := protojson.Unmarshal(respBody, &template); err != nil {
		return nil, err
	}
	return &template, nil
}

func (c *ConfigCenterHTTPClient) DeleteDefaultConfigurationTemplate(ctx context.Context, req *pb.DeleteDefaultConfigurationTemplateRequest) (*emptypb.Empty, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/templates/%d/default:delete", c.BaseURL, req.GetClusterType(), req.GetTemplateId())
	body, err := protojson.Marshal(req)
	if err != nil {
		return nil, err
	}

	reqHTTP, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	reqHTTP.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTPClient.Do(reqHTTP)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to delete default configuration template: %s, response: %s", resp.Status, string(respBody))
	}

	return &emptypb.Empty{}, nil
}

func (c *ConfigCenterHTTPClient) GetTemplateUsage(ctx context.Context, req *pb.GetTemplateUsageRequest) (*pb.TemplateUsage, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/templates/%d/usage", c.BaseURL, req.GetClusterType(), req.GetTemplateId())
	resp, err := c.HTTPClient.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get template usage: %s, response: %s", resp.Status, string(respBody))
	}

	var usage pb.TemplateUsage
	if err := protojson.Unmarshal(respBody, &usage); err != nil {
		return nil, err
	}
	return &usage, nil
}

func (c *ConfigCenterHTTPClient) ListTenantInitialCustomParams(ctx context.Context, req *pb.ListTenantInitialCustomParamsRequest) (*pb.ListTenantInitialCustomParamsResponse, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/tenants/%d/initial-custom-params", c.BaseURL, req.GetClusterType(), req.GetTenantId())
	resp, err := c.HTTPClient.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to list tenant initial custom params: %s, response: %s", resp.Status, string(respBody))
	}

	var response pb.ListTenantInitialCustomParamsResponse
	if err := protojson.Unmarshal(respBody, &response); err != nil {
		return nil, err
	}
	return &response, nil
}

func (c *ConfigCenterHTTPClient) BatchUpdateTenantInitialCustomParams(ctx context.Context, req *pb.BatchUpdateTenantInitialCustomParamsRequest) (*pb.BatchUpdateTenantInitialCustomParamsResponse, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/tenants/%d/initial-custom-params:batchUpdate", c.BaseURL, req.GetClusterType(), req.GetTenantId())
	body, err := protojson.Marshal(req)
	if err != nil {
		return nil, err
	}

	reqHTTP, err := http.NewRequestWithContext(ctx, http.MethodPatch, url, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	reqHTTP.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTPClient.Do(reqHTTP)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to batch update tenant initial custom params: %s, response: %s", resp.Status, string(respBody))
	}

	var response pb.BatchUpdateTenantInitialCustomParamsResponse
	if err := protojson.Unmarshal(respBody, &response); err != nil {
		return nil, err
	}
	return &response, nil
}

func (c *ConfigCenterHTTPClient) UpdateComponentImage(ctx context.Context, req *pb.UpdateComponentImageRequest) (*pb.ComponentImage, error) {
	url := fmt.Sprintf("%s/config-center/cluster-types/%s/component-images/%s", c.BaseURL, req.GetClusterType(), req.GetComponentName())
	body, err := protojson.Marshal(req)
	if err != nil {
		return nil, err
	}

	reqHTTP, err := http.NewRequestWithContext(ctx, http.MethodPut, url, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	reqHTTP.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTPClient.Do(reqHTTP)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to update component image: %s, response: %s", resp.Status, string(respBody))
	}

	var response pb.ComponentImage
	if err := protojson.Unmarshal(respBody, &response); err != nil {
		return nil, err
	}
	return &response, nil
}
