//go:build ops
// +build ops

package utils

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/ddliu/go-httpclient"
	"k8s.io/apimachinery/pkg/api/resource"

	"gopkg.in/yaml.v2"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/ajg/form"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/message"
	"github.com/tidbcloud/observability/pkg/common/structs"
	"k8s.io/client-go/kubernetes"
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
	ctrl "sigs.k8s.io/controller-runtime"
)

type ConfigInfo struct {
	Service ServiceInfo `yaml:"service"`
	Auth0   Auth0Req    `yaml:"auth0"`
}

type ServiceInfo struct {
	ServerURL string   `yaml:"serverURL"`
	Vendor    string   `yaml:"vendor"`
	Region    string   `yaml:"region"`
	UserARN   string   `yaml:"userARN"`
	Specs     []string `yaml:"specs"`
	Endpoint  []string `yaml:"endpoint"`
}

type Auth0Req struct {
	GrantType    string `yaml:"grantType" json:"grant_type" form:"grant_type"`
	ClientID     string `yaml:"clientID" json:"client_id" form:"client_id"`
	ClientSecret string `yaml:"clientSecret" json:"client_secret" form:"client_secret"`
	Audience     string `yaml:"audience" json:"audience" form:"audience"`
}

var config *ConfigInfo
var token string
var randomID string
var kubeClient *kubernetes.Clientset

func GetService() *ServiceInfo {
	if config == nil {
		panic("config is nil")
	}
	return &config.Service
}
func GetToken() *string {
	return &token
}

func HandleResp(t *testing.T, resp *http.Response, data interface{}) {
	respContent, err := io.ReadAll(resp.Body)
	assert.NoError(t, err)
	err = UnmarshalRespData(respContent, data)
	assert.NoError(t, err)
}

func SetRandomID(s string) {
	randomID = s
}

func PrepareConfig(path string) {
	in, err := os.ReadFile(path)
	if err != nil {
		panic(err)
	}
	var c ConfigInfo
	err = yaml.Unmarshal(in, &c)
	if err != nil {
		panic(err)
	}
	config = &c
}

func DefaultAuth0Req() Auth0Req {
	return config.Auth0
}

func PrepareToken() {
	body, err := form.EncodeToString(DefaultAuth0Req())
	if err != nil {
		panic(err)
	}
	resp, err := http.Post("https://tidb-soc2.us.auth0.com/oauth/token",
		"application/x-www-form-urlencoded",
		strings.NewReader(body))
	if err != nil {
		panic(err)
	}
	respContent, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	auth0Resp := &Auth0Resp{}
	err = json.Unmarshal(respContent, auth0Resp)
	if err != nil {
		panic(err)
	}
	token = auth0Resp.AccessToken
}

func PrepareKubeClient() {
	var err error
	os.Setenv("KUBECONFIG", fmt.Sprintf("%s/.kube/config", os.Getenv("HOME")))
	kubeConfig := ctrl.GetConfigOrDie()
	kubeClient, err = kubernetes.NewForConfig(kubeConfig)
	if err != nil {
		panic(err)
	}
}

const DefaultBizType = "ft-biz"

func ClearFtNamespaces() {
	names, err := kubeClient.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{})
	if err != nil {
		panic(err)
	}
	for _, one := range names.Items {
		if strings.Contains(one.Name, DefaultBizType) {
			err := kubeClient.CoreV1().Namespaces().Delete(context.Background(), one.Name, metav1.DeleteOptions{})
			if err != nil {
				panic(err)
			}
		}
	}
}

var ApiPrefix = "/api/v1"

type Auth0Resp struct {
	AccessToken string `json:"access_token" form:"access_token"`
}

func DefaultCreateReq() message.CreateClusterReq {
	s := GetService()
	return message.CreateClusterReq{
		ClusterBaseInfo: structs.ClusterBaseInfo{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.ProvidedVendor(s.Vendor),
				Region: s.Region,
			},
			TenantID:           randomID,
			ProjectID:          randomID,
			ApplicationID:      randomID,
			BizType:            DefaultBizType,
			Version:            "v1",
			ShareMode:          constants.EXCLUSIVE,
			Tags:               []string{"tag1", "default"},
			DataPropertyRights: constants.DataPropertyRights(1),
		},
		AgentAccessARN: s.UserARN,
		MonitorSpecID:  s.Specs[0],
	}
}

