package cluster

import (
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/api/common"
	error2 "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/db"
	model2 "github.com/pingcap-inc/clinic/pkg/db/model"
	"github.com/pingcap-inc/clinic/pkg/es"
	"github.com/pingcap-inc/clinic/pkg/importer/fetchconf"
	"github.com/pingcap-inc/clinic/pkg/resource_limit"
	"github.com/pingcap-inc/clinic/pkg/upload_file/manager"
)

// @Summary		download total package
// @Tags			Cluster
// @Description	download file
// @Produce		json
// @Param			x-csrf-token	header	string	true	"get value from login.ValidationResp response"
// @Param			oid				path	string	true	"organization id"
// @Param			cid				path	string	true	"cluster id"
// @Param			itemid			path	string	true	"item id"
// @Success		200				{file}	file
// @Router			/orgs/{oid}/clusters/{cid}/download/{itemid} [get]
func (s *ClusterService) Download(c *gin.Context) {
	itemID := c.Param("itemid")
	clusterID := c.Param("cid")
	orgID, err := common.GetOrgIDFromAPIPath(c)
	if err != nil {
		_ = c.Error(error2.ErrNotFound.New("unknown organization id"))
		return
	}
	if _, _, ok := s.checkPackagePermission(c, itemID); !ok {
		return
	}

	u := session.GetUser(c)
	if !u.Internal && !s.quotalimiter.AskForResource(resource_limit.DownloadCntPerCluster, 1, orgID, clusterID) {
		_ = c.Error(error2.ErrPreConditionFailed.New("%s", resource_limit.DownloadCntPerCluster.Reason()))
		return
	}

	if err := downloadFileByFileUUID(itemID, func(originalName string) string {
		return originalName
	}, itemID, s.manager, c); err != nil {
		log.Error("download file failed", zap.String("file", itemID), zap.Error(err))
		_ = c.Error(error2.ErrBadRequest.New("download file failed"))
	}
}

// @Summary		meta information of package file
// @Tags			Cluster
// @Description	meta information of package file
// @Produce		json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Param			cid				path		string	true	"cluster id"
// @Param			itemid			path		string	true	"item id"
// @Success		200				{object}	FileMeta
// @Router			/orgs/{oid}/clusters/{cid}/{itemid}/meta [get]
func (s *ClusterService) FileMeta(c *gin.Context) {
	itemID := c.Param("itemid")
	_, file, ok := s.checkPackagePermission(c, itemID)
	if !ok {
		return
	}

	confSize := int64(0)
	if file.RebuildConfigurationStatus == string(db.StatusSuccess) {
		resp, _, err := s.manager.DescribeFileByFileUUID(file.FileUUID, fmt.Sprintf("%s%s", file.FileUUID, fetchconf.FetchConfFileSubffix))
		if err != nil {
			log.Error("describe file failed", zap.String("fileUUID", file.FileUUID), zap.Error(err))
		}

		if resp != nil {
			confSize = resp.ContentLen
		}
	}

	c.JSON(http.StatusOK, FileMeta{
		TotalSize:  getFileMSize(file.FileLen),
		BriefSize:  getFileMSize(confSize),
		Collectors: file.Collectors,
		FileName:   file.FileOriginalName,
	})
}

var byteUnits = []string{"B", "KB", "MB", "GB"}

func getFileMSize(sizeInBytes int64) string {
	if sizeInBytes == 0 {
		return ""
	}

	fileSizeInBytes := float64(sizeInBytes)
	i := 0

	for fileSizeInBytes > 1024 && i < len(byteUnits)-1 {
		fileSizeInBytes = fileSizeInBytes / float64(1024)
		i++
	}

	return fmt.Sprintf("%v%s", fmt.Sprintf("%.1f", fileSizeInBytes), byteUnits[i])
}

