//go:build logss
// +build logss

package utils

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

	"cloud.google.com/go/storage"
	"github.com/ajg/form"
	"github.com/andygrunwald/go-jira"
	v2Aws "github.com/aws/aws-sdk-go-v2/aws"
	v2Config "github.com/aws/aws-sdk-go-v2/config"
	v2S3 "github.com/aws/aws-sdk-go-v2/service/s3"
	v2S3Type "github.com/aws/aws-sdk-go-v2/service/s3/types"
	"github.com/ddliu/go-httpclient"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/message"
	"github.com/tidbcloud/observability/pkg/common/structs"
	"google.golang.org/api/iterator"
	"gopkg.in/yaml.v2"
)

type ConfigInfo struct {
	Auth0              Auth0Req   `yaml:"auth0"`
	Region             RegionInfo `yaml:"region"`
	ClinicToken        string     `yaml:"clinicToken"`
	ClinicTokenStaging string     `yaml:"clinicTokenStaging"`
	ClinicURL          string     `yaml:"clinicURL"`
	Env                string     `yaml:"env"`
	JiraURL            string     `yaml:"jiraURL"`
	JiraToken          string     `yaml:"jiraToken"`
}

type RegionInfo struct {
	ServerURL string              `yaml:"serverURL"`
	Region    map[string][]string `yaml:"region"`
	BizType   []string            `yaml:"bizType"`
}

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"`
}

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

var config *ConfigInfo
var token string
var ApiPrefix = "/api/v1"
var S3ListCount int64
var GcsListCount int64

func GetRegion() *RegionInfo {
	if config == nil {
		panic("config is nil")
	}
	return &config.Region
}

func GetConfig() *ConfigInfo {
	if config == nil {
		panic("config is nil")
	}
	return config
}
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 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
}

type QueryResponse struct {
	Status    string `json:"status"`
	IsPartial bool   `json:"isPartial"`
	Data      Data   `json:"data"`
}

type Data struct {
	ResultType string   `json:"resultType"`
	Result     []Result `json:"result"`
}

type Result struct {
	Metric map[string]string `json:"metric"`
	Value  []interface{}     `json:"value"`
}

type AlertRequest struct {
	Labels       map[string]string `json:"labels"`
	Annotations  map[string]string `json:"annotations"`
	GeneratorURL string            `json:"generatorURL" validate:"optional"`
	StartsAt     string            `json:"startsAt" validate:"optional"`
}

type AlertPostResponse struct {
}

func QueryCluster(queryReq message.QueryClustersReq) *message.QueryClustersResp {
	request := QueryReq(queryReq)
	resp, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	clustersResp := &message.QueryClustersResp{}
	respContent, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	err = UnmarshalRespData(respContent, clustersResp)
	if err != nil {
		panic(err)
	}
	return clustersResp
}

func QueryReq(req message.QueryClustersReq) *http.Request {
	r := GetRegion()
	queryString := fmt.Sprintf("%s%s/clusters/?vendor=%s&region=%s&bizType=%s",
		r.ServerURL, ApiPrefix, req.Vendor, req.Region, req.BizType)
	request, _ := http.NewRequest("GET", queryString, nil)
	WithHeader(request)
	return request
}

func GetClusterDetail(req message.DetailClusterReq) *structs.ObservabilityCluster {
	request := DetailReq(req)
	resp, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	if resp.StatusCode == http.StatusNotFound {
		return 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 &clustersResp.ObservabilityCluster
}

func DetailReq(req message.DetailClusterReq) *http.Request {
	r := GetRegion()
	api := fmt.Sprintf("%s%s/clusters/%s?vendor=%s&region=%s",
		r.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 WithHeader(request *http.Request) {
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("%s %s", constants.BearerPrefix, token))
}

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)
}

type Alert struct {
	Labels       map[string]string `json:"labels"`
	Annotations  map[string]string `json:"annotations"`
	GeneratorURL string            `json:"generatorURL" validate:"optional"`
}

func PostAlertManager(domain string, alert Alert) (*httpclient.Response, error) {
	api := fmt.Sprintf("%s/api/v1/alerts", domain)
	req := make([]*Alert, 0)
	req = append(req, &alert)
	body, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}
	headers := map[string]string{
		"Content-Type": "application/json",
	}
	resp, err := httpclient.WithHeaders(headers).PostJson(api, body)
	return resp, err
}