func HandleWithDefault(handle func(req message.CreateClusterReq) message.CreateClusterReq) message.CreateClusterReq {
	return handle(DefaultCreateReq())
}

func CreateReq(req message.CreateClusterReq) *http.Request {
	api := fmt.Sprintf("%s%s/clusters/",
		GetService().ServerURL, ApiPrefix)

	body, _ := json.Marshal(req)
	request, _ := http.NewRequest("POST", api, strings.NewReader(string(body)))
	WithHeader(request)
	return request
}

func DetailReq(req message.DetailClusterReq) *http.Request {
	api := fmt.Sprintf("%s%s/clusters/%s?vendor=%s&region=%s",
		GetService().ServerURL, ApiPrefix, req.ID, req.Vendor, req.Region)

	body, _ := json.Marshal(req)
	request, _ := http.NewRequest("GET", api, strings.NewReader(string(body)))
	WithHeader(request)
	return request
}

func ModifySpecReq(req message.ModifyMonitorSpecReq) *http.Request {
	api := fmt.Sprintf("%s%s/clusters/%s/monitor-spec/?vendor=%s&region=%s",
		GetService().ServerURL, ApiPrefix, req.ID, req.Vendor, req.Region)

	body, _ := json.Marshal(req)
	request, _ := http.NewRequest("PUT", api, strings.NewReader(string(body)))
	WithHeader(request)
	return request
}

func WithHeader(request *http.Request) {
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("%s %s", constants.BearerPrefix, token))

}