func (s *ClusterService) checkPackagePermission(c *gin.Context, packageID string) (*db.Organization, *db.UploadFile, bool) {
	_, org, cluster, ok, err := common.CanReadCluster(c, s.rbac, c.Param("cid"))
	if err != nil {
		_ = c.Error(error2.ErrNotFound.New("%s", err.Error()))
		return nil, nil, false
	}
	if !ok {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return nil, nil, false
	}

	file, err := model2.QueryUploadFile(packageID)
	if err != nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid item"))
		return nil, nil, false
	}
	if file == nil {
		_ = c.Error(error2.ErrNotFound.New("not found the resource"))
		return nil, nil, false
	}
	if file.ClusterID != cluster.ClusterID {
		_ = c.Error(error2.ErrBadRequest.New("invalid cluster id"))
		return nil, nil, false
	}

	return org, file, true
}

// @Summary		download total package
// @Tags			Cluster
// @Description	download file
// @Produce		json
// @Param			x-csrf-token	header	string	true	"get value from login.ValidationResp response"
// @Param			oid				path	string	true	"organization id"
// @Param			cid				path	string	true	"cluster id"
// @Param			itemid			path	string	true	"item id"
// @Success		200				{file}	file
// @Router			/orgs/{oid}/clusters/{cid}/downloadConf/{itemid} [get]
func (s *ClusterService) DownloadConf(c *gin.Context) {
	itemID := c.Param("itemid")
	_, file, ok := s.checkPackagePermission(c, itemID)
	if !ok {
		return
	}

	if file.RebuildConfigurationStatus != string(db.StatusSuccess) {
		_ = c.Error(error2.ErrBadRequest.New("need to rebuild"))
		return
	}

	fileName := fmt.Sprintf("%s%s", itemID, fetchconf.FetchConfFileSubffix)
	if err := downloadFileByFileUUID(itemID, excludeLogAndMetricsFileName, fileName, s.manager, c); err != nil {
		log.Error("download file failed", zap.String("file", itemID), zap.Error(err))
		_ = c.Error(error2.ErrBadRequest.New("download file failed"))
	}
}

func excludeLogAndMetricsFileName(originalName string) string {
	index := strings.Index(originalName, ".")
	if index < 0 {
		return fmt.Sprintf("exclude-log-metric-%s", originalName)
	}

	return fmt.Sprintf("%s-exclude-log-metric%s", originalName[:index], originalName[index:])
}

func downloadFileByFileUUID(fileUUID string, downloadNameFunc func(originalName string) string, fileName string, manager *manager.FileStoreageManager, context *gin.Context) error {
	if fileUUID == "" {
		log.Warn("empty file uuid parameter")
		return errors.New("bad parameter")
	}

	object, on, err := manager.GetFileByFileUUID(fileUUID, fileName, context.Request.Header.Get("Range"))
	if err != nil {
		log.Error("get file failed", zap.String("file", fileUUID), zap.Error(err))
		return err
	}

	downloadName := downloadNameFunc(on)

	extraHeaders := map[string]string{
		"ETag":                object.ETag,
		"Content-Disposition": fmt.Sprintf(`attachment; filename="%s"`, downloadName),
		"Content-Range":       object.ContentRange,
		"Accept-Ranges":       object.AcceptRanges,
		"Content-Length":      fmt.Sprintf("%d", object.ContentLen),
	}

	code := http.StatusOK
	if context.Request.Header.Get("Range") != "" && object.ContentRange != "" {
		code = http.StatusPartialContent
		log.Info("download partial content", zap.String("Request Range", context.Request.Header.Get("Range")), zap.String("Response Range", object.ContentRange), zap.Int64("Response Length", object.ContentLen))
	}

	context.DataFromReader(code, object.ContentLen, object.ContentType, object.Body, extraHeaders)
	return object.Body.Close()
}