func DefaultSessionV2(region string) (v2Aws.Config, error) {
	cfg, err := v2Config.LoadDefaultConfig(context.TODO(),
		v2Config.WithRegion(region))
	if err != nil {
		fmt.Errorf("Failed to create session, %v", err)
	}

	return cfg, err
}

func ReadS3Logs(cluster *structs.ObservabilityCluster, cfg v2Aws.Config) []v2S3Type.Object {
	now := time.Now().Unix()
	timeStamp := strconv.FormatInt(now, 10)

	var component string
	property := cluster.ClusterBaseInfo.DataPropertyRights
	if cluster.ApplicationID == "k8s-infra" {
		property = 1
	}
	if property == 0 {
		component = "tikv"
	} else if property == 1 {
		component = "agent"
	}

	prefix := fmt.Sprintf("%d/%s/%s/%s/logs/%s/%s", cluster.ClusterBaseInfo.DataPropertyRights, cluster.TenantID, cluster.ProjectID, cluster.ApplicationID, component, timeStamp[0:5])
	t, _ := strconv.ParseInt(timeStamp[0:5]+"00000", 10, 64)
	path := fmt.Sprintf("%d/%s/%s/%s/logs/%s/", cluster.ClusterBaseInfo.DataPropertyRights, cluster.TenantID, cluster.ProjectID, cluster.ApplicationID, component)

	fmt.Printf("path: %s  startTime: %v\n", path, time.Unix(t, 0).Format("2006-01-02 15:04:05"))
	objects, err := v2S3.NewFromConfig(cfg).ListObjects(context.TODO(), &v2S3.ListObjectsInput{
		Bucket: v2Aws.String(cluster.StorageInfo[0].BucketName),
		Prefix: v2Aws.String(prefix),
	})

	if err != nil {
		panic(err)
	}
	fmt.Println("total logs:", len(objects.Contents))
	//for _, a := range objects.Contents {
	//	fmt.Printf("%s (%d bytes, %v) \n", strings.Trim(*a.Key, path), a.Size, a.LastModified.Format("2006-01-02 15:04:05"))
	//}
	return objects.Contents
}

func GetMinutesSinceLastUpdateTimeS3(cluster *structs.ObservabilityCluster, cfg v2Aws.Config, createTime time.Time) (lastModifiedTime time.Time,
	minutesToLastModified int, curTime time.Time) {
	prefix := fmt.Sprintf("%d/%s/%s/%s/logs/", cluster.ClusterBaseInfo.DataPropertyRights, cluster.TenantID, cluster.ProjectID, cluster.ApplicationID)
	objects, err := v2S3.NewFromConfig(cfg).ListObjects(context.TODO(), &v2S3.ListObjectsInput{
		Bucket:    v2Aws.String(cluster.StorageInfo[0].BucketName),
		Prefix:    v2Aws.String(prefix),
		Delimiter: v2Aws.String("/"),
	})
	if err != nil {
		panic(err)
	}

	var subPrefixes []string
	for _, commonPrefix := range objects.CommonPrefixes {
		subPrefixFullPathArray := strings.Split(*commonPrefix.Prefix, "/")
		subPrefixes = append(subPrefixes, subPrefixFullPathArray[len(subPrefixFullPathArray)-2])
	}
	lastModifiedTime = createTime
	minutesToLastModified = int(time.Now().Sub(lastModifiedTime).Minutes())
	for _, s := range subPrefixes {
		objects, err = v2S3.NewFromConfig(cfg).ListObjects(context.TODO(), &v2S3.ListObjectsInput{
			Bucket: v2Aws.String(cluster.StorageInfo[0].BucketName),
			Prefix: v2Aws.String(fmt.Sprintf("%s%s/", prefix, s)),
		})
		S3ListCount++
		if err != nil {
			panic(err)
		}
		tempCurTime := time.Now()
		// get a fundamental minutesToLastModified
		if len(objects.Contents) > 0 {
			o := objects.Contents[len(objects.Contents)-1]
			tempMinutesToLastModified := int(tempCurTime.Sub(*o.LastModified).Minutes())
			if tempMinutesToLastModified < minutesToLastModified {
				lastModifiedTime = *o.LastModified
				minutesToLastModified = tempMinutesToLastModified
				curTime = tempCurTime
			}
			//fmt.Printf("%s (%d bytes, %v) \n", *o.Key, o.Size, o.LastModified.Format("2006-01-02 15:04:05"))
		}

		// if object >= 1000, get logs within the last 6.5 hours. Then get the new minutesToLastModified if new object > 0
		if len(objects.Contents) >= 1000 {
			now := time.Now().Unix()
			timeStamp := strconv.FormatInt(now, 10)
			pre := fmt.Sprintf("%s%s/%s", prefix, s, timeStamp[0:6])
			newObjects, err := v2S3.NewFromConfig(cfg).ListObjects(context.TODO(), &v2S3.ListObjectsInput{
				Bucket: v2Aws.String(cluster.StorageInfo[0].BucketName),
				Prefix: v2Aws.String(pre),
			})
			S3ListCount++
			if err != nil {
				panic(err)
			}
			if len(newObjects.Contents) > 0 {
				o := newObjects.Contents[len(newObjects.Contents)-1]
				tempMinutesToLastModified := int(tempCurTime.Sub(*o.LastModified).Minutes())
				if tempMinutesToLastModified < minutesToLastModified {
					lastModifiedTime = *o.LastModified
					minutesToLastModified = tempMinutesToLastModified
					curTime = tempCurTime
				}
			}
		}
	}
	return
}