func WithHeaderV2(request *http.Request) {
	request.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("%s %s", constants.BearerPrefix, token))
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Accept", "*/*")
	request.Header.Set("accept-encoding", "gzip, deflate, br")
}

func CheckStatus(t *testing.T, id string, intermediateStatus, finalStatus constants.ResourceStatus) {
	assert.Contains(t, []constants.ResourceStatus{intermediateStatus, finalStatus, ""}, GetCluster(id).Status)

	ticker := time.NewTicker(5 * time.Second)
	defer ticker.Stop()

	count := 30

	for range ticker.C {
		cluster := GetCluster(id)
		assert.Contains(t, []constants.ResourceStatus{intermediateStatus, finalStatus, ""}, cluster.Status)

		if cluster.Status == finalStatus {
			return
		}
		// timeout
		count -= 1
		assert.NotEqual(t, 0, count)
	}
}

func CreateCluster(req message.CreateClusterReq) *message.CreateClusterResp {
	request := CreateReq(req)
	resp, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	clustersResp := &message.CreateClusterResp{}
	respContent, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	if resp.StatusCode != 202 {
		panic(string(respContent))
	}
	err = UnmarshalRespData(respContent, clustersResp)
	if err != nil {
		panic(err)
	}
	return clustersResp
}

func GetClusterIfExisted(id string) (bool, *structs.ObservabilityCluster) {
	s := GetService()
	request := DetailReq(message.DetailClusterReq{
		CommonReqInfo: structs.CommonReqInfo{
			Vendor: constants.ProvidedVendor(s.Vendor),
			Region: s.Region,
		},
		ID: id,
	})
	resp, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	if resp.StatusCode == http.StatusNotFound {
		return false, nil
	}
	clustersResp := &message.DetailClusterResp{}
	respContent, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}

	err = UnmarshalRespData(respContent, clustersResp)
	if err != nil {
		panic(err)
	}
	return true, &clustersResp.ObservabilityCluster
}

func UnmarshalRespData(data []byte, v interface{}) error {
	type Result struct {
		Code int         `json:"code"`
		Data interface{} `json:"data"`
	}

	result := &Result{
		Data: v,
	}

	return json.Unmarshal(data, result)
}

func GetCluster(id string) *structs.ObservabilityCluster {
	existed, cluster := GetClusterIfExisted(id)
	if existed {
		return cluster
	} else {
		panic(fmt.Sprintf("cluster id not found, %s", id))
	}
}

func ModifySpec(id, spec string) *http.Response {
	s := GetService()
	request := ModifySpecReq(message.ModifyMonitorSpecReq{
		CommonReqInfo: structs.CommonReqInfo{
			Vendor: constants.ProvidedVendor(s.Vendor),
			Region: s.Region,
		},
		ID:     id,
		SpecID: spec,
	})
	resp, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	return resp
}

func ModifyMonitorStorage(id string, ns string, pvcName string, request resource.Quantity) *http.Response {
	s := GetService()
	req := ModifyMonitorStorageReq(message.ModifyMonitorStorageReq{
		CommonReqInfo: structs.CommonReqInfo{
			Vendor: constants.ProvidedVendor(s.Vendor),
			Region: s.Region,
		},
		ID:        id,
		Namespace: ns,
		PVCName:   pvcName,
		Request:   request,
	})
	fmt.Println(id, ns, pvcName)
	fmt.Println(request)
	fmt.Println(req)
	resp, err := http.DefaultClient.Do(req)
	fmt.Println("do resp\n", resp)
	fmt.Println(err)
	if err != nil {
		panic(err)
	}
	return resp
}

func ModifyMonitorStorageReq(req message.ModifyMonitorStorageReq) *http.Request {
	api := fmt.Sprintf("%s%s/clusters/%s/monitor-storage?vendor=%s&region=%s",
		GetService().ServerURL, ApiPrefix, req.ID, req.Vendor, req.Region)

	body, err := json.Marshal(req)
	if err != nil {
		panic(err)
	}
	fmt.Println(err)
	fmt.Println("body\n", string(body))
	fmt.Println("api\n", api)
	request, _ := http.NewRequest("POST", api, strings.NewReader(string(body)))
	WithHeader(request)
	return request
}

func ModifyMonitorResource(id string, tp corev1.ResourceName, ns string, specID string, pvcName string, request resource.Quantity) *http.Response {
	s := GetService()
	req := ModifyMonitorResourceReq(message.ModifyMonitorResourceReq{
		CommonReqInfo: structs.CommonReqInfo{
			Vendor: constants.ProvidedVendor(s.Vendor),
			Region: s.Region,
		},
		ID:           id,
		ResourceType: tp,
		ResourceMeta: map[corev1.ResourceName]message.ResourceMeta{
			tp: {
				Namespace: ns,
				SpecID:    specID,
				PVCName:   pvcName,
				Request:   request,
			},
		},
	})

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	return resp
}

func ModifyMonitorResourceReq(req message.ModifyMonitorResourceReq) *http.Request {
	api := fmt.Sprintf("%s%s/clusters/%s/monitor-resource?vendor=%s&region=%s",
		GetService().ServerURL, ApiPrefix, req.ID, req.Vendor, req.Region)

	body, err := json.Marshal(req)
	if err != nil {
		panic(err)
	}

	request, _ := http.NewRequest("POST", api, strings.NewReader(string(body)))

	WithHeaderV2(request)
	return request
}

func ModifyMonitorResourceV2(id string, tp corev1.ResourceName, ns string, specID string, pvcName string, request resource.Quantity) *httpclient.Response {
	s := GetService()
	api := fmt.Sprintf("%s%s/clusters/%s/monitor-resource?vendor=%s&region=%s",
		s.ServerURL, ApiPrefix, id, s.Vendor, s.Region)
	req := message.ModifyMonitorResourceReq{
		CommonReqInfo: structs.CommonReqInfo{
			Vendor: constants.ProvidedVendor(s.Vendor),
			Region: s.Region,
		},
		ID:           id,
		ResourceType: tp,
		ResourceMeta: map[corev1.ResourceName]message.ResourceMeta{
			tp: {
				Namespace: ns,
				SpecID:    specID,
				PVCName:   pvcName,
				Request:   request,
			},
		},
	}
	body, err := json.Marshal(req)
	if err != nil {
		panic(err)
	}
	fmt.Println("body\n", string(body))
	fmt.Println("api\n", api)
	resp, err := httpclient.WithHeaders(Headers()).PostJson(api, body)
	if err != nil {
		panic(err)
	}
	return resp
}

func Headers() map[string]string {
	headers := map[string]string{
		constants.AuthorizationHeader: fmt.Sprintf("%s %s", constants.BearerPrefix, token),
		"Content-Type":                "application/json",
		"Accept":                      "*/*",
	}
	return headers
}

func HandleAlertReq(req message.GlobalHandleAlertReq) *http.Request {
	api := fmt.Sprintf("%s%s/ops/events/alerts", GetService().ServerURL, ApiPrefix)
	fmt.Println(api)
	body, err := json.Marshal(req)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(body))
	request, _ := http.NewRequest("POST", api, strings.NewReader(string(body)))
	return request
}