// @Summary		log url
// @Tags			Cluster
// @Description	get log url
// @Produce		json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Param			cid				path		string	true	"cluster id"
// @Param			itemid			path		string	true	"item id"
// @Param			startTime		query		int		false	"startTime of dashboard show"
// @Param			endTime			query		int		false	"endTime of dashboard show"
// @Success		200				{object}	DataUrl
// @Router			/orgs/{oid}/clusters/{cid}/logurl/{itemid} [get]
func (s *ClusterService) LogUrl(c *gin.Context) {
	itemID := c.Param("itemid")

	org, file, ok := s.checkPackagePermission(c, itemID)
	if !ok {
		return
	}

	indexPattern, err := es.SearchKibanaIndexPattern(org.ID, s.cfg.SinkConfig.LogConfig, es.GetLogIndexName(org.ID))
	if err != nil {
		log.Error("search kibana index pattern failed", zap.String("fileUUID", itemID), zap.Error(err))

		_ = c.Error(error2.ErrInternal.New("get log url failed"))
		return
	}
	//(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:'2022-01-08T06:09:23.625Z',to:'2022-03-23T02:01:50.700Z'))&_a=(columns:!(cluster_id,component),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'59163a90-a9b0-11ec-b5e9-0181f5b402fd',key:cluster_id,negate:!f,params:(query:'7077461722135969652'),type:phrase),query:(match_phrase:(cluster_id:'7077461722135969652'))),('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'59163a90-a9b0-11ec-b5e9-0181f5b402fd',key:packageid,negate:!f,params:(query:'773acf5645eeeb44-5163395f51c5dff8-5e4e39b3832854a9'),type:phrase),query:(match_phrase:(packageid:'773acf5645eeeb44-5163395f51c5dff8-5e4e39b3832854a9')))),index:'59163a90-a9b0-11ec-b5e9-0181f5b402fd',interval:auto,query:(language:kuery,query:''),sort:!())

	// 2022-02-26T12:00:00%2B08:00
	// url.QueryEscape(cstTimeZone(file.BeginTime).Format(time.RFC3339))
	//nolint:misspell
	// u := fmt.Sprintf("/kibana/app/discover?var-org=%d&_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:'%d',to:'%d'))&_a=(columns:!(cluster_id,component),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'%s',key:cluster_id,negate:!f,params:(query:'%s'),type:phrase),query:(match_phrase:(cluster_id:'%s'))),('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'%s',key:packageid,negate:!f,params:(query:'%s'),type:phrase),query:(match_phrase:(packageid:'%s')))),index:'%s',interval:auto,query:(language:kuery,query:''),sort:!())",
	//	org.ID, url.QueryEscape(cstTimeZone(file.BeginTime).Format(time.RFC3339)), url.QueryEscape(cstTimeZone(file.EndTime).Format(time.RFC3339)), indexPattern, file.ClusterID, file.ClusterID, indexPattern, file.FileUUID, file.FileUUID, indexPattern)

	startTime, err := strconv.ParseInt(c.Query("startTime"), 10, 64)
	if err != nil {
		_ = c.Error(error2.ErrBadRequest.New("%s", err.Error()))
		return
	}
	endTime, err := strconv.ParseInt(c.Query("endTime"), 10, 64)
	if err != nil {
		_ = c.Error(error2.ErrBadRequest.New("%s", err.Error()))
		return
	}

	c.JSON(http.StatusOK, DataUrl{
		URL: getKibanaURL(file, org.ID, indexPattern, time.Unix(startTime, 0), time.Unix(endTime, 0)),
	})
}

func getKibanaURL(file *db.UploadFile, orgID uint, indexPattern string, startTime, endTime time.Time) string {
	start := common.CstTimeZone(&startTime).Format(time.RFC3339)
	end := common.CstTimeZone(&endTime).Format(time.RFC3339)

	//nolint:misspell
	return fmt.Sprintf("/kibana/app/discover?security_tenant=%s#/?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:'%s',to:'%s'))&_a=(columns:!(component,level,message),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'%s',key:cluster_id,negate:!f,params:(query:'%s'),type:phrase),query:(match_phrase:(cluster_id:'%s'))),('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'%s',key:packageid,negate:!f,params:(query:'%s'),type:phrase),query:(match_phrase:(packageid:'%s')))),index:'%s',interval:auto,query:(language:kuery,query:''),sort:!())",
		es.GetOrgName(orgID), url.QueryEscape(start), url.QueryEscape(end), indexPattern, file.ClusterID, file.ClusterID, indexPattern, file.FileUUID, file.FileUUID, indexPattern)
}