func GetMinutesSinceLastUpdateTimeGCS(cluster *structs.ObservabilityCluster, createTime time.Time) (lastModifiedTime time.Time, minutesToLastModified int, curTime time.Time, lastBlock string) {
	prefix := fmt.Sprintf("%d/%s/%s/%s/logs/", cluster.ClusterBaseInfo.DataPropertyRights, cluster.TenantID, cluster.ProjectID, cluster.ApplicationID)

	ctx := context.Background()

	client, err := storage.NewClient(context.TODO())
	if err != nil {
		panic(err)
	}
	defer client.Close()

	_, cancel := context.WithTimeout(ctx, time.Second*10)
	defer cancel()

	bucket := client.Bucket(cluster.StorageInfo[0].BucketName)
	objects := bucket.Objects(context.TODO(), &storage.Query{
		Prefix: prefix,
	})
	GcsListCount++

	lastModifiedTime = createTime
	minutesToLastModified = int(time.Now().Sub(lastModifiedTime).Minutes())

	for {
		attrs, err := objects.Next()

		if err == iterator.Done {
			break
		}
		if err != nil {
			panic(err)
		}
		tempCurTime := time.Now()
		tempMinutesToLastModified := int(tempCurTime.Sub(attrs.Updated).Minutes())
		if tempMinutesToLastModified < minutesToLastModified {
			lastModifiedTime = attrs.Updated
			minutesToLastModified = tempMinutesToLastModified
			curTime = tempCurTime
			lastBlock = attrs.Name
		}
	}

	return
}

func ReadGcsLogs(cluster *structs.ObservabilityCluster) map[string]int64 {
	ctx := context.Background()

	client, err := storage.NewClient(context.TODO())
	if err != nil {
		panic(err)
	}
	defer client.Close()

	_, cancel := context.WithTimeout(ctx, time.Second*10)
	defer cancel()

	time := time.Now().Unix()
	timeStamp := strconv.FormatInt(time, 10)

	var component []string
	property := cluster.ClusterBaseInfo.DataPropertyRights
	if cluster.ApplicationID == "k8s-infra" {
		property = 1
	}
	if property == 0 {
		component = []string{"tikv", "tidb", "pd"}
	} else if property == 1 {
		component = []string{"agent", "etcd", "prometheus"}
	}
	size := make(map[string]int64)
	for _, c := range component {
		prefix := fmt.Sprintf("%d/%s/%s/%s/logs/%s/%s", cluster.ClusterBaseInfo.DataPropertyRights, cluster.TenantID, cluster.ProjectID, cluster.ApplicationID, c, timeStamp[0:5])
		fmt.Println(prefix)
		bucket := client.Bucket(cluster.StorageInfo[0].BucketName)
		objects := bucket.Objects(context.TODO(), &storage.Query{
			Prefix: prefix,
		})
		// get file size
		for {
			attrs, err := objects.Next()
			if err == iterator.Done {
				break
			}
			if err != nil {
				fmt.Println(err)
				return size
				//panic(err)
			}
			size[attrs.Name] = attrs.Size
		}
		if len(size) > 0 {
			return size
		} else {
			continue
		}
	}
	return size
}

type QueryTiDBClusterResp struct {
	Total int64         `json:"total"`
	Items []TiDBCluster `json:"items"`
}

type TiDBCluster struct {
	TenantID         string `json:"tenantID"`
	TenantName       string `json:"tenantName"`
	TenantPlan       string `json:"tenantPlan"`
	ClusterID        string `json:"clusterID"`
	ClusterName      string `json:"clusterName"`
	ClusterCreatedAt int64  `json:"clusterCreatedAt"`
	ClusterDuration  string `json:"clusterDuration"`
	ClusterDeletedAt int64  `json:"clusterDeletedAt"`

	ClusterStatus       string `json:"clusterStatus"`
	ClusterDeployType   string `json:"clusterDeployType"`
	ClusterRegionName   string `json:"clusterRegionName"`
	ClusterProviderName string `json:"clusterProviderName"`
	ClusterShootName    string `json:"clusterShootName"`
	ClusterK8sNamespace string `json:"ClusterK8sNamespace"`
	ClusterNetworkName  string `json:"ClusterNetworkName"`
	ClusterType         string `json:"clusterType"`
}

func clinicClusters2URL(host, clinicURL, applicationID string) string {
	q := url.Values{}
	q.Set("cluster_id", applicationID)
	q.Set("sort", "")
	q.Set("order", "")
	q.Set("limit", "1")
	q.Set("page", "1")
	return fmt.Sprintf("https://%s%s/clinic/api/v1/dashboard/clusters2?%s", host, clinicURL, q.Encode())
}

func GetTiDBCluster(cluster structs.ClusterBaseInfo) *TiDBCluster {
	var api string
	var clinicToken string
	if strings.HasPrefix(cluster.BizType, "staging") {
		api = clinicClusters2URL("staging-", GetConfig().ClinicURL, cluster.ApplicationID)
		clinicToken = GetConfig().ClinicTokenStaging
	} else {
		api = clinicClusters2URL("", GetConfig().ClinicURL, cluster.ApplicationID)
		clinicToken = GetConfig().ClinicToken
	}
	if GetConfig().Env == "staging" {
		api = clinicClusters2URL("staging-", GetConfig().ClinicURL, cluster.ApplicationID)
		clinicToken = GetConfig().ClinicTokenStaging
	}
	// request of querying tidb clusters
	request, _ := http.NewRequest("GET", api, nil)
	WithHeaderV2(request, clinicToken)

	// get response
	resp, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	//fmt.Println(resp, "\n", resp.Body) // get empty body
	if resp.StatusCode != http.StatusOK {
		fmt.Println(resp) // get empty body
		panic("request failed")
	}

	clustersResp := &QueryTiDBClusterResp{}
	respContent, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	//fmt.Println(string(respContent))
	err = json.Unmarshal(respContent, clustersResp)
	if err != nil {
		panic(err)
	}
	//fmt.Println(clustersResp)
	if len(clustersResp.Items) == 0 {
		return nil
	}
	return &clustersResp.Items[0]
}

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

func GetTidbClusterStatus(tidbCluster *TiDBCluster, tenantID, applicationID string) (clinicStatus string, createdAt time.Time) {
	if tidbCluster == nil {
		return
	}
	if tidbCluster.TenantID == tenantID && tidbCluster.ClusterID == applicationID {
		return tidbCluster.ClusterStatus, time.Unix(0, tidbCluster.ClusterCreatedAt*int64(time.Millisecond))
	}
	return
}

var jiraProject = map[string]string{
	"dev":     "O11YDEV",
	"staging": "O11YSTAG",
	"prod":    "O11Y",
}

var jiraTag = map[string]string{
	"dev":     "DEV",
	"staging": "STAGING",
	"prod":    "PROD",
}

func PostToJira(des string) (*http.Response, error) {
	issue := &jira.Issue{
		Fields: &jira.IssueFields{
			Project: jira.Project{
				Key: jiraProject[GetConfig().Env],
			},
			Type: jira.IssueType{
				Name: "Task",
			},
			Description: des,
			Summary:     fmt.Sprintf("[%s]O11yDailTestLogsCheckError", jiraTag[GetConfig().Env]),
			Priority:    &jira.Priority{Name: "Warning"},
			Components: []*jira.Component{
				{
					Name: "observability-regional-server",
				},
			},
		},
	}
	api := GetConfig().JiraURL
	body, _ := json.Marshal(issue)
	request, _ := http.NewRequest("POST", api, strings.NewReader(string(body)))
	WithHeaderV2(request, GetConfig().JiraToken)
	resp, err := http.DefaultClient.Do(request)

	return resp, err
}
